Add new file activity.

This commit is contained in:
2025-07-13 16:12:03 +08:00
parent 65fe85e6c1
commit e9f6e1968e
12 changed files with 145 additions and 24 deletions

View File

@@ -234,6 +234,7 @@ export const i18nData = {
"Comment Details": "Comment Details", "Comment Details": "Comment Details",
"Posted a comment": "Posted a comment", "Posted a comment": "Posted a comment",
"Resources": "Resources", "Resources": "Resources",
"Added a new file": "Added a new file",
}, },
}, },
"zh-CN": { "zh-CN": {
@@ -461,6 +462,7 @@ export const i18nData = {
"Posted a comment": "发布了一个评论", "Posted a comment": "发布了一个评论",
"Resources": "资源", "Resources": "资源",
"Added a new file": "添加了新文件",
}, },
}, },
"zh-TW": { "zh-TW": {
@@ -688,6 +690,7 @@ export const i18nData = {
"Posted a comment": "發布了評論", "Posted a comment": "發布了評論",
"Resources": "資源", "Resources": "資源",
"Added a new file": "添加了新檔案",
}, },
}, },
}; };

View File

@@ -177,6 +177,7 @@ export enum ActivityType {
ResourcePublished = 1, ResourcePublished = 1,
ResourceUpdated = 2, ResourceUpdated = 2,
NewComment = 3, NewComment = 3,
NewFile = 4,
} }
export interface Activity { export interface Activity {
@@ -187,4 +188,5 @@ export interface Activity {
resource?: Resource; resource?: Resource;
user?: User; user?: User;
comment?: Comment; comment?: Comment;
file?: RFile;
} }

View File

@@ -6,6 +6,8 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import Loading from "../components/loading.tsx"; import Loading from "../components/loading.tsx";
import { CommentContent } from "../components/comment_tile.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() { export default function ActivitiesPage() {
const [activities, setActivities] = useState<Activity[]>([]); const [activities, setActivities] = useState<Activity[]>([]);
@@ -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 }) { function ActivityCard({ activity }: { activity: Activity }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -67,6 +81,7 @@ function ActivityCard({ activity }: { activity: Activity }) {
t("Published a resource"), t("Published a resource"),
t("Updated a resource"), t("Updated a resource"),
t("Posted a comment"), t("Posted a comment"),
t("Added a new file"),
]; ];
const navigate = useNavigate(); const navigate = useNavigate();
@@ -97,6 +112,33 @@ function ActivityCard({ activity }: { activity: Activity }) {
<CommentContent content={activity.comment!.content} /> <CommentContent content={activity.comment!.content} />
</div> </div>
); );
} else if (activity.type === ActivityType.NewFile) {
content = (
<div>
<h4 className={"font-bold py-2"}>{activity.file!.filename}</h4>
<p className={"text-sm whitespace-pre-wrap"}>
{activity.file!.description}
</p>
<p className={"pt-1"}>
<Badge className={"badge-soft badge-secondary text-xs mr-2"}>
<MdOutlineArchive size={16} className={"inline-block"} />
{activity.file!.is_redirect
? t("Redirect")
: fileSizeToString(activity.file!.size)}
</Badge>
<Badge className={"badge-soft badge-accent text-xs mr-2"}>
<MdOutlinePhotoAlbum size={16} className={"inline-block"} />
{(() => {
let title = activity.resource!.title;
if (title.length > 20) {
title = title.slice(0, 20) + "...";
}
return title;
})()}
</Badge>
</p>
</div>
);
} }
return ( return (
@@ -112,6 +154,8 @@ function ActivityCard({ activity }: { activity: Activity }) {
navigate(`/resources/${activity.resource?.id}`); navigate(`/resources/${activity.resource?.id}`);
} else if (activity.type === ActivityType.NewComment) { } else if (activity.type === ActivityType.NewComment) {
navigate(`/comments/${activity.comment?.id}`); navigate(`/comments/${activity.comment?.id}`);
} else if (activity.type === ActivityType.NewFile) {
navigate(`/resources/${activity.resource?.id}#files`);
} }
}} }}
> >

View File

