feat: webiste add and list
This commit is contained in:
parent
2ccb084959
commit
bd7a5776c3
7
nodemon.json
Normal file
7
nodemon.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"verbose": true,
|
||||||
|
"watch": ["./src/server"],
|
||||||
|
"ext": "ts",
|
||||||
|
"delay": 1000,
|
||||||
|
"exec": "ts-node --transpileOnly ./src/server/main.ts"
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon src/server/main.ts -w src/server",
|
"dev": "nodemon",
|
||||||
"start": "NODE_ENV=production ts-node src/server/main.ts",
|
"start": "NODE_ENV=production ts-node src/server/main.ts",
|
||||||
"build": "vite build && pnpm build:tracker && pnpm build:geo",
|
"build": "vite build && pnpm build:tracker && pnpm build:geo",
|
||||||
"build:tracker": "ts-node scripts/build-tracker.ts",
|
"build:tracker": "ts-node scripts/build-tracker.ts",
|
||||||
@ -16,6 +16,7 @@
|
|||||||
"@ant-design/charts": "^1.4.2",
|
"@ant-design/charts": "^1.4.2",
|
||||||
"@ant-design/icons": "^5.2.5",
|
"@ant-design/icons": "^5.2.5",
|
||||||
"@prisma/client": "^5.2.0",
|
"@prisma/client": "^5.2.0",
|
||||||
|
"@tanstack/react-query": "^4.33.0",
|
||||||
"@types/uuid": "^9.0.3",
|
"@types/uuid": "^9.0.3",
|
||||||
"antd": "^5.8.5",
|
"antd": "^5.8.5",
|
||||||
"axios": "^1.5.0",
|
"axios": "^1.5.0",
|
||||||
|
@ -10,6 +10,9 @@ dependencies:
|
|||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.2.0(prisma@5.2.0)
|
version: 5.2.0(prisma@5.2.0)
|
||||||
|
'@tanstack/react-query':
|
||||||
|
specifier: ^4.33.0
|
||||||
|
version: 4.33.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@types/uuid':
|
'@types/uuid':
|
||||||
specifier: ^9.0.3
|
specifier: ^9.0.3
|
||||||
version: 9.0.3
|
version: 9.0.3
|
||||||
@ -1760,6 +1763,28 @@ packages:
|
|||||||
resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
|
resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@tanstack/query-core@4.33.0:
|
||||||
|
resolution: {integrity: sha512-qYu73ptvnzRh6se2nyBIDHGBQvPY1XXl3yR769B7B6mIDD7s+EZhdlWHQ67JI6UOTFRaI7wupnTnwJ3gE0Mr/g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@tanstack/react-query@4.33.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-97nGbmDK0/m0B86BdiXzx3EW9RcDYKpnyL2+WwyuLHEgpfThYAnXFaMMmnTDuAO4bQJXEhflumIEUfKmP7ESGA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-native: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/query-core': 4.33.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@tsconfig/node10@1.0.9:
|
/@tsconfig/node10@1.0.9:
|
||||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||||
|
|
||||||
|
@ -29,8 +29,10 @@ model Workspace {
|
|||||||
updatedAt DateTime? @updatedAt @db.Timestamptz(6)
|
updatedAt DateTime? @updatedAt @db.Timestamptz(6)
|
||||||
|
|
||||||
users WorkspacesOnUsers[]
|
users WorkspacesOnUsers[]
|
||||||
website Website[]
|
websites Website[]
|
||||||
User User[]
|
|
||||||
|
// for user currentWorkspace
|
||||||
|
selectedUsers User[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model WorkspacesOnUsers {
|
model WorkspacesOnUsers {
|
||||||
|
@ -8,35 +8,38 @@ import { Settings } from './pages/Settings';
|
|||||||
import { Servers } from './pages/Servers';
|
import { Servers } from './pages/Servers';
|
||||||
import { useUserStore } from './store/user';
|
import { useUserStore } from './store/user';
|
||||||
import { Register } from './pages/Register';
|
import { Register } from './pages/Register';
|
||||||
|
import { QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { queryClient } from './api/cache';
|
||||||
function App() {
|
function App() {
|
||||||
const { info } = useUserStore();
|
const { info } = useUserStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<BrowserRouter>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Routes>
|
<BrowserRouter>
|
||||||
{info && (
|
<Routes>
|
||||||
<Route element={<Layout />}>
|
{info && (
|
||||||
<Route path="/dashboard" element={<Dashboard />} />
|
<Route element={<Layout />}>
|
||||||
<Route path="/monitor" element={<Monitor />} />
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
<Route path="/website" element={<Website />} />
|
<Route path="/monitor" element={<Monitor />} />
|
||||||
<Route path="/servers" element={<Servers />} />
|
<Route path="/website" element={<Website />} />
|
||||||
<Route path="/settings" element={<Settings />} />
|
<Route path="/servers" element={<Servers />} />
|
||||||
</Route>
|
<Route path="/settings" element={<Settings />} />
|
||||||
)}
|
</Route>
|
||||||
|
)}
|
||||||
|
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/register" element={<Register />} />
|
<Route path="/register" element={<Register />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={
|
element={
|
||||||
<Navigate to={info ? '/dashboard' : '/login'} replace={true} />
|
<Navigate to={info ? '/dashboard' : '/login'} replace={true} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</QueryClientProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
10
src/client/api/cache.ts
Normal file
10
src/client/api/cache.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
export const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -1,7 +1,24 @@
|
|||||||
import { setUserInfo, UserLoginInfo } from '../../store/user';
|
import { setUserInfo } from '../../store/user';
|
||||||
import { getJWT, setJWT } from '../auth';
|
import { getJWT, setJWT } from '../auth';
|
||||||
import { request } from '../request';
|
import { request } from '../request';
|
||||||
|
|
||||||
|
export interface UserLoginInfo {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
role: string;
|
||||||
|
currentWorkspace: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
workspaces: {
|
||||||
|
role: string;
|
||||||
|
workspace: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
export async function login(username: string, password: string) {
|
export async function login(username: string, password: string) {
|
||||||
const { data } = await request.post('/api/user/login', {
|
const { data } = await request.post('/api/user/login', {
|
||||||
username,
|
username,
|
||||||
|
54
src/client/api/model/website.ts
Normal file
54
src/client/api/model/website.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { queryClient } from '../cache';
|
||||||
|
import { request } from '../request';
|
||||||
|
|
||||||
|
export interface WebsiteInfo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
domain: string | null;
|
||||||
|
shareId: string | null;
|
||||||
|
resetAt: string | null;
|
||||||
|
workspaceId: string;
|
||||||
|
createdAt: string | null;
|
||||||
|
updatedAt: string | null;
|
||||||
|
deletedAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkspaceWebsites(
|
||||||
|
workspaceId: string
|
||||||
|
): Promise<WebsiteInfo[]> {
|
||||||
|
const { data } = await request.get('/api/workspace/websites', {
|
||||||
|
params: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.websites;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWorspaceWebsites(workspaceId: string) {
|
||||||
|
const { data: websites = [], isLoading } = useQuery(
|
||||||
|
['websites', workspaceId],
|
||||||
|
() => {
|
||||||
|
return getWorkspaceWebsites(workspaceId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { websites, isLoading };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function refreshWorkspaceWebsites(workspaceId: string) {
|
||||||
|
queryClient.refetchQueries(['websites', workspaceId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addWorkspaceWebsite(
|
||||||
|
workspaceId: string,
|
||||||
|
name: string,
|
||||||
|
domain: string
|
||||||
|
) {
|
||||||
|
await request.post('/api/workspace/website', {
|
||||||
|
workspaceId,
|
||||||
|
name,
|
||||||
|
domain,
|
||||||
|
});
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Select } from 'antd';
|
import { Dropdown, Select } from 'antd';
|
||||||
import { compact } from 'lodash-es';
|
import { compact } from 'lodash-es';
|
||||||
|
|
||||||
export const DateFilter: React.FC<{
|
export const DateFilter: React.FC<{
|
||||||
|
11
src/client/components/Loading.tsx
Normal file
11
src/client/components/Loading.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const Loading: React.FC = React.memo(() => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LoadingOutlined />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Loading.displayName = 'Loading';
|
6
src/client/components/NoWorkspaceTip.tsx
Normal file
6
src/client/components/NoWorkspaceTip.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const NoWorkspaceTip: React.FC = React.memo(() => {
|
||||||
|
return <div>Please Select Workspace</div>;
|
||||||
|
});
|
||||||
|
NoWorkspaceTip.displayName = 'NoWorkspaceTip';
|
@ -6,18 +6,43 @@ import {
|
|||||||
import { Button, Form, Input, Modal, Table } from 'antd';
|
import { Button, Form, Input, Modal, Table } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
addWorkspaceWebsite,
|
||||||
|
refreshWorkspaceWebsites,
|
||||||
|
useWorspaceWebsites,
|
||||||
|
WebsiteInfo,
|
||||||
|
} from '../api/model/website';
|
||||||
|
import { Loading } from '../components/Loading';
|
||||||
|
import { NoWorkspaceTip } from '../components/NoWorkspaceTip';
|
||||||
|
import { useRequest } from '../hooks/useRequest';
|
||||||
|
import { useUserStore } from '../store/user';
|
||||||
|
|
||||||
export const Website: React.FC = React.memo(() => {
|
export const Website: React.FC = React.memo(() => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const currentWorkspace = useUserStore(
|
||||||
|
(state) => state.info?.currentWorkspace
|
||||||
|
);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const handleOk = () => {
|
const [{ loading }, handleAddWebsite] = useRequest(async () => {
|
||||||
|
await form.validateFields();
|
||||||
|
const values = form.getFieldsValue();
|
||||||
|
|
||||||
|
await addWorkspaceWebsite(currentWorkspace!.id, values.name, values.domain);
|
||||||
|
refreshWorkspaceWebsites(currentWorkspace!.id);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
|
||||||
|
form.resetFields();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentWorkspace) {
|
||||||
|
return <NoWorkspaceTip />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-24 flex items-center">
|
<div className="h-24 flex items-center">
|
||||||
<div className="text-2xl flex-1">Servers</div>
|
<div className="text-2xl flex-1">Websites</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -30,19 +55,26 @@ export const Website: React.FC = React.memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<WebsiteList />
|
<WebsiteList workspaceId={currentWorkspace.id} />
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Add Server"
|
title="Add Server"
|
||||||
open={isModalOpen}
|
open={isModalOpen}
|
||||||
onOk={handleOk}
|
okButtonProps={{
|
||||||
|
loading,
|
||||||
|
}}
|
||||||
|
onOk={() => handleAddWebsite()}
|
||||||
onCancel={() => setIsModalOpen(false)}
|
onCancel={() => setIsModalOpen(false)}
|
||||||
>
|
>
|
||||||
<Form layout="vertical">
|
<Form layout="vertical" form={form}>
|
||||||
<Form.Item label="Server Name">
|
<Form.Item
|
||||||
|
label="Server Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Domain">
|
<Form.Item label="Domain" name="domain" rules={[{ required: true }]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
@ -52,20 +84,10 @@ export const Website: React.FC = React.memo(() => {
|
|||||||
});
|
});
|
||||||
Website.displayName = 'Website';
|
Website.displayName = 'Website';
|
||||||
|
|
||||||
interface WebsiteInfoRecordType {
|
const WebsiteList: React.FC<{ workspaceId: string }> = React.memo((props) => {
|
||||||
name: string;
|
const { websites, isLoading } = useWorspaceWebsites(props.workspaceId);
|
||||||
domain: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WebsiteList: React.FC = React.memo(() => {
|
const columns = useMemo((): ColumnsType<WebsiteInfo> => {
|
||||||
const dataSource: WebsiteInfoRecordType[] = [
|
|
||||||
{
|
|
||||||
name: 'tianji',
|
|
||||||
domain: 'tianji.msgbyte.com',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const columns = useMemo((): ColumnsType<WebsiteInfoRecordType> => {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
@ -89,6 +111,10 @@ const WebsiteList: React.FC = React.memo(() => {
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <Table columns={columns} dataSource={dataSource} pagination={false} />;
|
if (isLoading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Table columns={columns} dataSource={websites} pagination={false} />;
|
||||||
});
|
});
|
||||||
WebsiteList.displayName = 'WebsiteList';
|
WebsiteList.displayName = 'WebsiteList';
|
||||||
|
@ -1,21 +1,5 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
import { UserLoginInfo } from '../api/model/user';
|
||||||
export interface UserLoginInfo {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
role: string;
|
|
||||||
currentWorkspace: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
workspaces: {
|
|
||||||
role: string;
|
|
||||||
workspace: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserState {
|
interface UserState {
|
||||||
info: UserLoginInfo | null;
|
info: UserLoginInfo | null;
|
||||||
|
@ -6,8 +6,9 @@ import compression from 'compression';
|
|||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import { userRouter } from './router/user';
|
import { userRouter } from './router/user';
|
||||||
import { websiteRouter } from './router/website';
|
import { websiteRouter } from './router/website';
|
||||||
|
import { workspaceRouter } from './router/workspace';
|
||||||
|
|
||||||
const port = Number(process.env.PORT || 3000);
|
const port = Number(process.env.PORT || 12345);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ app.disable('x-powered-by');
|
|||||||
|
|
||||||
app.use('/api/user', userRouter);
|
app.use('/api/user', userRouter);
|
||||||
app.use('/api/website', websiteRouter);
|
app.use('/api/website', websiteRouter);
|
||||||
|
app.use('/api/workspace', workspaceRouter);
|
||||||
|
|
||||||
app.use((err: any, req: any, res: any, next: any) => {
|
app.use((err: any, req: any, res: any, next: any) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -11,7 +11,7 @@ export const jwtSecret =
|
|||||||
export const jwtIssuer = process.env.JWT_ISSUER || 'tianji.msgbyte.com';
|
export const jwtIssuer = process.env.JWT_ISSUER || 'tianji.msgbyte.com';
|
||||||
export const jwtAudience = process.env.JWT_AUDIENCE || 'msgbyte.com';
|
export const jwtAudience = process.env.JWT_AUDIENCE || 'msgbyte.com';
|
||||||
|
|
||||||
interface JWTPayload {
|
export interface JWTPayload {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
role: string;
|
role: string;
|
||||||
|
52
src/server/model/workspace.ts
Normal file
52
src/server/model/workspace.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { prisma } from './_client';
|
||||||
|
|
||||||
|
export async function checkIsWorkspaceUser(
|
||||||
|
workspaceId: string,
|
||||||
|
userId: string
|
||||||
|
) {
|
||||||
|
const workspace = await prisma.workspace.findUnique({
|
||||||
|
where: {
|
||||||
|
id: workspaceId,
|
||||||
|
users: {
|
||||||
|
some: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (workspace) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkspaceWebsites(workspaceId: string) {
|
||||||
|
const workspace = await prisma.workspace.findUnique({
|
||||||
|
where: {
|
||||||
|
id: workspaceId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
websites: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return workspace?.websites ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addWorkspaceWebsite(
|
||||||
|
workspaceId: string,
|
||||||
|
name: string,
|
||||||
|
domain: string
|
||||||
|
) {
|
||||||
|
const website = await prisma.website.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
domain,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return website;
|
||||||
|
}
|
64
src/server/router/workspace.ts
Normal file
64
src/server/router/workspace.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import { auth } from '../middleware/auth';
|
||||||
|
import { body, param, query, validate } from '../middleware/validate';
|
||||||
|
import {
|
||||||
|
addWorkspaceWebsite,
|
||||||
|
checkIsWorkspaceUser,
|
||||||
|
getWorkspaceWebsites,
|
||||||
|
} from '../model/workspace';
|
||||||
|
|
||||||
|
export const workspaceRouter = Router();
|
||||||
|
|
||||||
|
workspaceRouter.get(
|
||||||
|
'/websites',
|
||||||
|
validate(
|
||||||
|
query('workspaceId')
|
||||||
|
.isString()
|
||||||
|
.withMessage('workspaceId should be string')
|
||||||
|
.isUUID()
|
||||||
|
.withMessage('workspaceId should be UUID')
|
||||||
|
),
|
||||||
|
auth(),
|
||||||
|
async (req, res) => {
|
||||||
|
const userId = req.user!.id;
|
||||||
|
const workspaceId = req.query.workspaceId as string;
|
||||||
|
|
||||||
|
const isWorkspaceUser = await checkIsWorkspaceUser(workspaceId, userId);
|
||||||
|
|
||||||
|
if (!isWorkspaceUser) {
|
||||||
|
throw new Error('Is not workspace user');
|
||||||
|
}
|
||||||
|
|
||||||
|
const websites = await getWorkspaceWebsites(workspaceId);
|
||||||
|
|
||||||
|
res.json({ websites });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
workspaceRouter.post(
|
||||||
|
'/website',
|
||||||
|
validate(
|
||||||
|
body('workspaceId')
|
||||||
|
.isString()
|
||||||
|
.withMessage('workspaceId should be string')
|
||||||
|
.isUUID()
|
||||||
|
.withMessage('workspaceId should be UUID'),
|
||||||
|
body('name').isString().withMessage('name should be a string'),
|
||||||
|
body('domain').isURL().withMessage('domain should be URL')
|
||||||
|
),
|
||||||
|
auth(),
|
||||||
|
async (req, res) => {
|
||||||
|
const userId = req.user!.id;
|
||||||
|
const { workspaceId, name, domain } = req.body;
|
||||||
|
|
||||||
|
const isWorkspaceUser = await checkIsWorkspaceUser(workspaceId, userId);
|
||||||
|
|
||||||
|
if (!isWorkspaceUser) {
|
||||||
|
throw new Error('Is not workspace user');
|
||||||
|
}
|
||||||
|
|
||||||
|
const website = await addWorkspaceWebsite(workspaceId, name, domain);
|
||||||
|
|
||||||
|
res.json({ website });
|
||||||
|
}
|
||||||
|
);
|
7
src/server/types/global.d.ts
vendored
Normal file
7
src/server/types/global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { JWTPayload } from '../middleware/auth';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface User extends JWTPayload {}
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src", "types"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user