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
+}