@@ -171,7 +171,10 @@ export default function ManageServerConfigPage() {
checked={config.allow_normal_user_upload} checked={config.allow_normal_user_upload}
className="toggle-primary toggle" className="toggle-primary toggle"
onChange={(e) => { onChange={(e) => {
setConfig({ ...config, allow_normal_user_upload: e.target.checked }); setConfig({
...config,
allow_normal_user_upload: e.target.checked,
});
}} }}
/> />
</fieldset> </fieldset>

View File

@@ -145,7 +145,9 @@ export default function StorageView() {
<tr key={s.id} className={"hover"}> <tr key={s.id} className={"hover"}>
<td> <td>
{s.name} {s.name}
{s.isDefault && <Badge className={"ml-1"}>{t("Default")}</Badge>} {s.isDefault && (
<Badge className={"ml-1"}>{t("Default")}</Badge>
)}
</td> </td>
<td>{new Date(s.createdAt).toLocaleString()}</td> <td>{new Date(s.createdAt).toLocaleString()}</td>
<td> <td>
@@ -173,15 +175,15 @@ export default function StorageView() {
> >
<a>{t("Delete")}</a> <a>{t("Delete")}</a>
</PopupMenuItem> </PopupMenuItem>
{!s.isDefault && <PopupMenuItem {!s.isDefault && (
<PopupMenuItem
onClick={() => { onClick={() => {
handleSetDefault(s.id); handleSetDefault(s.id);
}} }}
> >
<a> <a>t("Set as Default")</a>
t("Set as Default") </PopupMenuItem>
</a> )}
</PopupMenuItem>}
</ul>, </ul>,
document.getElementById( document.getElementById(
`set_default_button_${s.id}`, `set_default_button_${s.id}`,

View File

@@ -503,10 +503,7 @@ function Article({ resource }: { resource: ResourceDetails }) {
const href = props.href as string; const href = props.href as string;
const origin = window.location.origin; const origin = window.location.origin;
if ( if (href.startsWith(origin) || href.startsWith("/")) {
href.startsWith(origin) ||
href.startsWith("/")
) {
let path = href; let path = href;
if (path.startsWith(origin)) { if (path.startsWith(origin)) {
path = path.substring(origin.length); 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 navigate = useNavigate();
const [articleWidth, setArticleWidth] = useState<number | null>(null); const [articleWidth, setArticleWidth] = useState<number | null>(null);
@@ -549,10 +552,9 @@ function RelatedResourceCard({ r, content }: { r: Resource, content?: string })
if (articleElement) { if (articleElement) {
observer.observe(articleElement); observer.observe(articleElement);
} }
}, []) }, []);
const imgHeight = const imgHeight = r.image && r.image.width > r.image.height ? 320 : 420;
r.image && r.image.width > r.image.height ? 320 : 420;
let imgWidth = r.image let imgWidth = r.image
? (r.image.width / r.image.height) * imgHeight ? (r.image.width / r.image.height) * imgHeight
: undefined; : undefined;
@@ -561,7 +563,7 @@ function RelatedResourceCard({ r, content }: { r: Resource, content?: string })
} }
if (!articleWidth) { if (!articleWidth) {
return <></> return <></>;
} }
return ( return (

View File

@@ -29,6 +29,15 @@ func AddNewCommentActivity(userID, commentID uint) error {
return db.Create(activity).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 { func DeleteResourceActivity(resourceID uint) error {
return db.Where("ref_id = ? AND (type = ? OR type = ?)", resourceID, model.ActivityTypeNewResource, model.ActivityTypeUpdateResource).Delete(&model.Activity{}).Error return db.Where("ref_id = ? AND (type = ? OR type = ?)", resourceID, model.ActivityTypeNewResource, model.ActivityTypeUpdateResource).Delete(&model.Activity{}).Error
} }

View File

@@ -106,6 +106,8 @@ func CreateFile(filename string, description string, resourceID uint, storageID
return nil, err return nil, err
} }
_ = AddNewFileActivity(userID, f.ID)
return f, nil return f, nil
} }
@@ -120,6 +122,17 @@ func GetFile(id string) (*model.File, error) {
return f, nil 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 { func DeleteFile(id string) error {
f := &model.File{} f := &model.File{}
if err := db.Where("uuid = ?", id).First(f).Error; err != nil { 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 { if err := tx.Delete(f).Error; err != nil {
return err return err
} }
return tx.Model(&model.User{}).Where("id = ?", f.UserID). if err := tx.
UpdateColumn("files_count", gorm.Expr("files_count - ?", 1)).Error 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 { }); err != nil {
return err return err
} }

View File

@@ -122,6 +122,9 @@ func DeleteResource(id uint) error {
} }
return err 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 { if err := tx.Model(&model.User{}).Where("id = ?", r.UserID).Update("resources_count", gorm.Expr("resources_count - ?", 1)).Error; err != nil {
return err return err
} }
@@ -540,7 +543,10 @@ func RandomResource() (model.Resource, error) {
return model.Resource{}, err return model.Resource{}, err
} }
for { 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 var resource model.Resource
if err := db. if err := db.
Preload("User"). Preload("User").

View File

@@ -13,6 +13,7 @@ const (
ActivityTypeNewResource ActivityTypeNewResource
ActivityTypeUpdateResource ActivityTypeUpdateResource
ActivityTypeNewComment ActivityTypeNewComment
ActivityTypeNewFile
) )
type Activity struct { type Activity struct {
@@ -29,4 +30,5 @@ type ActivityView struct {
User UserView `json:"user"` User UserView `json:"user"`
Comment *CommentView `json:"comment,omitempty"` Comment *CommentView `json:"comment,omitempty"`
Resource *ResourceView `json:"resource,omitempty"` Resource *ResourceView `json:"resource,omitempty"`
File *FileView `json:"file,omitempty"`
} }

View File

@@ -22,6 +22,7 @@ func GetActivityList(page int) ([]model.ActivityView, int, error) {
} }
var comment *model.CommentView var comment *model.CommentView
var resource *model.ResourceView var resource *model.ResourceView
var file *model.FileView
switch activity.Type { switch activity.Type {
case model.ActivityTypeNewComment: case model.ActivityTypeNewComment:
c, err := dao.GetCommentByID(activity.RefID) c, err := dao.GetCommentByID(activity.RefID)
@@ -37,6 +38,19 @@ func GetActivityList(page int) ([]model.ActivityView, int, error) {
} }
rv := r.ToView() rv := r.ToView()
resource = &rv 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{ view := model.ActivityView{
ID: activity.ID, ID: activity.ID,
@@ -45,6 +59,7 @@ func GetActivityList(page int) ([]model.ActivityView, int, error) {
Time: activity.CreatedAt, Time: activity.CreatedAt,
Comment: comment, Comment: comment,
Resource: resource, Resource: resource,
File: file,
} }
views = append(views, view) views = append(views, view)
} }

View File

@@ -176,6 +176,13 @@ func DeleteResource(uid, id uint) error {
return model.NewUnAuthorizedError("You have not permission to delete this resource") 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 { if err := dao.DeleteResource(id); err != nil {
return err return err
} }