feat: add delete button

This commit is contained in:
moonrailgun 2023-09-06 14:14:12 +08:00
parent bc06258a1d
commit 066c9e8895
7 changed files with 132 additions and 46 deletions

View File

@ -53,6 +53,15 @@ export async function updateWorkspaceWebsiteInfo(
queryClient.resetQueries(['websites', workspaceId]); queryClient.resetQueries(['websites', workspaceId]);
} }
export async function deleteWorkspaceWebsite(
workspaceId: string,
websiteId: string
) {
await request.delete(`/api/workspace/${workspaceId}/website/${websiteId}`);
queryClient.resetQueries(['websites', workspaceId]);
}
export function useWorspaceWebsites(workspaceId: string) { export function useWorspaceWebsites(workspaceId: string) {
const { data: websites = [], isLoading } = useQuery( const { data: websites = [], isLoading } = useQuery(
['websites', workspaceId], ['websites', workspaceId],

View File

@ -9,10 +9,7 @@ function createRequest() {
const ins = axios.create(); const ins = axios.create();
ins.interceptors.request.use(async (val) => { ins.interceptors.request.use(async (val) => {
if ( if (!val.headers.Authorization) {
['post', 'get'].includes(String(val.method).toLowerCase()) &&
!val.headers.Authorization
) {
val.headers.Authorization = `Bearer ${getJWT()}`; val.headers.Authorization = `Bearer ${getJWT()}`;
} }

View File

@ -1,7 +1,8 @@
import { Button, Form, Input, message } from 'antd'; import { Button, Form, Input, message, Popconfirm, Tabs } from 'antd';
import React from 'react'; import React from 'react';
import { useParams } from 'react-router'; import { useNavigate, useParams } from 'react-router';
import { import {
deleteWorkspaceWebsite,
updateWorkspaceWebsiteInfo, updateWorkspaceWebsiteInfo,
useWorkspaceWebsiteInfo, useWorkspaceWebsiteInfo,
} from '../api/model/website'; } from '../api/model/website';
@ -20,6 +21,7 @@ export const WebsiteInfo: React.FC = React.memo(() => {
currentWorkspaceId!, currentWorkspaceId!,
websiteId! websiteId!
); );
const navigate = useNavigate();
const [, handleSave] = useRequest( const [, handleSave] = useRequest(
async (values: { name: string; domain: string }) => { async (values: { name: string; domain: string }) => {
@ -32,6 +34,14 @@ export const WebsiteInfo: React.FC = React.memo(() => {
} }
); );
const [, handleDeleteWebsite] = useRequest(async () => {
await deleteWorkspaceWebsite(currentWorkspaceId!, websiteId!);
message.success('Delete Success');
navigate('/settings/websites');
});
if (!currentWorkspaceId) { if (!currentWorkspaceId) {
return <NoWorkspaceTip />; return <NoWorkspaceTip />;
} }
@ -55,31 +65,50 @@ export const WebsiteInfo: React.FC = React.memo(() => {
</div> </div>
<div> <div>
<Form <Tabs>
layout="vertical" <Tabs.TabPane key={'detail'} tab={'Detail'}>
initialValues={{ <Form
id: website.id, layout="vertical"
name: website.name, initialValues={{
domain: website.domain, id: website.id,
}} name: website.name,
onFinish={handleSave} domain: website.domain,
> }}
<Form.Item label="Website ID" name="id"> onFinish={handleSave}
<Input size="large" disabled={true} /> >
</Form.Item> <Form.Item label="Website ID" name="id">
<Form.Item label="Name" name="name" rules={[{ required: true }]}> <Input size="large" disabled={true} />
<Input size="large" /> </Form.Item>
</Form.Item> <Form.Item label="Name" name="name" rules={[{ required: true }]}>
<Form.Item label="Domain" name="domain" rules={[{ required: true }]}> <Input size="large" />
<Input size="large" /> </Form.Item>
</Form.Item> <Form.Item
label="Domain"
name="domain"
rules={[{ required: true }]}
>
<Input size="large" />
</Form.Item>
<Form.Item> <Form.Item>
<Button size="large" htmlType="submit"> <Button size="large" htmlType="submit">
Save Save
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>
</Tabs.TabPane>
<Tabs.TabPane key={'data'} tab={'Data'}>
<Popconfirm
title="Delete Website"
onConfirm={() => handleDeleteWebsite()}
>
<Button type="primary" danger={true}>
Delete Website
</Button>
</Popconfirm>
</Tabs.TabPane>
</Tabs>
</div> </div>
</div> </div>
); );

View File

@ -34,7 +34,6 @@ export const Settings: React.FC = React.memo(() => {
mode="vertical" mode="vertical"
items={items} items={items}
/> />
{pathname}
</div> </div>
<div className="w-full md:w-5/6 py-2 px-4"> <div className="w-full md:w-5/6 py-2 px-4">
<Routes> <Routes>

View File

@ -1,7 +1,7 @@
import { Handler } from 'express'; import { Handler } from 'express';
import { checkIsWorkspaceUser } from '../model/workspace'; import { getWorkspaceUser } from '../model/workspace';
export function workspacePermission(): Handler { export function workspacePermission(roles: string[] = []): Handler {
return async (req, res, next) => { return async (req, res, next) => {
const workspaceId = const workspaceId =
req.body.workspaceId ?? req.query.workspaceId ?? req.params.workspaceId; req.body.workspaceId ?? req.query.workspaceId ?? req.params.workspaceId;
@ -16,12 +16,20 @@ export function workspacePermission(): Handler {
throw new Error('This middleware should be use after auth()'); throw new Error('This middleware should be use after auth()');
} }
const isWorkspaceUser = await checkIsWorkspaceUser(workspaceId, userId); const info = await getWorkspaceUser(workspaceId, userId);
if (!isWorkspaceUser) { if (!info) {
throw new Error('Is not workspace user'); throw new Error('Is not workspace user');
} }
if (Array.isArray(roles) && roles.length > 0) {
if (!roles.includes(info.role)) {
throw new Error(
`Workspace roles not has this permission, need ${roles}`
);
}
}
next(); next();
}; };
} }

View File

@ -1,21 +1,23 @@
import { prisma } from './_client'; import { prisma } from './_client';
export async function getWorkspaceUser(workspaceId: string, userId: string) {
const info = await prisma.workspacesOnUsers.findFirst({
where: {
workspaceId,
userId,
},
});
return info;
}
export async function checkIsWorkspaceUser( export async function checkIsWorkspaceUser(
workspaceId: string, workspaceId: string,
userId: string userId: string
) { ) {
const workspace = await prisma.workspace.findUnique({ const info = await getWorkspaceUser(workspaceId, userId);
where: {
id: workspaceId,
users: {
some: {
userId,
},
},
},
});
if (workspace) { if (info) {
return true; return true;
} else { } else {
return false; return false;
@ -84,3 +86,17 @@ export async function addWorkspaceWebsite(
return website; return website;
} }
export async function deleteWorkspaceWebsite(
workspaceId: string,
websiteId: string
) {
const website = await prisma.website.delete({
where: {
id: websiteId,
workspaceId,
},
});
return website;
}

View File

@ -4,10 +4,12 @@ import { body, param, query, validate } from '../middleware/validate';
import { workspacePermission } from '../middleware/workspace'; import { workspacePermission } from '../middleware/workspace';
import { import {
addWorkspaceWebsite, addWorkspaceWebsite,
deleteWorkspaceWebsite,
getWorkspaceWebsiteInfo, getWorkspaceWebsiteInfo,
getWorkspaceWebsites, getWorkspaceWebsites,
updateWorkspaceWebsiteInfo, updateWorkspaceWebsiteInfo,
} from '../model/workspace'; } from '../model/workspace';
import { ROLES } from '../utils/const';
export const workspaceRouter = Router(); export const workspaceRouter = Router();
@ -128,3 +130,29 @@ workspaceRouter.post(
res.json({ website }); res.json({ website });
} }
); );
workspaceRouter.delete(
'/:workspaceId/website/:websiteId',
validate(
param('workspaceId')
.isString()
.withMessage('workspaceId should be string')
.isUUID()
.withMessage('workspaceId should be UUID'),
param('websiteId')
.isString()
.withMessage('workspaceId should be string')
.isUUID()
.withMessage('workspaceId should be UUID')
),
auth(),
workspacePermission([ROLES.owner]),
async (req, res) => {
const workspaceId = req.params.workspaceId;
const websiteId = req.params.websiteId;
const website = await deleteWorkspaceWebsite(workspaceId, websiteId);
res.json({ website });
}
);