From 5a79304153db8978d7ee970275533ce47adac667 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 14 Sep 2022 22:49:45 +0800 Subject: [PATCH] chore: get shortcut by name and workspace name --- server/embed_frontend.go | 8 +++ server/workspace.go | 56 +++++++++++++++++++ web/package.json | 1 + web/src/App.tsx | 2 + web/src/components/ShortcutListView.tsx | 71 +++++++++++++++---------- web/src/helpers/api.ts | 4 ++ web/src/helpers/utils.ts | 12 ----- web/src/pages/ShortcutRedirector.tsx | 43 +++++++++++++++ web/yarn.lock | 12 +++++ 9 files changed, 170 insertions(+), 39 deletions(-) create mode 100644 web/src/pages/ShortcutRedirector.tsx diff --git a/server/embed_frontend.go b/server/embed_frontend.go index fa7ed59..f934bac 100644 --- a/server/embed_frontend.go +++ b/server/embed_frontend.go @@ -5,6 +5,7 @@ import ( "io/fs" "net/http" + "github.com/boojack/corgi/common" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) @@ -21,10 +22,16 @@ func getFileSystem(path string) http.FileSystem { return http.FS(fs) } +func skipper(c echo.Context) bool { + path := c.Path() + return common.HasPrefixes(path, "/api") +} + func embedFrontend(e *echo.Echo) { // Use echo static middleware to serve the built dist folder // refer: https://github.com/labstack/echo/blob/master/middleware/static.go e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ + Skipper: skipper, HTML5: true, Filesystem: getFileSystem("dist"), })) @@ -37,6 +44,7 @@ func embedFrontend(e *echo.Echo) { } }) g.Use(middleware.StaticWithConfig(middleware.StaticConfig{ + Skipper: skipper, HTML5: true, Filesystem: getFileSystem("dist/assets"), })) diff --git a/server/workspace.go b/server/workspace.go index b905491..48be091 100644 --- a/server/workspace.go +++ b/server/workspace.go @@ -90,12 +90,68 @@ func (s *Server) registerWorkspaceRoutes(g *echo.Group) { return nil }) + g.GET("/workspace/:workspaceName/shortcut/:shortcutName", func(c echo.Context) error { + ctx := c.Request().Context() + userID, ok := c.Get(getUserIDContextKey()).(int) + if !ok { + return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") + } + + workspaceName := c.Param("workspaceName") + shortcutName := c.Param("shortcutName") + if workspaceName == "" || shortcutName == "" { + return echo.NewHTTPError(http.StatusBadRequest, "Missing workspace name or shortcut name") + } + + workspace, err := s.Store.FindWorkspace(ctx, &api.WorkspaceFind{ + Name: &workspaceName, + }) + if err != nil { + if common.ErrorCode(err) == common.NotFound { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("workspace not found by name %s", workspaceName)) + } + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to find workspace with name %s", workspaceName)).SetInternal(err) + } + + workspaceUser, err := s.Store.FindWordspaceUser(ctx, &api.WorkspaceUserFind{ + WorkspaceID: &workspace.ID, + UserID: &userID, + }) + if err != nil { + if common.ErrorCode(err) == common.NotFound { + return echo.NewHTTPError(http.StatusNotFound, "workspace user not found") + } + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err) + } + if workspaceUser == nil { + return echo.NewHTTPError(http.StatusUnauthorized, "not workspace user") + } + + shortcut, err := s.Store.FindShortcut(ctx, &api.ShortcutFind{ + WorkspaceID: &workspace.ID, + Name: &shortcutName, + }) + if err != nil { + if common.ErrorCode(err) == common.NotFound { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("shortcut not found by name %s", shortcutName)) + } + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to find shortcut with name %s", shortcutName)).SetInternal(err) + } + + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(shortcut)); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode shortcut response").SetInternal(err) + } + return nil + }) + g.PATCH("/workspace/:id", func(c echo.Context) error { ctx := c.Request().Context() userID, ok := c.Get(getUserIDContextKey()).(int) if !ok { return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") } + workspaceID, err := strconv.Atoi(c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err) diff --git a/web/package.json b/web/package.json index 0982b2c..c3970bf 100644 --- a/web/package.json +++ b/web/package.json @@ -10,6 +10,7 @@ "dependencies": { "@reduxjs/toolkit": "^1.8.1", "axios": "^0.27.2", + "copy-to-clipboard": "^3.3.2", "dayjs": "^1.11.3", "lodash-es": "^4.17.21", "qs": "^6.11.0", diff --git a/web/src/App.tsx b/web/src/App.tsx index b451353..8f5fe84 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -7,6 +7,7 @@ import Auth from "./pages/Auth"; import Home from "./pages/Home"; import WorkspaceDetail from "./pages/WorkspaceDetail"; import UserDetail from "./pages/UserDetail"; +import ShortcutRedirector from "./pages/ShortcutRedirector"; function App() { const navigate = useNavigate(); @@ -33,6 +34,7 @@ function App() { } /> } /> } /> + } /> ); diff --git a/web/src/components/ShortcutListView.tsx b/web/src/components/ShortcutListView.tsx index f620b94..620a79f 100644 --- a/web/src/components/ShortcutListView.tsx +++ b/web/src/components/ShortcutListView.tsx @@ -1,4 +1,5 @@ -import { shortcutService } from "../services"; +import copy from "copy-to-clipboard"; +import { shortcutService, workspaceService } from "../services"; import Dropdown from "./common/Dropdown"; import showCreateShortcutDialog from "./CreateShortcutDialog"; import Icon from "./Icon"; @@ -11,6 +12,11 @@ interface Props { const ShortcutListView: React.FC = (props: Props) => { const { workspaceId, shortcutList } = props; + const handleCopyButtonClick = (shortcut: Shortcut) => { + const workspace = workspaceService.getWorkspaceById(workspaceId); + copy(`${location.host}/${workspace?.name}/go/${shortcut.name}`); + }; + const handleDeleteShortcutButtonClick = (shortcut: Shortcut) => { shortcutService.deleteShortcutById(shortcut.id); }; @@ -20,33 +26,44 @@ const ShortcutListView: React.FC = (props: Props) => { {shortcutList.map((shortcut) => { return (
-
- {shortcut.name} - - {shortcut.link} - +
+ {shortcut.name} + {shortcut.description} +
+
+ { + handleCopyButtonClick(shortcut); + }} + > + + + + + + + showCreateShortcutDialog(workspaceId, shortcut.id)} + > + Edit + + { + handleDeleteShortcutButtonClick(shortcut); + }} + > + Delete + + + } + actionsClassName="!w-24" + >
- - showCreateShortcutDialog(workspaceId, shortcut.id)} - > - Edit - - { - handleDeleteShortcutButtonClick(shortcut); - }} - > - Delete - - - } - actionsClassName="!w-24" - >
); })} diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index b58c35b..1ad9484 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -87,6 +87,10 @@ export function getShortcutList(shortcutFind?: ShortcutFind) { return axios.get>(`/api/shortcut?${queryList.join("&")}`); } +export function getShortcutWithNameAndWorkspaceName(workspaceName: string, shortcutName: string) { + return axios.get>(`/api/workspace/${workspaceName}/shortcut/${shortcutName}`); +} + export function createShortcut(shortcutCreate: ShortcutCreate) { return axios.post>("/api/shortcut", shortcutCreate); } diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts index 25a4920..2752928 100644 --- a/web/src/helpers/utils.ts +++ b/web/src/helpers/utils.ts @@ -44,15 +44,3 @@ export function throttle(fn: FunctionType, delay: number) { }, delay); }; } - -export async function copyTextToClipboard(text: string) { - if (navigator.clipboard && navigator.clipboard.writeText) { - try { - await navigator.clipboard.writeText(text); - } catch (error: unknown) { - console.warn("Copy to clipboard failed.", error); - } - } else { - console.warn("Copy to clipboard failed, methods not supports."); - } -} diff --git a/web/src/pages/ShortcutRedirector.tsx b/web/src/pages/ShortcutRedirector.tsx new file mode 100644 index 0000000..c068781 --- /dev/null +++ b/web/src/pages/ShortcutRedirector.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import Header from "../components/Header"; +import { getShortcutWithNameAndWorkspaceName } from "../helpers/api"; +import useLoading from "../hooks/useLoading"; + +interface State { + errMessage?: string; +} + +const ShortcutRedirector: React.FC = () => { + const params = useParams(); + const [state, setState] = useState(); + const loadingState = useLoading(); + + useEffect(() => { + const workspaceName = params.workspaceName || ""; + const shortcutName = params.shortcutName || ""; + getShortcutWithNameAndWorkspaceName(workspaceName, shortcutName) + .then(({ data: { data: shortcut } }) => { + if (shortcut) { + window.location.href = shortcut.link; + } + }) + .catch((err) => { + setState({ + errMessage: err?.message || "Not found", + }); + loadingState.setFinish(); + }); + }, []); + + return loadingState.isLoading ? null : ( +
+
+
+

{state?.errMessage}

+
+
+ ); +}; + +export default ShortcutRedirector; diff --git a/web/yarn.lock b/web/yarn.lock index 631ecc4..07ded7d 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -809,6 +809,13 @@ copy-anything@^2.0.1: dependencies: is-what "^3.14.1" +copy-to-clipboard@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz#5b263ec2366224b100181dded7ce0579b340c107" + integrity sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg== + dependencies: + toggle-selection "^1.0.6" + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2474,6 +2481,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"