diff --git a/api/shortcut_organizer.go b/api/shortcut_organizer.go deleted file mode 100644 index 8ce67aa..0000000 --- a/api/shortcut_organizer.go +++ /dev/null @@ -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"` -} diff --git a/server/acl.go b/server/acl.go index 39bdd2f..4356ffb 100644 --- a/server/acl.go +++ b/server/acl.go @@ -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) } diff --git a/server/shortcut.go b/server/shortcut.go index 248059e..856af9b 100644 --- a/server/shortcut.go +++ b/server/shortcut.go @@ -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) } diff --git a/store/db/migration/dev/LATEST__SCHEMA.sql b/store/db/migration/dev/LATEST__SCHEMA.sql index d4ee07e..1eb9227 100644 --- a/store/db/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/migration/dev/LATEST__SCHEMA.sql @@ -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) -); \ No newline at end of file + ('shortcut', 1000); \ No newline at end of file diff --git a/store/db/migration/prod/LATEST__SCHEMA.sql b/store/db/migration/prod/LATEST__SCHEMA.sql index d4ee07e..1eb9227 100644 --- a/store/db/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/migration/prod/LATEST__SCHEMA.sql @@ -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) -); \ No newline at end of file + ('shortcut', 1000); \ No newline at end of file diff --git a/store/db/seed/10000__reset.sql b/store/db/seed/10000__reset.sql index afd5a14..66c04fc 100644 --- a/store/db/seed/10000__reset.sql +++ b/store/db/seed/10000__reset.sql @@ -1,6 +1,3 @@ -DELETE FROM - shortcut_organizer; - DELETE FROM shortcut; diff --git a/web/src/components/ShortcutListView.tsx b/web/src/components/ShortcutListView.tsx index 1401519..199a1ef 100644 --- a/web/src/components/ShortcutListView.tsx +++ b/web/src/components/ShortcutListView.tsx @@ -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) => { 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({ 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) => { actions={ <>