feat: update shortcut redirector

prod
Steven 2 years ago
parent 6b3ff5e462
commit 700598d1a5

@ -1,18 +0,0 @@
package api
type ShortcutOrganizer struct {
ShortcutID int
UserID int
Pinned bool
}
type ShortcutOrganizerFind struct {
ShortcutID int
UserID int
}
type ShortcutOrganizerUpsert struct {
ShortcutID int
UserID int
Pinned bool `json:"pinned"`
}

@ -80,7 +80,7 @@ func aclMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc {
}
}
if common.HasPrefixes(path, "/api/ping", "/api/status") && c.Request().Method == http.MethodGet {
if common.HasPrefixes(path, "/api/ping", "/api/status", "/api/workspace") && c.Request().Method == http.MethodGet {
return next(c)
}

@ -26,6 +26,17 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
}
existingShortcut, err := s.Store.FindShortcut(ctx, &api.ShortcutFind{
Name: &shortcutCreate.Name,
WorkspaceID: &shortcutCreate.WorkspaceID,
})
if err != nil && common.ErrorCode(err) != common.NotFound {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
}
if existingShortcut != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Shortcut with name %s already exists", shortcutCreate.Name))
}
shortcut, err := s.Store.CreateShortcut(ctx, shortcutCreate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
@ -48,6 +59,29 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
shortcut, err := s.Store.FindShortcut(ctx, &api.ShortcutFind{
ID: &shortcutID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
}
workspaceUser, err := s.Store.FindWordspaceUser(ctx, &api.WorkspaceUserFind{
UserID: &userID,
WorkspaceID: &shortcut.WorkspaceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find workspace user").SetInternal(err)
}
if shortcut.CreatorID != userID && workspaceUser.Role != api.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "Forbidden to patch shortcut")
}
shortcutPatch := &api.ShortcutPatch{
ID: shortcutID,
@ -56,7 +90,7 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
}
shortcut, err := s.Store.PatchShortcut(ctx, shortcutPatch)
shortcut, err = s.Store.PatchShortcut(ctx, shortcutPatch)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
}

