Add collelction permission.

This commit is contained in:
2025-08-01 17:09:36 +08:00
parent 25720f5e49
commit d35473c905
11 changed files with 190 additions and 68 deletions

View File

@@ -236,7 +236,8 @@ export const i18nData = {
"Collection created successfully": "合集创建成功", "Collection created successfully": "合集创建成功",
"Collection deleted successfully": "合集删除成功", "Collection deleted successfully": "合集删除成功",
"Remove Resource": "移除资源", "Remove Resource": "移除资源",
"Are you sure you want to remove this resource?": "您确定要移除此资源吗?", "Are you sure you want to remove this resource?":
"您确定要移除此资源吗?",
"Resource deleted successfully": "资源移除成功", "Resource deleted successfully": "资源移除成功",
"Edit Collection": "编辑合集", "Edit Collection": "编辑合集",
"Edit successful": "编辑成功", "Edit successful": "编辑成功",
@@ -250,7 +251,9 @@ export const i18nData = {
"Update File Info": "更新文件信息", "Update File Info": "更新文件信息",
"File info updated successfully": "文件信息更新成功", "File info updated successfully": "文件信息更新成功",
"File URL": "文件URL", "File URL": "文件URL",
"You do not have permission to upload files, please contact the administrator.": "您没有上传文件的权限,请联系管理员。", "You do not have permission to upload files, please contact the administrator.":
"您没有上传文件的权限,请联系管理员。",
"Private": "私有",
}, },
}, },
"zh-TW": { "zh-TW": {
@@ -490,7 +493,8 @@ export const i18nData = {
"Collection created successfully": "合集創建成功", "Collection created successfully": "合集創建成功",
"Collection deleted successfully": "合集刪除成功", "Collection deleted successfully": "合集刪除成功",
"Remove Resource": "移除資源", "Remove Resource": "移除資源",
"Are you sure you want to remove this resource?": "您確定要移除此資源嗎?", "Are you sure you want to remove this resource?":
"您確定要移除此資源嗎?",
"Resource deleted successfully": "資源移除成功", "Resource deleted successfully": "資源移除成功",
"Edit Collection": "編輯合集", "Edit Collection": "編輯合集",
"Edit successful": "編輯成功", "Edit successful": "編輯成功",
@@ -504,7 +508,9 @@ export const i18nData = {
"Update File Info": "更新檔案信息", "Update File Info": "更新檔案信息",
"File info updated successfully": "檔案信息更新成功", "File info updated successfully": "檔案信息更新成功",
"File URL": "檔案URL", "File URL": "檔案URL",
"You do not have permission to upload files, please contact the administrator.": "您沒有上傳檔案的權限,請聯繫管理員。", "You do not have permission to upload files, please contact the administrator.":
"您沒有上傳檔案的權限,請聯繫管理員。",
"Private": "私有",
}, },
}, },
}; };

View File

@@ -199,4 +199,5 @@ export interface Collection {
user: User; user: User;
resources_count: number; resources_count: number;
images: Image[]; images: Image[];
isPublic: boolean;
} }

View File

@@ -693,11 +693,13 @@ class Network {
async createCollection( async createCollection(
title: string, title: string,
article: string, article: string,
isPublic: boolean,
): Promise<Response<Collection>> { ): Promise<Response<Collection>> {
return this._callApi(() => return this._callApi(() =>
axios.postForm(`${this.apiBaseUrl}/collection/create`, { axios.postForm(`${this.apiBaseUrl}/collection/create`, {
title, title,
article, article,
public: isPublic,
}), }),
); );
} }
@@ -706,12 +708,14 @@ class Network {
id: number, id: number,
title: string, title: string,
article: string, article: string,
isPublic: boolean,
): Promise<Response<any>> { ): Promise<Response<any>> {
return this._callApi(() => return this._callApi(() =>
axios.postForm(`${this.apiBaseUrl}/collection/update`, { axios.postForm(`${this.apiBaseUrl}/collection/update`, {
id, id,
title, title,
article, article,
public: isPublic,
}), }),
); );
} }

