all done
This commit is contained in:
parent
f6a36c1739
commit
e2e9d74983
2
client/.env
Normal file
2
client/.env
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_SIMPLE_REST_URL=http://my.api.url/
|
||||
|
20
client/.eslintrc.js
Normal file
20
client/.eslintrc.js
Normal file
@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
24
client/.gitignore
vendored
Normal file
24
client/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
9
client/.prettierrc
Normal file
9
client/.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 90,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "always"
|
||||
}
|
126
client/index.html
Normal file
126
client/index.html
Normal file
@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
<link rel="shortcut icon" href="./favicon.ico" />
|
||||
<title>client</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* CSS Spinner from https://projects.lukehaas.me/css-loaders/ */
|
||||
|
||||
.loader,
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.loader {
|
||||
color: #283593;
|
||||
font-size: 11px;
|
||||
text-indent: -99999em;
|
||||
margin: 55px auto;
|
||||
position: relative;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
box-shadow: inset 0 0 0 1em;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.loader:before {
|
||||
width: 5.2em;
|
||||
height: 10.2em;
|
||||
background: #fafafa;
|
||||
border-radius: 10.2em 0 0 10.2em;
|
||||
top: -0.1em;
|
||||
left: -0.1em;
|
||||
-webkit-transform-origin: 5.2em 5.1em;
|
||||
transform-origin: 5.2em 5.1em;
|
||||
-webkit-animation: load2 2s infinite ease 1.5s;
|
||||
animation: load2 2s infinite ease 1.5s;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
width: 5.2em;
|
||||
height: 10.2em;
|
||||
background: #fafafa;
|
||||
border-radius: 0 10.2em 10.2em 0;
|
||||
top: -0.1em;
|
||||
left: 5.1em;
|
||||
-webkit-transform-origin: 0px 5.1em;
|
||||
transform-origin: 0px 5.1em;
|
||||
-webkit-animation: load2 2s infinite ease;
|
||||
animation: load2 2s infinite ease;
|
||||
}
|
||||
|
||||
@-webkit-keyframes load2 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load2 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="root">
|
||||
<div class="loader-container">
|
||||
<div class="loader">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</html>
|
||||
|
5685
client/package-lock.json
generated
Normal file
5685
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
client/package.json
Normal file
33
client/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "client",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "eslint --fix --ext .js,.jsx,.ts,.tsx ./src",
|
||||
"format": "prettier --write ./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"react": "^18.2.0",
|
||||
"react-admin": "^4.16.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.1",
|
||||
"@types/react": "^18.0.22",
|
||||
"@types/react-dom": "^18.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prettier": "^2.8.8",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.3.9"
|
||||
}
|
||||
}
|
46
client/src/App.tsx
Normal file
46
client/src/App.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { Admin, Resource } from "react-admin";
|
||||
import dataProvider from "./dataProvider";
|
||||
import { SectionCreate, SectionEdit, SectionsList } from "./components/Section";
|
||||
import { MessagesList, MessageCreate, MessageEdit } from "./components/Message";
|
||||
import { RolesList, RoleCreate, RoleEdit } from "./components/Role";
|
||||
import { ThreadsList, ThreadCreate, ThreadEdit } from "./components/Thread";
|
||||
import { UsersList, UserCreate, UserEdit } from "./components/User";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Admin dataProvider={dataProvider}>
|
||||
<Resource
|
||||
name="roles"
|
||||
list={<RolesList />}
|
||||
create={<RoleCreate />}
|
||||
edit={<RoleEdit />}
|
||||
/>
|
||||
<Resource
|
||||
name="users"
|
||||
list={<UsersList />}
|
||||
create={<UserCreate />}
|
||||
edit={<UserEdit />}
|
||||
/>
|
||||
<Resource
|
||||
name="sections"
|
||||
list={<SectionsList />}
|
||||
create={<SectionCreate />}
|
||||
edit={<SectionEdit />}
|
||||
/>
|
||||
<Resource
|
||||
name="threads"
|
||||
list={<ThreadsList />}
|
||||
create={<ThreadCreate />}
|
||||
edit={<ThreadEdit />}
|
||||
/>
|
||||
<Resource
|
||||
name="messages"
|
||||
list={<MessagesList />}
|
||||
create={<MessageCreate />}
|
||||
edit={<MessageEdit />}
|
||||
/>
|
||||
</Admin>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
8
client/src/api/get-sections-filtered-list.ts
Normal file
8
client/src/api/get-sections-filtered-list.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import axios from "axios";
|
||||
import { ISection } from "../components/Section/section.type";
|
||||
|
||||
export async function getSectionsFilteredList(params?: string) {
|
||||
return await axios.get<ISection[]>(
|
||||
`http://localhost:5000/api/sections/get-filtered-list?${params}`
|
||||
);
|
||||
}
|
26
client/src/components/Message/Create.tsx
Normal file
26
client/src/components/Message/Create.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import {
|
||||
Create,
|
||||
ReferenceInput,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
required
|
||||
} from "react-admin";
|
||||
|
||||
function MessageCreate() {
|
||||
return (
|
||||
<Create>
|
||||
<SimpleForm>
|
||||
<TextInput source="text" fullWidth multiline />
|
||||
<ReferenceInput source="thread_id" reference="threads">
|
||||
<SelectInput optionText="name" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
<ReferenceInput source="user_id" reference="users">
|
||||
<SelectInput optionText="nickname" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageCreate;
|
27
client/src/components/Message/Edit.tsx
Normal file
27
client/src/components/Message/Edit.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import {
|
||||
Edit,
|
||||
ReferenceInput,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
required,
|
||||
} from "react-admin";
|
||||
import Title from "../Title";
|
||||
|
||||
function MessageEdit() {
|
||||
return (
|
||||
<Edit title={<Title prefixText="Сообщение" />} actions={false}>
|
||||
<SimpleForm>
|
||||
<TextInput source="text" fullWidth multiline />
|
||||
<ReferenceInput source="thread_id" reference="threads">
|
||||
<SelectInput optionText="name" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
<ReferenceInput source="user_id" reference="users">
|
||||
<SelectInput optionText="nickname" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
</SimpleForm>
|
||||
</Edit>
|
||||
);
|
||||
}
|
||||
|
||||
export default MessageEdit;
|
21
client/src/components/Message/List.tsx
Normal file
21
client/src/components/Message/List.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { List, Datagrid, TextField, EditButton, ReferenceField } from "react-admin";
|
||||
|
||||
function MessagesList() {
|
||||
return (
|
||||
<List>
|
||||
<Datagrid>
|
||||
<TextField source="id" />
|
||||
<TextField source="text" />
|
||||
<ReferenceField label="User" source="user_id" reference="users">
|
||||
<TextField source="nickname" />
|
||||
</ReferenceField>
|
||||
<ReferenceField label="Thread" source="thread_id" reference="threads">
|
||||
<TextField source="name" />
|
||||
</ReferenceField>
|
||||
<EditButton />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessagesList;
|
3
client/src/components/Message/index.tsx
Normal file
3
client/src/components/Message/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as MessageEdit } from "./Edit";
|
||||
export { default as MessageCreate } from "./Create";
|
||||
export { default as MessagesList } from "./List";
|
13
client/src/components/Role/Create.tsx
Normal file
13
client/src/components/Role/Create.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Create, SimpleForm, TextInput } from "react-admin";
|
||||
|
||||
function RoleCreate() {
|
||||
return (
|
||||
<Create>
|
||||
<SimpleForm>
|
||||
<TextInput source="name" fullWidth />
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoleCreate;
|
14
client/src/components/Role/Edit.tsx
Normal file
14
client/src/components/Role/Edit.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Edit, SimpleForm, TextInput } from "react-admin";
|
||||
import Title from "../Title";
|
||||
|
||||
function RoleEdit() {
|
||||
return (
|
||||
<Edit title={<Title prefixText="Роль" />} actions={false}>
|
||||
<SimpleForm>
|
||||
<TextInput source="name" fullWidth />
|
||||
</SimpleForm>
|
||||
</Edit>
|
||||
);
|
||||
}
|
||||
|
||||
export default RoleEdit;
|
15
client/src/components/Role/List.tsx
Normal file
15
client/src/components/Role/List.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { List, Datagrid, TextField, EditButton } from "react-admin";
|
||||
|
||||
function RolesList() {
|
||||
return (
|
||||
<List>
|
||||
<Datagrid>
|
||||
<TextField source="id" />
|
||||
<TextField source="name" />
|
||||
<EditButton />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default RolesList;
|
3
client/src/components/Role/index.tsx
Normal file
3
client/src/components/Role/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as RoleEdit } from "./Edit";
|
||||
export { default as RoleCreate } from "./Create";
|
||||
export { default as RolesList } from "./List";
|
17
client/src/components/Section/Create.tsx
Normal file
17
client/src/components/Section/Create.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { Create, SelectInput, SimpleForm, TextInput } from "react-admin";
|
||||
import { useFilteredSectionsList } from "../../hooks/useFilteredSectionsList";
|
||||
|
||||
function SectionCreate() {
|
||||
const { filteredSectionsList } = useFilteredSectionsList();
|
||||
|
||||
return (
|
||||
<Create>
|
||||
<SimpleForm>
|
||||
<TextInput source="name" fullWidth />
|
||||
<SelectInput source="root_section_id" fullWidth choices={filteredSectionsList} />
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionCreate;
|
25
client/src/components/Section/Edit.tsx
Normal file
25
client/src/components/Section/Edit.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import {
|
||||
Edit,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
useGetRecordId
|
||||
} from "react-admin";
|
||||
import Title from "../Title";
|
||||
import { useFilteredSectionsList } from "../../hooks/useFilteredSectionsList";
|
||||
|
||||
function SectionEdit() {
|
||||
const recordId = useGetRecordId();
|
||||
const { filteredSectionsList } = useFilteredSectionsList(`id=${recordId}`);
|
||||
|
||||
return (
|
||||
<Edit title={<Title prefixText="Роль" />} actions={false}>
|
||||
<SimpleForm>
|
||||
<TextInput source="name" fullWidth />
|
||||
<SelectInput source="root_section_id" fullWidth choices={filteredSectionsList} />
|
||||
</SimpleForm>
|
||||
</Edit>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionEdit;
|
18
client/src/components/Section/List.tsx
Normal file
18
client/src/components/Section/List.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { List, Datagrid, TextField, EditButton, ReferenceField } from "react-admin";
|
||||
|
||||
function SectionsList() {
|
||||
return (
|
||||
<List>
|
||||
<Datagrid>
|
||||
<TextField source="id" />
|
||||
<TextField source="name" />
|
||||
<ReferenceField label="Root section" source="root_section_id" reference="sections">
|
||||
<TextField source="name" />
|
||||
</ReferenceField>
|
||||
<EditButton />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionsList;
|
3
client/src/components/Section/index.tsx
Normal file
3
client/src/components/Section/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as SectionEdit } from "./Edit";
|
||||
export { default as SectionCreate } from "./Create";
|
||||
export { default as SectionsList } from "./List";
|
5
client/src/components/Section/section.type.ts
Normal file
5
client/src/components/Section/section.type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ISection {
|
||||
id: number;
|
||||
name: string;
|
||||
root_section_id: number | null;
|
||||
}
|
27
client/src/components/Thread/Create.tsx
Normal file
27
client/src/components/Thread/Create.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import {
|
||||
Create,
|
||||
ReferenceInput,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
required
|
||||
} from "react-admin";
|
||||
import { useFilteredSectionsList } from "../../hooks/useFilteredSectionsList";
|
||||
|
||||
function ThreadCreate() {
|
||||
const { filteredSectionsList } = useFilteredSectionsList(`onlyEndSections=true`);
|
||||
|
||||
return (
|
||||
<Create>
|
||||
<SimpleForm>
|
||||
<TextInput source="name" fullWidth />
|
||||
<SelectInput source="section_id" fullWidth choices={filteredSectionsList} />
|
||||
<ReferenceInput source="creator_id" reference="users">
|
||||
<SelectInput optionText="nickname" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
);
|
||||
}
|
||||
|
||||
export default ThreadCreate;
|
27
client/src/components/Thread/Edit.tsx
Normal file
27
client/src/components/Thread/Edit.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import {
|
||||
Edit,
|
||||
ReferenceInput,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
required
|
||||
} from "react-admin";
|
||||
import { useFilteredSectionsList } from "../../hooks/useFilteredSectionsList";
|
||||
|
||||
function ThreadEdit() {
|
||||
const { filteredSectionsList } = useFilteredSectionsList(`onlyEndSections=true`);
|
||||
|
||||
return (
|
||||
<Edit actions={false}>
|
||||
<SimpleForm>
|
||||
<TextInput source="name" fullWidth />
|
||||
<SelectInput source="section_id" fullWidth choices={filteredSectionsList} />
|
||||
<ReferenceInput source="creator_id" reference="users">
|
||||
<SelectInput optionText="nickname" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
</SimpleForm>
|
||||
</Edit>
|
||||
);
|
||||
}
|
||||
|
||||
export default ThreadEdit;
|
21
client/src/components/Thread/List.tsx
Normal file
21
client/src/components/Thread/List.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { List, Datagrid, TextField, EditButton, ReferenceField } from "react-admin";
|
||||
|
||||
function ThreadsList() {
|
||||
return (
|
||||
<List>
|
||||
<Datagrid>
|
||||
<TextField source="id" />
|
||||
<TextField source="name" />
|
||||
<ReferenceField label="Section" source="section_id" reference="sections">
|
||||
<TextField source="name" />
|
||||
</ReferenceField>
|
||||
<ReferenceField label="User" source="creator_id" reference="users">
|
||||
<TextField source="nickname" />
|
||||
</ReferenceField>
|
||||
<EditButton />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
||||
export default ThreadsList;
|
3
client/src/components/Thread/index.tsx
Normal file
3
client/src/components/Thread/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as ThreadEdit } from "./Edit";
|
||||
export { default as ThreadCreate } from "./Create";
|
||||
export { default as ThreadsList } from "./List";
|
15
client/src/components/Title.tsx
Normal file
15
client/src/components/Title.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useRecordContext } from "react-admin";
|
||||
|
||||
function Title({ prefixText = "" }: { prefixText?: string }) {
|
||||
const record = useRecordContext();
|
||||
if (!record) return null;
|
||||
|
||||
return (
|
||||
<span>
|
||||
{prefixText} {record.name} (id: {record.id})
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Title;
|
27
client/src/components/User/Create.tsx
Normal file
27
client/src/components/User/Create.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import {
|
||||
Create,
|
||||
PasswordInput,
|
||||
ReferenceInput,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
required,
|
||||
} from "react-admin";
|
||||
|
||||
function UserCreate() {
|
||||
return (
|
||||
<Create>
|
||||
<SimpleForm>
|
||||
<TextInput source="nickname" fullWidth />
|
||||
<TextInput source="email" fullWidth />
|
||||
<TextInput source="avatar" fullWidth />
|
||||
<ReferenceInput source="role_id" reference="roles">
|
||||
<SelectInput optionText="name" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
<PasswordInput source="password" fullWidth />
|
||||
</SimpleForm>
|
||||
</Create>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserCreate;
|
28
client/src/components/User/Edit.tsx
Normal file
28
client/src/components/User/Edit.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import {
|
||||
Edit,
|
||||
PasswordInput,
|
||||
ReferenceInput,
|
||||
SelectInput,
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
required,
|
||||
} from "react-admin";
|
||||
import Title from "../Title";
|
||||
|
||||
function UserEdit() {
|
||||
return (
|
||||
<Edit title={<Title prefixText="Пользователь" />} actions={false}>
|
||||
<SimpleForm>
|
||||
<TextInput source="nickname" fullWidth />
|
||||
<TextInput source="email" fullWidth />
|
||||
<TextInput source="avatar" fullWidth />
|
||||
<ReferenceInput source="role_id" reference="roles">
|
||||
<SelectInput optionText="name" fullWidth validate={required()} />
|
||||
</ReferenceInput>
|
||||
<PasswordInput source="password" fullWidth />
|
||||
</SimpleForm>
|
||||
</Edit>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserEdit;
|
20
client/src/components/User/List.tsx
Normal file
20
client/src/components/User/List.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { List, Datagrid, TextField, EditButton, ReferenceField } from "react-admin";
|
||||
|
||||
function UsersList() {
|
||||
return (
|
||||
<List>
|
||||
<Datagrid>
|
||||
<TextField source="id" />
|
||||
<TextField source="nickname" />
|
||||
<TextField source="email" />
|
||||
<TextField source="avatar" />
|
||||
<ReferenceField label="Role" source="role_id" reference="roles">
|
||||
<TextField source="name" />
|
||||
</ReferenceField>
|
||||
<EditButton />
|
||||
</Datagrid>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersList;
|
3
client/src/components/User/index.tsx
Normal file
3
client/src/components/User/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as UserEdit } from "./Edit";
|
||||
export { default as UserCreate } from "./Create";
|
||||
export { default as UsersList } from "./List";
|
103
client/src/dataProvider.ts
Normal file
103
client/src/dataProvider.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { DataProvider, fetchUtils } from "react-admin";
|
||||
import { stringify } from "query-string";
|
||||
import axios from "axios";
|
||||
|
||||
const apiUrl = "http://localhost:5000/api";
|
||||
const httpClient = fetchUtils.fetchJson;
|
||||
|
||||
export default {
|
||||
getList: async (resource, params) => {
|
||||
const { page, perPage } = params.pagination;
|
||||
const { field, order } = params.sort;
|
||||
const query = {
|
||||
sort: JSON.stringify([field, order]),
|
||||
range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
|
||||
filter: JSON.stringify(params.filter),
|
||||
};
|
||||
const url = `${apiUrl}/${resource}?${stringify(query)}`;
|
||||
const response = await axios.get(url);
|
||||
|
||||
return {
|
||||
data: response.data.data,
|
||||
total: response.data.total,
|
||||
};
|
||||
},
|
||||
|
||||
getOne: async (resource, params) => {
|
||||
const url = `${apiUrl}/${resource}/${params.id}`;
|
||||
const { data } = await axios.get(url);
|
||||
return { data };
|
||||
},
|
||||
|
||||
getMany: async (resource, params) => {
|
||||
const query = {
|
||||
filter: JSON.stringify({ ids: params.ids }),
|
||||
};
|
||||
const url = `${apiUrl}/${resource}/get-many?${stringify(query)}`;
|
||||
const { data } = await axios.get(url);
|
||||
return { data: data.data };
|
||||
},
|
||||
|
||||
getManyReference: async (resource, params) => {
|
||||
const { page, perPage } = params.pagination;
|
||||
const { field, order } = params.sort;
|
||||
const query = {
|
||||
sort: JSON.stringify([field, order]),
|
||||
range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
|
||||
filter: JSON.stringify({
|
||||
...params.filter,
|
||||
[params.target]: params.id,
|
||||
}),
|
||||
};
|
||||
|
||||
const url = `${apiUrl}/${resource}/many-references?${stringify(query)}`;
|
||||
|
||||
const { data: responseData } = await axios.get(url);
|
||||
return {
|
||||
data: responseData.data,
|
||||
total: responseData.total,
|
||||
};
|
||||
},
|
||||
|
||||
create: async (resource, params) => {
|
||||
// const response = await axios.post(`${apiUrl}/${resource}`, params.data);
|
||||
const { json } = await httpClient(`${apiUrl}/${resource}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(params.data),
|
||||
});
|
||||
return { data: json };
|
||||
},
|
||||
|
||||
update: async (resource, params) => {
|
||||
const url = `${apiUrl}/${resource}/${params.id}`;
|
||||
const { data } = await axios.put(url, params.data);
|
||||
return { data };
|
||||
},
|
||||
|
||||
updateMany: async (resource, params) => {
|
||||
const query = {
|
||||
filter: JSON.stringify({ id: params.ids }),
|
||||
};
|
||||
const url = `${apiUrl}/${resource}?${stringify(query)}`;
|
||||
const { json } = await httpClient(url, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(params.data),
|
||||
});
|
||||
return { data: json };
|
||||
},
|
||||
|
||||
delete: async (resource, params) => {
|
||||
const url = `${apiUrl}/${resource}/${params.id}`;
|
||||
const { data } = await axios.delete(url);
|
||||
return data;
|
||||
},
|
||||
|
||||
deleteMany: async (resource, params) => {
|
||||
const query = {
|
||||
filter: JSON.stringify({ id: params.ids }),
|
||||
};
|
||||
const url = `${apiUrl}/${resource}/delete-many?${stringify(query)}`;
|
||||
const { data } = await axios.delete(url);
|
||||
return data;
|
||||
},
|
||||
} as DataProvider;
|
16
client/src/hooks/useFilteredSectionsList.tsx
Normal file
16
client/src/hooks/useFilteredSectionsList.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ISection } from "../components/Section/section.type";
|
||||
import { getSectionsFilteredList } from "../api/get-sections-filtered-list";
|
||||
|
||||
export const useFilteredSectionsList = (queryParams?: string) => {
|
||||
const [filteredSectionsList, setFilteredSectionsList] = useState<ISection[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const response = await getSectionsFilteredList(queryParams);
|
||||
setFilteredSectionsList(response.data);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return { filteredSectionsList };
|
||||
}
|
9
client/src/index.tsx
Normal file
9
client/src/index.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
1
client/src/vite-env.d.ts
vendored
Normal file
1
client/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
26
client/tsconfig.json
Normal file
26
client/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
14
client/vite.config.ts
Normal file
14
client/vite.config.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
define: {
|
||||
'process.env': process.env,
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
},
|
||||
base: './',
|
||||
});
|
6
server/.env
Normal file
6
server/.env
Normal file
@ -0,0 +1,6 @@
|
||||
PORT=5000
|
||||
DB_NAME=subd_db
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=postgres
|
||||
DB_HOST=192.168.56.101
|
||||
DB_PORT=5432
|
34
server/.eslintrc
Normal file
34
server/.eslintrc
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"env": { "browser": true, "es2020": true },
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
|
||||
"plugins": ["import"],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"prettier/prettier": ["warn", { "endOfLine": "auto" }],
|
||||
"no-console": "warn",
|
||||
"prefer-const": "error",
|
||||
"comma-dangle": ["warn", "never"],
|
||||
"semi": ["warn", "always"],
|
||||
"import/order": [
|
||||
"warn",
|
||||
{
|
||||
"groups": [
|
||||
"builtin",
|
||||
"external",
|
||||
"internal",
|
||||
"parent",
|
||||
"sibling",
|
||||
"index",
|
||||
"object",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
9
server/.prettierrc
Normal file
9
server/.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 90,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "always"
|
||||
}
|
144
server/controllers/message/message.controller.ts
Normal file
144
server/controllers/message/message.controller.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { ApiErrorHandler } from "../../error/api-error.handler";
|
||||
import Message from "../../models/message.model";
|
||||
import type { IMessageDto } from "./message.dto";
|
||||
import type { Order } from "sequelize";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import type {
|
||||
FilterQueryParamType,
|
||||
RangeQueryParamType,
|
||||
SortQueryParamType
|
||||
} from "../query-param.types";
|
||||
|
||||
class MessageController {
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const range: RangeQueryParamType = req.query.range
|
||||
? JSON.parse(req.query.range.toString())
|
||||
: [0, 10];
|
||||
const sort: SortQueryParamType = req.query.sort
|
||||
? JSON.parse(req.query.sort.toString())
|
||||
: ["id", "ASC"];
|
||||
|
||||
const messages = await Message.findAndCountAll({
|
||||
offset: range[0],
|
||||
limit: range[1] - range[0] + 1,
|
||||
order: [sort] as Order
|
||||
});
|
||||
|
||||
return res.json({ data: messages.rows, total: messages.count });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Message[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: { ids: [] };
|
||||
|
||||
const messages = await Message.findAll({
|
||||
where: { id: filter.ids as number[] }
|
||||
});
|
||||
return res.json({ data: messages });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
create = async (
|
||||
req: Request<object, object, IMessageDto>,
|
||||
res: Response<Message>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { text, user_id, thread_id } = req.body;
|
||||
const message = await Message.create({ text, user_id, thread_id });
|
||||
return res.json(message);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getById = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Message>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const message = await Message.findByPk(id);
|
||||
if (!message) return next(ApiErrorHandler.notFound("Такой роли не найдено"));
|
||||
|
||||
return res.json(message);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
update = async (
|
||||
req: Request<{ id: number }, object, IMessageDto>,
|
||||
res: Response<Message>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { text, user_id, thread_id } = req.body;
|
||||
|
||||
const message = await Message.findByPk(id);
|
||||
if (!message) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
message.text = text;
|
||||
message.user_id = user_id;
|
||||
message.thread_id = thread_id;
|
||||
await message.save();
|
||||
|
||||
return res.json(message);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Message>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const message = await Message.findByPk(id);
|
||||
if (!message) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
await message.destroy();
|
||||
|
||||
return res.json(message);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
deleteMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Message[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: {};
|
||||
const messages = await Message.findAll({ where: filter });
|
||||
|
||||
Message.destroy({ where: filter });
|
||||
|
||||
return res.json({ data: messages });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new MessageController();
|
5
server/controllers/message/message.dto.ts
Normal file
5
server/controllers/message/message.dto.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface IMessageDto {
|
||||
text: string;
|
||||
user_id: number;
|
||||
thread_id: number;
|
||||
}
|
5
server/controllers/query-param.types.ts
Normal file
5
server/controllers/query-param.types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type RangeQueryParamType = [number, number];
|
||||
export type SortQueryParamType = [string, string] | Array<[string, string]>;
|
||||
export type FilterQueryParamType = {
|
||||
[key: string]: unknown
|
||||
}
|
138
server/controllers/role/role.controller.ts
Normal file
138
server/controllers/role/role.controller.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import Role from "../../models/role.model";
|
||||
import { ApiErrorHandler } from "../../error/api-error.handler";
|
||||
import type { Order } from "sequelize";
|
||||
import type { IRoleDto } from "./role.dto";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import type {
|
||||
FilterQueryParamType,
|
||||
RangeQueryParamType,
|
||||
SortQueryParamType
|
||||
} from "../query-param.types";
|
||||
|
||||
class RoleController {
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const range: RangeQueryParamType = req.query.range
|
||||
? JSON.parse(req.query.range.toString())
|
||||
: [0, 10];
|
||||
const sort: SortQueryParamType = req.query.sort
|
||||
? JSON.parse(req.query.sort.toString())
|
||||
: ["id", "ASC"];
|
||||
|
||||
const roles = await Role.findAndCountAll({
|
||||
offset: range[0],
|
||||
limit: range[1] - range[0] + 1,
|
||||
order: [sort] as Order
|
||||
});
|
||||
|
||||
return res.json({ data: roles.rows, total: roles.count });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getMany = async (req: Request, res: Response<{ data: Role[] }>, next: NextFunction) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: { ids: [] };
|
||||
|
||||
const roles = await Role.findAll({
|
||||
where: { id: filter.ids as number[] }
|
||||
});
|
||||
return res.json({ data: roles });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
create = async (
|
||||
req: Request<object, object, IRoleDto>,
|
||||
res: Response<Role>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
const role = await Role.create({ name: name.toUpperCase() });
|
||||
return res.json(role);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getById = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Role>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const role = await Role.findByPk(id);
|
||||
if (!role) return next(ApiErrorHandler.notFound("Такой роли не найдено"));
|
||||
|
||||
return res.json(role);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
update = async (
|
||||
req: Request<{ id: number }, object, IRoleDto>,
|
||||
res: Response<Role>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name } = req.body;
|
||||
|
||||
const role = await Role.findByPk(id);
|
||||
if (!role) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
role.name = name;
|
||||
await role.save();
|
||||
|
||||
return res.json(role);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Role>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const role = await Role.findByPk(id);
|
||||
if (!role) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
await role.destroy();
|
||||
|
||||
return res.json(role);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
deleteMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Role[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: {};
|
||||
const roles = await Role.findAll({ where: filter });
|
||||
|
||||
Role.destroy({ where: filter });
|
||||
|
||||
return res.json({ data: roles });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new RoleController();
|
3
server/controllers/role/role.dto.ts
Normal file
3
server/controllers/role/role.dto.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface IRoleDto {
|
||||
name: string;
|
||||
}
|
209
server/controllers/section/section.controller.ts
Normal file
209
server/controllers/section/section.controller.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { QueryTypes, type Order } from "sequelize";
|
||||
import Section from "../../models/section.model";
|
||||
import { ApiErrorHandler } from "../../error/api-error.handler";
|
||||
import { sequelize } from "../../db";
|
||||
import type { ISectionDto } from "./section.dto";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import type {
|
||||
FilterQueryParamType,
|
||||
RangeQueryParamType,
|
||||
SortQueryParamType
|
||||
} from "../query-param.types";
|
||||
|
||||
class SectionController {
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const range: RangeQueryParamType = req.query.range
|
||||
? JSON.parse(req.query.range.toString())
|
||||
: [0, 10];
|
||||
const sort: SortQueryParamType = req.query.sort
|
||||
? JSON.parse(req.query.sort.toString())
|
||||
: ["id", "ASC"];
|
||||
|
||||
const sections = await Section.findAndCountAll({
|
||||
offset: range[0],
|
||||
limit: range[1] - range[0] + 1,
|
||||
order: [sort] as Order
|
||||
});
|
||||
|
||||
return res.json({ data: sections.rows, total: sections.count });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getFillteredList = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Сюда приходит id текущего товара при редактировании,
|
||||
// чтобы не возвращать его в списке
|
||||
// или же флаг onlyEndSections
|
||||
// const filter: { onlyEndSections: boolean } | { id: number } = req.query.filter
|
||||
// ? JSON.parse(req.query.filter.toString())
|
||||
// : {};
|
||||
|
||||
const currentSectionId: number = req.query.id
|
||||
? JSON.parse(req.query.id.toString())
|
||||
: -1;
|
||||
|
||||
const onlyEndSections: boolean = req.query.onlyEndSections
|
||||
? JSON.parse(req.query.onlyEndSections.toString())
|
||||
: false;
|
||||
|
||||
// const range: RangeQueryParamType = req.query.range
|
||||
// ? JSON.parse(req.query.range.toString())
|
||||
// : [0, 10];
|
||||
// const sort: SortQueryParamType = req.query.sort
|
||||
// ? JSON.parse(req.query.sort.toString())
|
||||
// : ["id", "ASC"];
|
||||
|
||||
if (onlyEndSections === true) {
|
||||
const endSections = await sequelize.query(
|
||||
`
|
||||
SELECT * FROM sections
|
||||
WHERE id NOT IN (SELECT DISTINCT root_section_id FROM sections
|
||||
WHERE root_section_id IS NOT NULL);
|
||||
`,
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
return res.json(endSections);
|
||||
}
|
||||
|
||||
// const sections = await Section.findAndCountAll({
|
||||
// offset: range[0],
|
||||
// limit: range[1] - range[0] + 1,
|
||||
// order: [sort] as Order,
|
||||
// include: {
|
||||
// model: Thread,
|
||||
// required: false,
|
||||
// where: { id: null }
|
||||
// },
|
||||
// where: {
|
||||
// id: {
|
||||
// [Op.ne]: filter && "id" in filter ? filter.id : null
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
const sections = await sequelize.query(
|
||||
`SELECT * FROM sections WHERE id != ${currentSectionId} AND id NOT IN
|
||||
(SELECT DISTINCT section_id FROM threads) AND
|
||||
(root_section_id IS NULL OR root_section_id != ${currentSectionId});`,
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
return res.json(sections);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Section[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: { ids: [] };
|
||||
|
||||
const sections = await Section.findAll({
|
||||
where: { id: filter.ids as number[] }
|
||||
});
|
||||
return res.json({ data: sections });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
create = async (
|
||||
req: Request<object, object, ISectionDto>,
|
||||
res: Response<Section>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { name, root_section_id } = req.body;
|
||||
const section = await Section.create({ name, root_section_id });
|
||||
return res.json(section);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getById = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Section>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const section = await Section.findByPk(id);
|
||||
if (!section) return next(ApiErrorHandler.notFound("Такой роли не найдено"));
|
||||
|
||||
return res.json(section);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
update = async (
|
||||
req: Request<{ id: number }, object, ISectionDto>,
|
||||
res: Response<Section>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, root_section_id } = req.body;
|
||||
|
||||
const section = await Section.findByPk(id);
|
||||
if (!section) return next(ApiErrorHandler.internal("Такой секции не найдено"));
|
||||
|
||||
section.name = name;
|
||||
section.root_section_id = root_section_id;
|
||||
await section.save();
|
||||
|
||||
return res.json(section);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Section>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const section = await Section.findByPk(id);
|
||||
if (!section) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
await section.destroy();
|
||||
|
||||
return res.json(section);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
deleteMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Section[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: {};
|
||||
const sections = await Section.findAll({ where: filter });
|
||||
|
||||
Section.destroy({ where: filter });
|
||||
|
||||
return res.json({ data: sections });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new SectionController();
|
4
server/controllers/section/section.dto.ts
Normal file
4
server/controllers/section/section.dto.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface ISectionDto {
|
||||
name: string;
|
||||
root_section_id: number | null;
|
||||
}
|
145
server/controllers/thread/thread.controller.ts
Normal file
145
server/controllers/thread/thread.controller.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { type Order } from "sequelize";
|
||||
import Section from "../../models/section.model";
|
||||
import { ApiErrorHandler } from "../../error/api-error.handler";
|
||||
import Thread from "../../models/thread.model";
|
||||
import type { IThreadDto } from "./thread.dto";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import type {
|
||||
FilterQueryParamType,
|
||||
RangeQueryParamType,
|
||||
SortQueryParamType
|
||||
} from "../query-param.types";
|
||||
|
||||
class ThreadController {
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const range: RangeQueryParamType = req.query.range
|
||||
? JSON.parse(req.query.range.toString())
|
||||
: [0, 10];
|
||||
const sort: SortQueryParamType = req.query.sort
|
||||
? JSON.parse(req.query.sort.toString())
|
||||
: ["id", "ASC"];
|
||||
|
||||
const threads = await Thread.findAndCountAll({
|
||||
offset: range[0],
|
||||
limit: range[1] - range[0] + 1,
|
||||
order: [sort] as Order
|
||||
});
|
||||
|
||||
return res.json({ data: threads.rows, total: threads.count });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Thread[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: { ids: [] };
|
||||
|
||||
const threads = await Thread.findAll({
|
||||
where: { id: filter.ids as number[] }
|
||||
});
|
||||
return res.json({ data: threads });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
create = async (
|
||||
req: Request<object, object, IThreadDto>,
|
||||
res: Response<Thread>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { name, section_id, creator_id } = req.body;
|
||||
const thread = await Thread.create({ name, section_id, creator_id });
|
||||
return res.json(thread);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getById = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Thread>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const thread = await Thread.findByPk(id);
|
||||
if (!thread) return next(ApiErrorHandler.notFound("Такой роли не найдено"));
|
||||
|
||||
return res.json(thread);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
update = async (
|
||||
req: Request<{ id: number }, object, IThreadDto>,
|
||||
res: Response<Thread>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, section_id, creator_id } = req.body;
|
||||
|
||||
const thread = await Thread.findByPk(id);
|
||||
if (!thread) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
thread.name = name;
|
||||
thread.section_id = section_id;
|
||||
thread.creator_id = creator_id;
|
||||
await thread.save();
|
||||
|
||||
return res.json(thread);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<Thread>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const thread = await Thread.findByPk(id);
|
||||
if (!thread) return next(ApiErrorHandler.internal("Такой роли не найдено"));
|
||||
|
||||
await thread.destroy();
|
||||
|
||||
return res.json(thread);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
deleteMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: Section[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: {};
|
||||
const sections = await Section.findAll({ where: filter });
|
||||
|
||||
Section.destroy({ where: filter });
|
||||
|
||||
return res.json({ data: sections });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new ThreadController();
|
5
server/controllers/thread/thread.dto.ts
Normal file
5
server/controllers/thread/thread.dto.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface IThreadDto {
|
||||
name: string;
|
||||
section_id: number;
|
||||
creator_id: number;
|
||||
}
|
149
server/controllers/user/user.controller.ts
Normal file
149
server/controllers/user/user.controller.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import Role from "../../models/role.model";
|
||||
import { ApiErrorHandler } from "../../error/api-error.handler";
|
||||
import User from "../../models/user.model";
|
||||
import type { IUserDto } from "./user.dto";
|
||||
import type { Order } from "sequelize";
|
||||
import type {
|
||||
FilterQueryParamType,
|
||||
RangeQueryParamType,
|
||||
SortQueryParamType
|
||||
} from "../query-param.types";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
|
||||
class UserController {
|
||||
getAll = async (
|
||||
req: Request,
|
||||
res: Response<{ data: User[]; total: number }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const range: RangeQueryParamType = req.query.range
|
||||
? JSON.parse(req.query.range.toString())
|
||||
: [0, 10];
|
||||
const sort: SortQueryParamType = req.query.sort
|
||||
? JSON.parse(req.query.sort.toString())
|
||||
: ["id", "ASC"];
|
||||
|
||||
const users = await User.findAndCountAll({
|
||||
attributes: { exclude: ["password"] },
|
||||
include: Role,
|
||||
offset: range[0],
|
||||
limit: range[1] - range[0] + 1,
|
||||
order: [sort] as Order
|
||||
});
|
||||
|
||||
return res.json({ data: users.rows, total: users.count });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getMany = async (req: Request, res: Response<{ data: User[] }>, next: NextFunction) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: { ids: [] };
|
||||
|
||||
const users = await User.findAll({
|
||||
where: { id: filter.ids as number[] }
|
||||
});
|
||||
return res.json({ data: users });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
create = async (
|
||||
req: Request<object, object, IUserDto>,
|
||||
res: Response<User>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { nickname, avatar, email, password, role_id } = req.body;
|
||||
const user = await User.create({ nickname, avatar, email, password, role_id });
|
||||
return res.json(user);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
getById = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<User>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const user = await User.findByPk(id);
|
||||
if (!user) return next(ApiErrorHandler.notFound("Такого пользователя не найдено"));
|
||||
|
||||
return res.json(user);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
update = async (
|
||||
req: Request<{ id: number }, object, IUserDto>,
|
||||
res: Response<User>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { nickname, avatar, email, password, role_id } = req.body;
|
||||
|
||||
const user = await User.findByPk(id);
|
||||
if (!user) return next(ApiErrorHandler.internal("Такого пользователя не найдено"));
|
||||
|
||||
user.nickname = nickname;
|
||||
user.avatar = avatar;
|
||||
user.email = email;
|
||||
user.password = password;
|
||||
user.role_id = role_id;
|
||||
await user.save();
|
||||
|
||||
return res.json(user);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (
|
||||
req: Request<{ id: number }>,
|
||||
res: Response<User>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const user = await User.findByPk(id);
|
||||
if (!user) return next(ApiErrorHandler.internal("Такого пользователя не найдено"));
|
||||
|
||||
await user.destroy();
|
||||
|
||||
return res.json(user);
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
|
||||
deleteMany = async (
|
||||
req: Request,
|
||||
res: Response<{ data: User[] }>,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const filter: FilterQueryParamType = req.query.filter
|
||||
? JSON.parse(req.query.filter.toString())
|
||||
: {};
|
||||
const users = await User.findAll({ where: filter });
|
||||
|
||||
User.destroy({ where: filter });
|
||||
|
||||
return res.json({ data: users });
|
||||
} catch (error) {
|
||||
next(ApiErrorHandler.internal((error as Error).message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new UserController();
|
7
server/controllers/user/user.dto.ts
Normal file
7
server/controllers/user/user.dto.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface IUserDto {
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
password: string;
|
||||
role_id: number;
|
||||
}
|
19
server/db.ts
Normal file
19
server/db.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import dotenv from "dotenv";
|
||||
import { Sequelize } from "sequelize-typescript";
|
||||
import Role from "./models/role.model";
|
||||
import User from "./models/user.model";
|
||||
import Section from "./models/section.model";
|
||||
import Thread from "./models/thread.model";
|
||||
import Message from "./models/message.model";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const sequelize = new Sequelize({
|
||||
database: process.env.DB_NAME,
|
||||
dialect: "postgres",
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
models: [Role, User, Section, Thread, Message]
|
||||
});
|
21
server/error/api-error.handler.ts
Normal file
21
server/error/api-error.handler.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export class ApiErrorHandler extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super();
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
static notFound(message: string) {
|
||||
return new ApiErrorHandler(404, message);
|
||||
}
|
||||
|
||||
static internal(message: string) {
|
||||
return new ApiErrorHandler(500, `Ошибка сервера: ${message}`);
|
||||
}
|
||||
|
||||
static forbidden(message: string) {
|
||||
return new ApiErrorHandler(403, message);
|
||||
}
|
||||
}
|
10
server/global.d.ts
vendored
Normal file
10
server/global.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
PORT: number;
|
||||
DB_NAME: string;
|
||||
DB_USER: string;
|
||||
DB_PASSWORD: string;
|
||||
DB_HOST: string;
|
||||
DB_PORT: number;
|
||||
}
|
||||
}
|
3
server/helpers/get-random-number.helper.ts
Normal file
3
server/helpers/get-random-number.helper.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function getRandomNumber(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
28
server/index.ts
Normal file
28
server/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { sequelize } from "./db.js";
|
||||
import { router } from "./routes/routes.ts";
|
||||
import { ErrorHandlingMiddleware } from "./middleware/error-handling.middleware.ts";
|
||||
|
||||
const port = 5000;
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use("/api/", router);
|
||||
|
||||
app.use(ErrorHandlingMiddleware);
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
await sequelize.sync();
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is started on port ${port}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
10
server/middleware/error-handling.middleware.ts
Normal file
10
server/middleware/error-handling.middleware.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { NextFunction, Request, Response } from "express"
|
||||
import { ApiErrorHandler } from "../error/api-error.handler.ts"
|
||||
|
||||
export function ErrorHandlingMiddleware(err: Error, req: Request, res: Response, next: NextFunction) {
|
||||
if (err instanceof ApiErrorHandler) {
|
||||
return res.status(err.status).json({message: err.message})
|
||||
}
|
||||
|
||||
return res.status(500).json({message: "Непредвиденная ошибка"})
|
||||
}
|
53
server/models/message.model.ts
Normal file
53
server/models/message.model.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {
|
||||
Table,
|
||||
Column,
|
||||
Model,
|
||||
DataType,
|
||||
Length,
|
||||
ForeignKey,
|
||||
BelongsTo
|
||||
} from "sequelize-typescript";
|
||||
import User from "./user.model";
|
||||
import Thread from "./thread.model";
|
||||
|
||||
@Table({
|
||||
timestamps: false,
|
||||
tableName: "messages",
|
||||
modelName: "Message"
|
||||
})
|
||||
export default class Message extends Model {
|
||||
@Column({
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Length({ min: 1 })
|
||||
@Column({
|
||||
allowNull: false,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare text: string;
|
||||
|
||||
@ForeignKey(() => User)
|
||||
@Column({
|
||||
allowNull: false,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare user_id: number;
|
||||
|
||||
@BelongsTo(() => User)
|
||||
declare user: User;
|
||||
|
||||
@ForeignKey(() => Thread)
|
||||
@Column({
|
||||
allowNull: false,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare thread_id: number;
|
||||
|
||||
@BelongsTo(() => Thread)
|
||||
declare thread: Thread;
|
||||
}
|
27
server/models/role.model.ts
Normal file
27
server/models/role.model.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Table, Column, Model, DataType, Length, HasMany } from "sequelize-typescript";
|
||||
import User from "./user.model";
|
||||
|
||||
@Table({
|
||||
timestamps: false,
|
||||
tableName: "roles",
|
||||
modelName: "Role"
|
||||
})
|
||||
export default class Role extends Model {
|
||||
@Column({
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Length({ min: 1, max: 20 })
|
||||
@Column({
|
||||
allowNull: false,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare name: string;
|
||||
|
||||
@HasMany(() => User, "role_id")
|
||||
declare users: User[];
|
||||
}
|
55
server/models/section.model.ts
Normal file
55
server/models/section.model.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
Table,
|
||||
Column,
|
||||
Model,
|
||||
DataType,
|
||||
Length,
|
||||
UpdatedAt,
|
||||
CreatedAt,
|
||||
ForeignKey,
|
||||
BelongsTo,
|
||||
HasMany
|
||||
} from "sequelize-typescript";
|
||||
import Thread from "./thread.model";
|
||||
|
||||
@Table({
|
||||
tableName: "sections",
|
||||
modelName: "Section"
|
||||
})
|
||||
export default class Section extends Model {
|
||||
@Column({
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Length({ min: 1 })
|
||||
@Column({
|
||||
allowNull: false,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare name: string;
|
||||
|
||||
@CreatedAt
|
||||
declare created_at: Date;
|
||||
|
||||
@UpdatedAt
|
||||
declare updated_at: Date;
|
||||
|
||||
@ForeignKey(() => Section)
|
||||
@Column({
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare root_section_id: number | null;
|
||||
|
||||
@BelongsTo(() => Section)
|
||||
declare root_section: Section;
|
||||
|
||||
@HasMany(() => Section)
|
||||
declare inner_sections: Section[];
|
||||
|
||||
@HasMany(() => Thread)
|
||||
declare threads: Thread[];
|
||||
}
|
65
server/models/thread.model.ts
Normal file
65
server/models/thread.model.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
Table,
|
||||
Column,
|
||||
Model,
|
||||
DataType,
|
||||
Length,
|
||||
UpdatedAt,
|
||||
CreatedAt,
|
||||
ForeignKey,
|
||||
BelongsTo,
|
||||
HasMany
|
||||
} from "sequelize-typescript";
|
||||
import Section from "./section.model";
|
||||
import User from "./user.model";
|
||||
import Message from "./message.model";
|
||||
|
||||
@Table({
|
||||
tableName: "threads",
|
||||
modelName: "Thread"
|
||||
})
|
||||
export default class Thread extends Model {
|
||||
@Column({
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Length({ min: 1 })
|
||||
@Column({
|
||||
allowNull: false,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare name: string;
|
||||
|
||||
@CreatedAt
|
||||
declare created_at: Date;
|
||||
|
||||
@UpdatedAt
|
||||
declare updated_at: Date;
|
||||
|
||||
@ForeignKey(() => Section)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false
|
||||
})
|
||||
declare section_id: number;
|
||||
|
||||
@BelongsTo(() => Section)
|
||||
declare section: Section;
|
||||
|
||||
@ForeignKey(() => User)
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false
|
||||
})
|
||||
declare creator_id: number | null;
|
||||
|
||||
@BelongsTo(() => User)
|
||||
declare creator: User;
|
||||
|
||||
@HasMany(() => Message)
|
||||
declare messages: Message[];
|
||||
}
|
81
server/models/user.model.ts
Normal file
81
server/models/user.model.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
Table,
|
||||
Column,
|
||||
Model,
|
||||
DataType,
|
||||
IsEmail,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
ForeignKey,
|
||||
BelongsTo,
|
||||
HasMany,
|
||||
} from "sequelize-typescript";
|
||||
import Role from "./role.model";
|
||||
import Thread from "./thread.model";
|
||||
import Message from "./message.model";
|
||||
|
||||
@Table({
|
||||
timestamps: true,
|
||||
tableName: "users",
|
||||
modelName: "user"
|
||||
})
|
||||
export default class User extends Model {
|
||||
@Column({
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare id: number;
|
||||
|
||||
@Column({
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare nickname: string;
|
||||
|
||||
@Column({
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare avatar: string;
|
||||
|
||||
@IsEmail
|
||||
@Column({
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare email: string;
|
||||
|
||||
@Column({
|
||||
allowNull: false,
|
||||
defaultValue: "Unknown",
|
||||
type: DataType.STRING
|
||||
})
|
||||
declare password: string;
|
||||
|
||||
@CreatedAt
|
||||
declare created_at: Date;
|
||||
|
||||
@UpdatedAt
|
||||
declare updated_at: Date;
|
||||
|
||||
@ForeignKey(() => Role)
|
||||
@Column({
|
||||
allowNull: false,
|
||||
type: DataType.INTEGER
|
||||
})
|
||||
declare role_id: number;
|
||||
|
||||
@BelongsTo(() => Role)
|
||||
declare role: Role;
|
||||
|
||||
@HasMany(() => Thread)
|
||||
declare threads: Thread[];
|
||||
|
||||
@HasMany(() => Message)
|
||||
declare messages: Message[];
|
||||
}
|
5209
server/package-lock.json
generated
Normal file
5209
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
server/package.json
Normal file
39
server/package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"argon2": "^0.31.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"express": "^4.18.2",
|
||||
"pg": "^8.11.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"prettier": "^3.2.5",
|
||||
"sequelize": "^6.33.0",
|
||||
"sequelize-typescript": "^2.1.6",
|
||||
"tsx": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@types/node": "^20.11.30",
|
||||
"nodemon": "^3.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3"
|
||||
},
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"server": "tsx watch ./index.ts",
|
||||
"seed": "tsx seeders/seeder.ts"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"type": "module"
|
||||
}
|
14
server/routes/routes.ts
Normal file
14
server/routes/routes.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Router } from "express";
|
||||
import roleRouter from "./routes/role.router";
|
||||
import userRouter from "./routes/user.router";
|
||||
import sectionRouter from "./routes/section.router";
|
||||
import threadRouter from "./routes/thread.router";
|
||||
import messageRouter from "./routes/message.router";
|
||||
|
||||
export const router = Router();
|
||||
|
||||
router.use("/roles", roleRouter);
|
||||
router.use("/users", userRouter);
|
||||
router.use("/sections", sectionRouter);
|
||||
router.use("/threads", threadRouter);
|
||||
router.use("/messages", messageRouter);
|
14
server/routes/routes/message.router.ts
Normal file
14
server/routes/routes/message.router.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Router } from "express";
|
||||
import MessageController from "../../controllers/message/message.controller";
|
||||
|
||||
const messageRouter = Router();
|
||||
|
||||
messageRouter.post("/", MessageController.create);
|
||||
messageRouter.get("/", MessageController.getAll);
|
||||
messageRouter.get("/get-many", MessageController.getMany);
|
||||
messageRouter.get("/:id", MessageController.getById);
|
||||
messageRouter.put("/:id", MessageController.update);
|
||||
messageRouter.delete("/delete-many", MessageController.deleteMany);
|
||||
messageRouter.delete("/:id", MessageController.delete);
|
||||
|
||||
export default messageRouter;
|
14
server/routes/routes/role.router.ts
Normal file
14
server/routes/routes/role.router.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Router } from "express";
|
||||
import RoleController from "../../controllers/role/role.controller";
|
||||
|
||||
const roleRouter = Router();
|
||||
|
||||
roleRouter.post("/", RoleController.create);
|
||||
roleRouter.get("/", RoleController.getAll);
|
||||
roleRouter.get("/get-many", RoleController.getMany);
|
||||
roleRouter.get("/:id", RoleController.getById);
|
||||
roleRouter.put("/:id", RoleController.update);
|
||||
roleRouter.delete("/delete-many", RoleController.deleteMany);
|
||||
roleRouter.delete("/:id", RoleController.delete);
|
||||
|
||||
export default roleRouter;
|
15
server/routes/routes/section.router.ts
Normal file
15
server/routes/routes/section.router.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Router } from "express";
|
||||
import SectionController from "../../controllers/section/section.controller";
|
||||
|
||||
const sectionRouter = Router();
|
||||
|
||||
sectionRouter.post("/", SectionController.create);
|
||||
sectionRouter.get("/", SectionController.getAll);
|
||||
sectionRouter.get("/get-many", SectionController.getMany);
|
||||
sectionRouter.get("/get-filtered-list", SectionController.getFillteredList);
|
||||
sectionRouter.get("/:id", SectionController.getById);
|
||||
sectionRouter.put("/:id", SectionController.update);
|
||||
sectionRouter.delete("/delete-many", SectionController.deleteMany);
|
||||
sectionRouter.delete("/:id", SectionController.delete);
|
||||
|
||||
export default sectionRouter;
|
14
server/routes/routes/thread.router.ts
Normal file
14
server/routes/routes/thread.router.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Router } from "express";
|
||||
import ThreadController from "../../controllers/thread/thread.controller";
|
||||
|
||||
const threadRouter = Router();
|
||||
|
||||
threadRouter.post("/", ThreadController.create);
|
||||
threadRouter.get("/", ThreadController.getAll);
|
||||
threadRouter.get("/get-many", ThreadController.getMany);
|
||||
threadRouter.get("/:id", ThreadController.getById);
|
||||
threadRouter.put("/:id", ThreadController.update);
|
||||
threadRouter.delete("/delete-many", ThreadController.deleteMany);
|
||||
threadRouter.delete("/:id", ThreadController.delete);
|
||||
|
||||
export default threadRouter;
|
14
server/routes/routes/user.router.ts
Normal file
14
server/routes/routes/user.router.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Router } from "express";
|
||||
import UserController from "../../controllers/user/user.controller";
|
||||
|
||||
const userRouter = Router();
|
||||
|
||||
userRouter.post("/", UserController.create);
|
||||
userRouter.get("/", UserController.getAll);
|
||||
userRouter.get("/get-many", UserController.getMany);
|
||||
userRouter.get("/:id", UserController.getById);
|
||||
userRouter.put("/:id", UserController.update);
|
||||
userRouter.delete("/delete-many", UserController.deleteMany);
|
||||
userRouter.delete("/:id", UserController.delete);
|
||||
|
||||
export default userRouter;
|
17
server/seeders/seeder.ts
Normal file
17
server/seeders/seeder.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { seedMessages } from "./seeders/message.seeder";
|
||||
import { seedRoles } from "./seeders/role.seeder";
|
||||
import { seedSections } from "./seeders/section.seeder";
|
||||
import { seedThreads } from "./seeders/thread.seeder";
|
||||
import { seedUsers } from "./seeders/user.seeder";
|
||||
|
||||
const rolesLength = 100;
|
||||
const usersLength = 250;
|
||||
const sectionsLength = 1000;
|
||||
const threadsLength = 1000;
|
||||
const messagesLength = 1000;
|
||||
|
||||
// seedRoles(rolesLength);
|
||||
// seedUsers(usersLength, rolesLength);
|
||||
// seedSections(sectionsLength);
|
||||
// seedThreads(threadsLength, usersLength, sectionsLength);
|
||||
seedMessages(messagesLength, threadsLength, usersLength);
|
28
server/seeders/seeders/message.seeder.ts
Normal file
28
server/seeders/seeders/message.seeder.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import Message from "../../models/message.model";
|
||||
import { getRandomNumber } from "../../helpers/get-random-number.helper";
|
||||
import { sequelize } from "../../db";
|
||||
import type { IMessageDto } from "../../controllers/message/message.dto";
|
||||
|
||||
export async function seedMessages(
|
||||
length: number,
|
||||
threadsLength: number,
|
||||
usersLength: number
|
||||
) {
|
||||
if (length <= 0 || threadsLength <= 0 || usersLength <= 0) return;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < length; i++) {
|
||||
let startQueryString = `INSERT INTO messages (text, user_id, thread_id) VALUES `;
|
||||
const text = faker.lorem.words({ min: 3, max: 7 });
|
||||
const user_id = getRandomNumber(41, 45);
|
||||
const thread_id = getRandomNumber(870, 876);
|
||||
startQueryString += `('${text}', ${user_id}, ${thread_id})`;
|
||||
await sequelize.query(`${startQueryString};`);
|
||||
}
|
||||
|
||||
console.log("Messages seeded successfully!");
|
||||
} catch (error) {
|
||||
console.log("Error seeding messages: ", error);
|
||||
}
|
||||
}
|
19
server/seeders/seeders/role.seeder.ts
Normal file
19
server/seeders/seeders/role.seeder.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { sequelize } from "./../../db";
|
||||
|
||||
export async function seedRoles(length: number) {
|
||||
if (length <= 0) return;
|
||||
|
||||
try {
|
||||
let startQueryString = `INSERT INTO roles (name) VALUES `;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (i !== length - 1) startQueryString += `('${faker.person.jobType()}'), `;
|
||||
else startQueryString += `('${faker.person.jobType()}')`;
|
||||
}
|
||||
|
||||
await sequelize.query(`${startQueryString};`);
|
||||
console.log("Roles seeded successfully!");
|
||||
} catch (error) {
|
||||
console.log("Error seeding roles: ", error);
|
||||
}
|
||||
}
|
27
server/seeders/seeders/section.seeder.ts
Normal file
27
server/seeders/seeders/section.seeder.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { getRandomNumber } from "../../helpers/get-random-number.helper";
|
||||
import { sequelize } from "../../db";
|
||||
|
||||
export async function seedSections(length: number) {
|
||||
if (length <= 0) return;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let startQueryString = `INSERT INTO sections (name, root_section_id) VALUES `;
|
||||
for (let j = 0; j < length / 10; j++) {
|
||||
const name = faker.lorem.words({ min: 3, max: 7 });
|
||||
const root_section_id =
|
||||
j * i < length / 2 ? "NULL" : getRandomNumber(1, length / 2);
|
||||
|
||||
if (j !== length / 10 - 1)
|
||||
startQueryString += `('${name}', ${root_section_id}), `;
|
||||
else startQueryString += `('${name}', ${root_section_id})`;
|
||||
}
|
||||
|
||||
await sequelize.query(`${startQueryString};`);
|
||||
}
|
||||
console.log("Sections seeded successfully!");
|
||||
} catch (error) {
|
||||
console.log("Error seeding sections: ", error);
|
||||
}
|
||||
}
|
27
server/seeders/seeders/thread.seeder.ts
Normal file
27
server/seeders/seeders/thread.seeder.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import Thread from "../../models/thread.model";
|
||||
import { getRandomNumber } from "../../helpers/get-random-number.helper";
|
||||
import type { IThreadDto } from "../../controllers/thread/thread.dto";
|
||||
import { sequelize } from "../../db";
|
||||
|
||||
export async function seedThreads(
|
||||
length: number,
|
||||
usersLength: number,
|
||||
allSectionsLength: number
|
||||
) {
|
||||
if (length <= 0 || usersLength <= 0 || allSectionsLength <= 0) return;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < length; i++) {
|
||||
let startQueryString = `INSERT INTO threads (name, creator_id, section_id) VALUES `;
|
||||
const name = faker.lorem.words({ min: 3, max: 7 });
|
||||
const creator_id = getRandomNumber(41, 45);
|
||||
const section_id = getRandomNumber(allSectionsLength / 2 + 1, allSectionsLength);
|
||||
startQueryString += `('${name}', ${creator_id}, ${section_id})`;
|
||||
await sequelize.query(`${startQueryString};`);
|
||||
}
|
||||
console.log("Threads seeded successfully!");
|
||||
} catch (error) {
|
||||
console.log("Error seeding threads: ", error);
|
||||
}
|
||||
}
|
31
server/seeders/seeders/user.seeder.ts
Normal file
31
server/seeders/seeders/user.seeder.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { getRandomNumber } from "../../helpers/get-random-number.helper";
|
||||
import { sequelize } from "../../db";
|
||||
|
||||
export async function seedUsers(length: number, rolesLength: number) {
|
||||
if (length <= 0 || rolesLength <= 0) return;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let startQueryString = `INSERT INTO users (nickname, avatar, email, password, role_id) VALUES `;
|
||||
for (let j = 0; j < length / 10; j++) {
|
||||
const nickname =
|
||||
faker.person.firstName() + faker.person.jobArea() + faker.person.jobTitle();
|
||||
const avatar = faker.system.filePath();
|
||||
const email = faker.internet.email();
|
||||
const password = faker.internet.password();
|
||||
const role_id = getRandomNumber(1, rolesLength);
|
||||
if (j !== length / 10 - 1)
|
||||
startQueryString += `('${nickname}', '${avatar}', '${email}', '${password}', '${role_id}'), `;
|
||||
else
|
||||
startQueryString += `('${nickname}', '${avatar}', '${email}', '${password}', '${role_id}')`;
|
||||
}
|
||||
|
||||
await sequelize.query(`${startQueryString};`);
|
||||
}
|
||||
|
||||
console.log("Users seeded successfully!");
|
||||
} catch (error) {
|
||||
console.log("Error seeding users: ", error);
|
||||
}
|
||||
}
|
109
server/tsconfig.json
Normal file
109
server/tsconfig.json
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
"useDefineForClassFields": false, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
"allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
"noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user