From 99a2d328f1dccdada0c50a0f987e2266d5a946cc Mon Sep 17 00:00:00 2001 From: nyne Date: Sun, 8 Jun 2025 17:49:49 +0800 Subject: [PATCH] Add Random resource retrieval and navigation. --- frontend/src/app.tsx | 2 ++ frontend/src/components/navigator.tsx | 18 +++++++++++++ frontend/src/i18n.ts | 5 ++++ frontend/src/network/network.ts | 13 +++++++++ frontend/src/pages/random_page.tsx | 28 ++++++++++++++++++++ frontend/src/pages/resource_details_page.tsx | 26 +++++++++++------- server/api/resource.go | 16 +++++++++++ server/dao/resource.go | 25 +++++++++++++++++ server/service/resource.go | 9 +++++++ 9 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 frontend/src/pages/random_page.tsx diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 5926c54..9d6eb31 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -12,6 +12,7 @@ import UserPage from "./pages/user_page.tsx"; import EditResourcePage from "./pages/edit_resource_page.tsx"; import AboutPage from "./pages/about_page.tsx"; import TagsPage from "./pages/tags_page.tsx"; +import RandomPage from "./pages/random_page.tsx"; export default function App() { return ( @@ -30,6 +31,7 @@ export default function App() { } /> } /> } /> + } /> diff --git a/frontend/src/components/navigator.tsx b/frontend/src/components/navigator.tsx index 39510d9..360ceba 100644 --- a/frontend/src/components/navigator.tsx +++ b/frontend/src/components/navigator.tsx @@ -103,6 +103,17 @@ export default function Navigator() { {"Github"} +
  • { + const menu = document.getElementById( + "navi_menu", + ) as HTMLElement; + menu.blur(); + navigate("/random"); + }} + > + {t("Random")} +
  • { const menu = document.getElementById( @@ -143,6 +154,13 @@ export default function Navigator() { > {t("Tags")}
  • +
  • { + navigate("/random"); + }} + > + {t("Random")} +
  • { navigate("/about"); diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 9b60ea3..ab057cd 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -211,6 +211,7 @@ export const i18nData = { "Ocean Breeze": "Ocean Breeze", "Mint Leaf": "Mint Leaf", "Golden Glow": "Golden Glow", + "Random": "Random", }, }, "zh-CN": { @@ -414,6 +415,8 @@ export const i18nData = { "Ocean Breeze": "海蓝", "Mint Leaf": "薄荷", "Golden Glow": "微光", + + "Random": "随机", }, }, "zh-TW": { @@ -617,6 +620,8 @@ export const i18nData = { "Ocean Breeze": "海藍", "Mint Leaf": "薄荷", "Golden Glow": "微光", + + "Random": "隨機", }, }, }; diff --git a/frontend/src/network/network.ts b/frontend/src/network/network.ts index 3ab4111..a627c10 100644 --- a/frontend/src/network/network.ts +++ b/frontend/src/network/network.ts @@ -613,6 +613,19 @@ class Network { } } + async getRandomResource(): Promise> { + try { + const response = await axios.get(`${this.apiBaseUrl}/resource/random`); + return response.data; + } catch (e: any) { + console.error(e); + return { + success: false, + message: e.toString(), + }; + } + } + async deleteResource(id: number): Promise> { try { const response = await axios.delete(`${this.apiBaseUrl}/resource/${id}`); diff --git a/frontend/src/pages/random_page.tsx b/frontend/src/pages/random_page.tsx new file mode 100644 index 0000000..daa6281 --- /dev/null +++ b/frontend/src/pages/random_page.tsx @@ -0,0 +1,28 @@ +import Loading from "../components/loading.tsx"; +import { useNavigate } from "react-router"; +import { useEffect } from "react"; +import { network } from "../network/network.ts"; +import showToast from "../components/toast.ts"; + +export default function RandomPage() { + const navigate = useNavigate(); + + useEffect(() => { + network.getRandomResource().then((res) => { + if (res.success) { + navigate(`/resources/${res.data!.id}`, { + state: { + resource: res.data, + }, + }); + } else { + showToast({ + type: "error", + message: res.message || "Failed to fetch random resource", + }); + } + }); + }, [navigate]); + + return ; +} diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index 584ac1d..5ed1fe3 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -1,4 +1,4 @@ -import { useNavigate, useParams } from "react-router"; +import { useLocation, useNavigate, useParams } from "react-router"; import { createContext, createRef, @@ -57,6 +57,8 @@ export default function ResourcePage() { const [page, setPage] = useState(0); + const location = useLocation(); + const reload = useCallback(async () => { if (!isNaN(id)) { setResource(null); @@ -76,16 +78,20 @@ export default function ResourcePage() { useEffect(() => { setResource(null); if (!isNaN(id)) { - network.getResourceDetails(id).then((res) => { - if (res.success) { - setResource(res.data!); - document.title = res.data!.title; - } else { - showToast({ message: res.message, type: "error" }); - } - }); + if (location.state) { + setResource(location.state.resource); + } else { + network.getResourceDetails(id).then((res) => { + if (res.success) { + setResource(res.data!); + document.title = res.data!.title; + } else { + showToast({ message: res.message, type: "error" }); + } + }); + } } - }, [id]); + }, [id, location.state]); const navigate = useNavigate(); diff --git a/server/api/resource.go b/server/api/resource.go index 0c197b1..5b3e5c9 100644 --- a/server/api/resource.go +++ b/server/api/resource.go @@ -251,12 +251,28 @@ func handleUpdateResource(c fiber.Ctx) error { }) } +func handleGetRandomResource(c fiber.Ctx) error { + resource, err := service.RandomResource() + if err != nil { + return err + } + if resource == nil { + return model.NewNotFoundError("No resources found") + } + return c.Status(fiber.StatusOK).JSON(model.Response[model.ResourceDetailView]{ + Success: true, + Data: *resource, + Message: "Random resource retrieved successfully", + }) +} + func AddResourceRoutes(api fiber.Router) { resource := api.Group("/resource") { resource.Post("/", handleCreateResource) resource.Get("/search", handleSearchResources) resource.Get("/", handleListResources) + resource.Get("/random", handleGetRandomResource) resource.Get("/:id", handleGetResource) resource.Delete("/:id", handleDeleteResource) resource.Get("/tag/:tag", handleListResourcesWithTag) diff --git a/server/dao/resource.go b/server/dao/resource.go index 7cad2b8..32a6e78 100644 --- a/server/dao/resource.go +++ b/server/dao/resource.go @@ -3,6 +3,7 @@ package dao import ( "errors" "github.com/gofiber/fiber/v3/log" + "math/rand" "nysoure/server/model" "strings" "sync" @@ -510,3 +511,27 @@ func AddResourceDownloadCount(id uint) error { stats.downloads.Add(1) return nil } + +func RandomResource() (model.Resource, error) { + var maxID int64 + if err := db.Model(&model.Resource{}).Select("MAX(id)").Scan(&maxID).Error; err != nil { + return model.Resource{}, err + } + for { + randomID := uint(1 + rand.Int63n(maxID-1)) + var resource model.Resource + if err := db. + Preload("User"). + Preload("Images"). + Preload("Tags"). + Preload("Files"). + Where("id = ?", randomID). + First(&resource).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + continue // Try again if the resource does not exist + } + return model.Resource{}, err // Return error if any other issue occurs + } + return resource, nil // Return the found resource + } +} diff --git a/server/service/resource.go b/server/service/resource.go index 8e8d1ed..3d1557b 100644 --- a/server/service/resource.go +++ b/server/service/resource.go @@ -255,3 +255,12 @@ func EditResource(uid, rid uint, params *ResourceCreateParams) error { } return nil } + +func RandomResource() (*model.ResourceDetailView, error) { + r, err := dao.RandomResource() + if err != nil { + return nil, err + } + v := r.ToDetailView() + return &v, nil +}