View File

@@ -6,10 +6,11 @@ import { Collection } from "../network/models";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import ResourcesView from "../components/resources_view"; import ResourcesView from "../components/resources_view";
import Loading from "../components/loading"; import Loading from "../components/loading";
import { MdOutlineDelete, MdOutlineEdit } from "react-icons/md"; import { MdOutlineAdd, MdOutlineDelete, MdOutlineEdit } from "react-icons/md";
import { app } from "../app"; import { app } from "../app";
import { useTranslation } from "../utils/i18n"; import { useTranslation } from "../utils/i18n";
import Button from "../components/button"; import Button from "../components/button";
import Badge from "../components/badge";
export default function CollectionPage() { export default function CollectionPage() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@@ -54,7 +55,7 @@ export default function CollectionPage() {
useEffect(() => { useEffect(() => {
if (!collection) return; if (!collection) return;
document.title = collection.title; document.title = collection.title;
}, [collection]) }, [collection]);
const toBeDeletedRID = useRef<number | null>(null); const toBeDeletedRID = useRef<number | null>(null);
@@ -160,6 +161,12 @@ export default function CollectionPage() {
</button> </button>
</> </>
)} )}
<span className="flex-1" />
{!collection.isPublic && (
<Badge className="badge-soft badge-error text-xs mr-2">
<MdOutlineAdd size={16} className="inline-block" /> {t("Private")}
</Badge>
)}
</div> </div>
</div> </div>
<ResourcesView <ResourcesView
@@ -275,6 +282,7 @@ function EditCollectionDialog({
}) { }) {
const [editTitle, setEditTitle] = useState(collection.title); const [editTitle, setEditTitle] = useState(collection.title);
const [editArticle, setEditArticle] = useState(collection.article); const [editArticle, setEditArticle] = useState(collection.article);
const [editIsPublic, setEditIsPublic] = useState(collection.isPublic);
const [editLoading, setEditLoading] = useState(false); const [editLoading, setEditLoading] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -292,6 +300,7 @@ function EditCollectionDialog({
collection.id, collection.id,
editTitle, editTitle,
editArticle, editArticle,
editIsPublic,
); );
setEditLoading(false); setEditLoading(false);
if (res.success) { if (res.success) {
@@ -300,7 +309,12 @@ function EditCollectionDialog({
if (getRes.success) { if (getRes.success) {
onSaved(getRes.data!); onSaved(getRes.data!);
} else { } else {
onSaved({ ...collection, title: editTitle, article: editArticle }); onSaved({
...collection,
title: editTitle,
article: editArticle,
isPublic: editIsPublic,
});
} }
} else { } else {
showToast({ showToast({
@@ -325,11 +339,21 @@ function EditCollectionDialog({
/> />
<label className="block mb-1">{t("Description")}</label> <label className="block mb-1">{t("Description")}</label>
<textarea <textarea
className="textarea w-full min-h-32" className="textarea w-full min-h-32 mb-2"
value={editArticle} value={editArticle}
onChange={(e) => setEditArticle(e.target.value)} onChange={(e) => setEditArticle(e.target.value)}
disabled={editLoading} disabled={editLoading}
/> />
<label className="flex items-center mb-4">
<input
type="checkbox"
checked={editIsPublic}
onChange={(e) => setEditIsPublic(e.target.checked)}
className="checkbox mr-2"
disabled={editLoading}
/>
{t("Public")}
</label>
<div className="modal-action"> <div className="modal-action">
<button className="btn" onClick={onClose} disabled={editLoading}> <button className="btn" onClick={onClose} disabled={editLoading}>
{t("Cancel")} {t("Cancel")}

View File

@@ -8,6 +8,7 @@ import { useNavigate } from "react-router";
export default function CreateCollectionPage() { export default function CreateCollectionPage() {
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [article, setArticle] = useState<string>(""); const [article, setArticle] = useState<string>("");
const [isPublic, setIsPublic] = useState<boolean>(true);
const [isLoading, setLoading] = useState(false); const [isLoading, setLoading] = useState(false);
const [isUploadingimage, setUploadingImage] = useState(false); const [isUploadingimage, setUploadingImage] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -73,7 +74,7 @@ export default function CreateCollectionPage() {
return; return;
} }
setLoading(true); setLoading(true);
const res = await network.createCollection(title, article); const res = await network.createCollection(title, article, isPublic);
if (res.success) { if (res.success) {
showToast({ showToast({
message: t("Collection created successfully"), message: t("Collection created successfully"),
@@ -110,6 +111,17 @@ export default function CreateCollectionPage() {
onChange={(e) => setArticle(e.target.value)} onChange={(e) => setArticle(e.target.value)}
className="textarea mt-1 w-full min-h-80" className="textarea mt-1 w-full min-h-80"
/> />
<div className="mt-4">
<label className="flex items-center">
<input
type="checkbox"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
className="checkbox mr-2"
/>
{t("Public")}
</label>
</div>
</div> </div>
<div className={"flex items-center mt-4"}> <div className={"flex items-center mt-4"}>
<button <button

View File

@@ -218,24 +218,25 @@ export default function ResourcePage() {
</button> </button>
<Tags tags={resource.tags} /> <Tags tags={resource.tags} />
<p className={"px-3 mt-2"}> <p className={"px-3 mt-2"}>
{resource.links && resource.links.map((l) => { {resource.links &&
return ( resource.links.map((l) => {
<a href={l.url} target={"_blank"}> return (
<span <a href={l.url} target={"_blank"}>
className={ <span
"py-1 px-3 inline-flex items-center m-1 border border-base-300 bg-base-100 opacity-90 rounded-2xl hover:bg-base-200 transition-colors cursor-pointer select-none" className={
} "py-1 px-3 inline-flex items-center m-1 border border-base-300 bg-base-100 opacity-90 rounded-2xl hover:bg-base-200 transition-colors cursor-pointer select-none"
> }
{l.url.includes("steampowered.com") ? ( >
<BiLogoSteam size={20} /> {l.url.includes("steampowered.com") ? (
) : ( <BiLogoSteam size={20} />
<MdOutlineLink size={20} /> ) : (
)} <MdOutlineLink size={20} />
<span className={"ml-2 text-sm"}>{l.label}</span> )}
</span> <span className={"ml-2 text-sm"}>{l.label}</span>
</a> </span>
); </a>
})} );
})}
<CollectionDialog rid={resource.id} /> <CollectionDialog rid={resource.id} />
</p> </p>
@@ -1637,7 +1638,7 @@ function CollectionDialog({ rid }: { rid: number }) {
}; };
if (!app.isLoggedIn()) { if (!app.isLoggedIn()) {
return <></> return <></>;
} }
return ( return (
@@ -1683,13 +1684,16 @@ function CollectionDialog({ rid }: { rid: number }) {
/> />
)} )}
<div className="modal-action"> <div className="modal-action">
<Button className="btn-ghost" onClick={() => { <Button
const dialog = document.getElementById( className="btn-ghost"
"collection_dialog", onClick={() => {
) as HTMLDialogElement; const dialog = document.getElementById(
dialog.close(); "collection_dialog",
navigate("/create-collection"); ) as HTMLDialogElement;
}}> dialog.close();
navigate("/create-collection");
}}
>
<div className="flex items-center"> <div className="flex items-center">
<MdOutlineAdd size={20} className={"inline-block mr-1"} /> <MdOutlineAdd size={20} className={"inline-block mr-1"} />
{t("Create")} {t("Create")}

View File

@@ -413,15 +413,17 @@ function Collections({ username }: { username?: string }) {
onChange={(e) => delayedSetSearchKeyword(e.target.value)} onChange={(e) => delayedSetSearchKeyword(e.target.value)}
/> />
<span className="flex-1" /> <span className="flex-1" />
{username == app.user?.username && <button {username == app.user?.username && (
className="btn btn-primary btn-soft" <button
onClick={() => { className="btn btn-primary btn-soft"
navigate("/create-collection"); onClick={() => {
}} navigate("/create-collection");
> }}
<MdOutlineAdd size={20} className="inline-block mr-1" /> >
{t("Create")} <MdOutlineAdd size={20} className="inline-block mr-1" />
</button>} {t("Create")}
</button>
)}
</div> </div>
<CollectionsList <CollectionsList
username={username} username={username}
@@ -522,6 +524,12 @@ function CollectionCard({ collection }: { collection: Collection }) {
<MdOutlinePhotoAlbum size={16} className="inline-block" /> <MdOutlinePhotoAlbum size={16} className="inline-block" />
{collection.resources_count} {t("Resources")} {collection.resources_count} {t("Resources")}
</Badge> </Badge>
<span className="flex-1" />
{!collection.isPublic && (
<Badge className="badge-soft badge-error text-xs mr-2">
<MdOutlineAdd size={16} className="inline-block" /> {t("Private")}
</Badge>
)}
</div> </div>
</div> </div>
); );

View File

@@ -15,11 +15,16 @@ func handleCreateCollection(c fiber.Ctx) error {
} }
title := c.FormValue("title") title := c.FormValue("title")
article := c.FormValue("article") article := c.FormValue("article")
publicStr := c.FormValue("public")
public := false
if publicStr == "true" || publicStr == "1" {
public = true
}
if title == "" || article == "" { if title == "" || article == "" {
return model.NewRequestError("Title and article are required") return model.NewRequestError("Title and article are required")
} }
host := c.Hostname() host := c.Hostname()
col, err := service.CreateCollection(uid, title, article, host) col, err := service.CreateCollection(uid, title, article, host, public)
if err != nil { if err != nil {
return err return err
} }
@@ -38,6 +43,11 @@ func handleUpdateCollection(c fiber.Ctx) error {
idStr := c.FormValue("id") idStr := c.FormValue("id")
title := c.FormValue("title") title := c.FormValue("title")
article := c.FormValue("article") article := c.FormValue("article")
publicStr := c.FormValue("public")
public := false
if publicStr == "true" || publicStr == "1" {
public = true
}
if idStr == "" || title == "" || article == "" { if idStr == "" || title == "" || article == "" {
return model.NewRequestError("ID, title and article are required") return model.NewRequestError("ID, title and article are required")
} }
@@ -46,7 +56,7 @@ func handleUpdateCollection(c fiber.Ctx) error {
return model.NewRequestError("Invalid collection ID") return model.NewRequestError("Invalid collection ID")
} }
host := c.Hostname() host := c.Hostname()
if err := service.UpdateCollection(uid, uint(id), title, article, host); err != nil { if err := service.UpdateCollection(uid, uint(id), title, article, host, public); err != nil {
return err return err
} }
return c.Status(fiber.StatusOK).JSON(model.Response[any]{ return c.Status(fiber.StatusOK).JSON(model.Response[any]{
@@ -83,7 +93,11 @@ func handleGetCollection(c fiber.Ctx) error {
if err != nil { if err != nil {
return model.NewRequestError("Invalid collection ID") return model.NewRequestError("Invalid collection ID")
} }
col, err := service.GetCollectionByID(uint(id))
// Get viewer UID (0 if not authenticated)
viewerUID, _ := c.Locals("uid").(uint)
col, err := service.GetCollectionByID(uint(id), viewerUID)
if err != nil { if err != nil {
return err return err
} }
@@ -104,7 +118,11 @@ func handleListUserCollections(c fiber.Ctx) error {
if username == "" { if username == "" {
return model.NewRequestError("Username is required") return model.NewRequestError("Username is required")
} }
cols, total, err := service.ListUserCollections(username, page)
// Get viewer UID (0 if not authenticated)
viewerUID, _ := c.Locals("uid").(uint)
cols, total, err := service.ListUserCollections(username, page, viewerUID)
if err != nil { if err != nil {
return err return err
} }
@@ -127,7 +145,11 @@ func handleListCollectionResources(c fiber.Ctx) error {
if err != nil || page < 1 { if err != nil || page < 1 {
page = 1 page = 1
} }
res, total, err := service.ListCollectionResources(uint(id), page)
// Get viewer UID (0 if not authenticated)
viewerUID, _ := c.Locals("uid").(uint)
res, total, err := service.ListCollectionResources(uint(id), page, viewerUID)
if err != nil { if err != nil {
return err return err
} }
@@ -209,7 +231,11 @@ func handleSearchUserCollections(c fiber.Ctx) error {
excludedRID = uint(rid) excludedRID = uint(rid)
} }
} }
cols, err := service.SearchUserCollections(username, keyword, excludedRID)
// Get viewer UID (0 if not authenticated)
viewerUID, _ := c.Locals("uid").(uint)
cols, err := service.SearchUserCollections(username, keyword, excludedRID, viewerUID)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -6,13 +6,14 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
func CreateCollection(uid uint, title string, article string, images []uint) (model.Collection, error) { func CreateCollection(uid uint, title string, article string, images []uint, public bool) (model.Collection, error) {
var collection model.Collection var collection model.Collection
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
collection = model.Collection{ collection = model.Collection{
UserID: uid, UserID: uid,
Title: title, Title: title,
Article: article, Article: article,
Public: public, // 新增
} }
if err := tx.Create(&collection).Error; err != nil { if err := tx.Create(&collection).Error; err != nil {
@@ -32,7 +33,7 @@ func CreateCollection(uid uint, title string, article string, images []uint) (mo
return collection, nil return collection, nil
} }
func UpdateCollection(id uint, title string, article string, images []uint) error { func UpdateCollection(id uint, title string, article string, images []uint, public bool) error {
return db.Transaction(func(tx *gorm.DB) error { return db.Transaction(func(tx *gorm.DB) error {
collection := &model.Collection{ collection := &model.Collection{
Model: gorm.Model{ Model: gorm.Model{
@@ -40,6 +41,7 @@ func UpdateCollection(id uint, title string, article string, images []uint) erro
}, },
Title: title, Title: title,
Article: article, Article: article,
Public: public, // 新增
} }
if err := tx.Model(collection).Updates(collection).Error; err != nil { if err := tx.Model(collection).Updates(collection).Error; err != nil {
@@ -142,19 +144,22 @@ func GetCollectionByID(id uint) (*model.Collection, error) {
return collection, nil return collection, nil
} }
func ListUserCollections(uid uint, page int, pageSize int) ([]*model.Collection, int64, error) { func ListUserCollections(uid uint, page int, pageSize int, showPrivate bool) ([]*model.Collection, int64, error) {
var collections []*model.Collection var collections []*model.Collection
var total int64 var total int64
if err := db.Model(&model.Collection{}).Where("user_id = ?", uid).Count(&total).Error; err != nil { query := db.Model(&model.Collection{}).Where("user_id = ?", uid)
if !showPrivate {
query = query.Where("public = ?", true)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err return nil, 0, err
} }
if err := db. if err := query.
Model(&model.Collection{}).
Preload("Images"). Preload("Images").
Preload("Resources"). Preload("Resources").
Where("user_id = ?", uid).
Offset((page - 1) * pageSize). Offset((page - 1) * pageSize).
Limit(pageSize). Limit(pageSize).
Find(&collections).Error; err != nil { Find(&collections).Error; err != nil {
@@ -196,12 +201,16 @@ func ListCollectionResources(collectionID uint, page int, pageSize int) ([]*mode
// SearchUserCollections searches for collections by user ID and keyword limited to 10 results. // SearchUserCollections searches for collections by user ID and keyword limited to 10 results.
// excludedRID: if >0, only return collections not containing this resource. // excludedRID: if >0, only return collections not containing this resource.
func SearchUserCollections(uid uint, keyword string, excludedRID uint) ([]*model.Collection, error) { func SearchUserCollections(uid uint, keyword string, excludedRID uint, showPrivate bool) ([]*model.Collection, error) {
var collections []*model.Collection var collections []*model.Collection
query := db.Model(&model.Collection{}). query := db.Model(&model.Collection{}).
Where("user_id = ?", uid) Where("user_id = ?", uid)
if !showPrivate {
query = query.Where("public = ?", true)
}
if keyword != "" { if keyword != "" {
query = query.Where("title LIKE ?", "%"+keyword+"%") query = query.Where("title LIKE ?", "%"+keyword+"%")
} }

View File

@@ -11,6 +11,7 @@ type Collection struct {
ResourcesCount int `gorm:"default:0"` ResourcesCount int `gorm:"default:0"`
Images []Image `gorm:"many2many:collection_images;"` Images []Image `gorm:"many2many:collection_images;"`
Resources []Resource `gorm:"many2many:collection_resources;"` Resources []Resource `gorm:"many2many:collection_resources;"`
Public bool `gorm:"default:false"` // 新增公开/私有字段
} }
type CollectionView struct { type CollectionView struct {
@@ -20,6 +21,7 @@ type CollectionView struct {
User UserView `json:"user"` User UserView `json:"user"`
ResourcesCount int `json:"resources_count"` ResourcesCount int `json:"resources_count"`
Images []Image `json:"images"` Images []Image `json:"images"`
IsPublic bool `json:"isPublic"` // 新增公开/私有字段
} }
func (c Collection) ToView() *CollectionView { func (c Collection) ToView() *CollectionView {
@@ -30,5 +32,6 @@ func (c Collection) ToView() *CollectionView {
User: c.User.ToView(), User: c.User.ToView(),
ResourcesCount: c.ResourcesCount, ResourcesCount: c.ResourcesCount,
Images: c.Images, Images: c.Images,
IsPublic: c.Public, // 新增
} }
} }

View File

@@ -6,11 +6,11 @@ import (
) )
// Create a new collection. // Create a new collection.
func CreateCollection(uid uint, title, article string, host string) (*model.CollectionView, error) { func CreateCollection(uid uint, title, article string, host string, public bool) (*model.CollectionView, error) {
if uid == 0 || title == "" || article == "" { if uid == 0 || title == "" || article == "" {
return nil, model.NewRequestError("invalid parameters") return nil, model.NewRequestError("invalid parameters")
} }
c, err := dao.CreateCollection(uid, title, article, findImagesInContent(article, host)) c, err := dao.CreateCollection(uid, title, article, findImagesInContent(article, host), public)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -19,7 +19,7 @@ func CreateCollection(uid uint, title, article string, host string) (*model.Coll
} }
// Update an existing collection with user validation. // Update an existing collection with user validation.
func UpdateCollection(uid, id uint, title, article string, host string) error { func UpdateCollection(uid, id uint, title, article string, host string, public bool) error {
if uid == 0 || id == 0 || title == "" || article == "" { if uid == 0 || id == 0 || title == "" || article == "" {
return model.NewRequestError("invalid parameters") return model.NewRequestError("invalid parameters")
} }
@@ -30,7 +30,7 @@ func UpdateCollection(uid, id uint, title, article string, host string) error {
if collection.UserID != uid { if collection.UserID != uid {
return model.NewUnAuthorizedError("user does not have permission to update this collection") return model.NewUnAuthorizedError("user does not have permission to update this collection")
} }
return dao.UpdateCollection(id, title, article, findImagesInContent(article, host)) return dao.UpdateCollection(id, title, article, findImagesInContent(article, host), public)
} }
// Delete a collection by ID. // Delete a collection by ID.
@@ -83,7 +83,7 @@ func RemoveResourceFromCollection(uid, collectionID, resourceID uint) error {
} }
// Get a collection by ID. // Get a collection by ID.
func GetCollectionByID(id uint) (*model.CollectionView, error) { func GetCollectionByID(id uint, viewerUID uint) (*model.CollectionView, error) {
if id == 0 { if id == 0 {
return nil, model.NewRequestError("invalid collection id") return nil, model.NewRequestError("invalid collection id")
} }
@@ -91,11 +91,17 @@ func GetCollectionByID(id uint) (*model.CollectionView, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Check if collection is private and viewer is not the owner
if !c.Public && c.UserID != viewerUID {
return nil, model.NewUnAuthorizedError("you do not have permission to view this private collection")
}
return c.ToView(), nil return c.ToView(), nil
} }
// List collections of a user with pagination. // List collections of a user with pagination.
func ListUserCollections(username string, page int) ([]*model.CollectionView, int64, error) { func ListUserCollections(username string, page int, viewerUID uint) ([]*model.CollectionView, int64, error) {
if username == "" || page < 1 { if username == "" || page < 1 {
return nil, 0, model.NewRequestError("invalid parameters") return nil, 0, model.NewRequestError("invalid parameters")
} }
@@ -104,7 +110,11 @@ func ListUserCollections(username string, page int) ([]*model.CollectionView, in
return nil, 0, err return nil, 0, err
} }
uid := user.ID uid := user.ID
collections, total, err := dao.ListUserCollections(uid, page, pageSize)
// Check if viewer can see private collections (only owner can see their private collections)
showPrivate := uid == viewerUID
collections, total, err := dao.ListUserCollections(uid, page, pageSize, showPrivate)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -116,10 +126,21 @@ func ListUserCollections(username string, page int) ([]*model.CollectionView, in
} }
// List resources in a collection with pagination. // List resources in a collection with pagination.
func ListCollectionResources(collectionID uint, page int) ([]*model.ResourceView, int64, error) { func ListCollectionResources(collectionID uint, page int, viewerUID uint) ([]*model.ResourceView, int64, error) {
if collectionID == 0 || page < 1 { if collectionID == 0 || page < 1 {
return nil, 0, model.NewRequestError("invalid parameters") return nil, 0, model.NewRequestError("invalid parameters")
} }
// Check collection privacy first
collection, err := dao.GetCollectionByID(collectionID)
if err != nil {
return nil, 0, err
}
if !collection.Public && collection.UserID != viewerUID {
return nil, 0, model.NewUnAuthorizedError("you do not have permission to view this private collection")
}
resources, total, err := dao.ListCollectionResources(collectionID, page, pageSize) resources, total, err := dao.ListCollectionResources(collectionID, page, pageSize)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -134,7 +155,7 @@ func ListCollectionResources(collectionID uint, page int) ([]*model.ResourceView
// Search user collections by keyword, limited to 10 results. // Search user collections by keyword, limited to 10 results.
// excludedRID: if >0, only return collections not containing this resource. // excludedRID: if >0, only return collections not containing this resource.
func SearchUserCollections(username string, keyword string, excludedRID uint) ([]*model.CollectionView, error) { func SearchUserCollections(username string, keyword string, excludedRID uint, viewerUID uint) ([]*model.CollectionView, error) {
if username == "" { if username == "" {
return nil, model.NewRequestError("invalid parameters") return nil, model.NewRequestError("invalid parameters")
} }
@@ -143,7 +164,11 @@ func SearchUserCollections(username string, keyword string, excludedRID uint) ([
return nil, err return nil, err
} }
uid := user.ID uid := user.ID
collections, err := dao.SearchUserCollections(uid, keyword, excludedRID)
// Check if viewer can see private collections
showPrivate := uid == viewerUID
collections, err := dao.SearchUserCollections(uid, keyword, excludedRID, showPrivate)
if err != nil { if err != nil {
return nil, err return nil, err
} }