@ -70,7 +70,7 @@ CREATE TABLE shortcut (
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
workspace_id INTEGER NOT NULL,
name TEXT NOT NULL,
link TEXT NOT NULL DEFAULT '',
link TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE'
);
@ -78,12 +78,4 @@ CREATE TABLE shortcut (
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('shortcut', 1000);
-- shortcut_organizer
CREATE TABLE shortcut_organizer (
shortcut_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
UNIQUE(shortcut_id, user_id)
);
('shortcut', 1000);

@ -70,7 +70,7 @@ CREATE TABLE shortcut (
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
workspace_id INTEGER NOT NULL,
name TEXT NOT NULL,
link TEXT NOT NULL DEFAULT '',
link TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE'
);
@ -78,12 +78,4 @@ CREATE TABLE shortcut (
INSERT INTO
sqlite_sequence (name, seq)
VALUES
('shortcut', 1000);
-- shortcut_organizer
CREATE TABLE shortcut_organizer (
shortcut_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
UNIQUE(shortcut_id, user_id)
);
('shortcut', 1000);

@ -1,6 +1,3 @@
DELETE FROM
shortcut_organizer;
DELETE FROM
shortcut;

@ -1,11 +1,14 @@
import { Tooltip } from "@mui/joy";
import copy from "copy-to-clipboard";
import { useState } from "react";
import { UNKNOWN_ID } from "../helpers/consts";
import { shortcutService, workspaceService } from "../services";
import { useAppSelector } from "../store";
import { UNKNOWN_ID } from "../helpers/consts";
import { unknownWorkspace, unknownWorkspaceUser } from "../store/modules/workspace";
import { absolutifyLink } from "../helpers/utils";
import { showCommonDialog } from "./Alert";
import Icon from "./Icon";
import toastHelper from "./Toast";
import Dropdown from "./common/Dropdown";
import CreateShortcutDialog from "./CreateShortcutDialog";
@ -20,14 +23,22 @@ interface State {
const ShortcutListView: React.FC<Props> = (props: Props) => {
const { workspaceId, shortcutList } = props;
const { user } = useAppSelector((state) => state.user);
const user = useAppSelector((state) => state.user.user as User);
const { workspaceList } = useAppSelector((state) => state.workspace);
const [state, setState] = useState<State>({
currentEditingShortcutId: UNKNOWN_ID,
});
const workspace = workspaceList.find((workspace) => workspace.id === workspaceId) ?? unknownWorkspace;
const workspaceUser = workspace.workspaceUserList.find((workspaceUser) => workspaceUser.userId === user.id) ?? unknownWorkspaceUser;
const havePermission = (shortcut: Shortcut) => {
return workspaceUser.role === "ADMIN" || shortcut.creatorId === user.id;
};
const handleCopyButtonClick = (shortcut: Shortcut) => {
const workspace = workspaceService.getWorkspaceById(workspaceId);
copy(`${location.host}/${workspace?.name}/go/${shortcut.name}`);
copy(absolutifyLink(`/${workspace?.name}/${shortcut.name}`));
toastHelper.error("Shortcut link copied to clipboard.");
};
const handleEditShortcutButtonClick = (shortcut: Shortcut) => {
@ -79,14 +90,14 @@ const ShortcutListView: React.FC<Props> = (props: Props) => {
actions={
<>
<button
disabled={shortcut.creatorId !== user?.id}
disabled={!havePermission(shortcut)}
className="w-full px-3 text-left leading-10 cursor-pointer rounded hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
onClick={() => handleEditShortcutButtonClick(shortcut)}
>
Edit
</button>
<button
disabled={shortcut.creatorId !== user?.id}
disabled={!havePermission(shortcut)}
className="w-full px-3 text-left leading-10 cursor-pointer rounded text-red-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
onClick={() => {
handleDeleteShortcutButtonClick(shortcut);

@ -4,49 +4,8 @@ export const isNullorUndefined = (value: any) => {
return isNull(value) || isUndefined(value);
};
export function getNowTimeStamp(): number {
return Date.now();
}
export function getOSVersion(): "Windows" | "MacOS" | "Linux" | "Unknown" {
const appVersion = navigator.userAgent;
let detectedOS: "Windows" | "MacOS" | "Linux" | "Unknown" = "Unknown";
if (appVersion.indexOf("Win") != -1) {
detectedOS = "Windows";
} else if (appVersion.indexOf("Mac") != -1) {
detectedOS = "MacOS";
} else if (appVersion.indexOf("Linux") != -1) {
detectedOS = "Linux";
}
return detectedOS;
}
export function debounce(fn: FunctionType, delay: number) {
let timer: number | null = null;
return () => {
if (timer) {
clearTimeout(timer);
timer = setTimeout(fn, delay);
} else {
timer = setTimeout(fn, delay);
}
};
}
export function throttle(fn: FunctionType, delay: number) {
let valid = true;
return () => {
if (!valid) {
return false;
}
valid = false;
setTimeout(() => {
fn();
valid = true;
}, delay);
};
export function absolutifyLink(rel: string): string {
const anchor = document.createElement("a");
anchor.setAttribute("href", rel);
return anchor.href;
}

@ -1,37 +1,35 @@
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import Header from "../components/Header";
import { getShortcutWithNameAndWorkspaceName } from "../helpers/api";
import useLoading from "../hooks/useLoading";
import { userService } from "../services";
interface State {
errMessage?: string;
}
const ShortcutRedirector: React.FC = () => {
const navigate = useNavigate();
const params = useParams();
const [state, setState] = useState<State>();
const loadingState = useLoading();
useEffect(() => {
if (!userService.getState().user) {
navigate("/user/auth");
return;
}
const workspaceName = params.workspaceName || "";
const shortcutName = params.shortcutName || "";
getShortcutWithNameAndWorkspaceName(workspaceName, shortcutName)
.then(({ data: { data: shortcut } }) => {
if (shortcut) {
window.location.href = shortcut.link;
} else {
setState({
errMessage: "Not found",
});
loadingState.setFinish();
}
})
.catch((err) => {
.catch((error) => {
setState({
errMessage: err?.message || "Not found",
errMessage: error.response.data.error || "Error occurred",
});
loadingState.setFinish();
});

@ -62,21 +62,8 @@ const router = createBrowserRouter([
},
},
{
path: "/:workspaceName/go/:shortcutName",
path: "/:workspaceName/:shortcutName",
element: <ShortcutRedirector />,
loader: async () => {
try {
await userService.initialState();
await workspaceService.fetchWorkspaceList();
} catch (error) {
// do nth
}
const { user } = userService.getState();
if (isNullorUndefined(user)) {
return redirect("/user/auth");
}
},
},
]);

Loading…
Cancel
Save