merge branch feature/cache-manager-admin
This commit is contained in:
parent
bd7cf3284b
commit
93d16cf35c
@ -45,6 +45,7 @@
|
||||
"fastify": "^4.26.1",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "^3.0.0",
|
||||
"shared": "workspace:*",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "29.1.1",
|
||||
|
||||
@ -4,14 +4,18 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import {
|
||||
All,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Query,
|
||||
Req,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import type { Cache } from 'cache-manager';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { QueryItem } from 'shared/types/cache';
|
||||
import { env } from 'src/config/env';
|
||||
|
||||
type RedisStore = Omit<Cache, 'set'> & {
|
||||
@ -23,6 +27,7 @@ export class ProxyController {
|
||||
constructor(
|
||||
@Inject(CACHE_MANAGER) private readonly cacheManager: RedisStore,
|
||||
) {}
|
||||
|
||||
@All('/graphql')
|
||||
public async graphql(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
|
||||
const { operationName, query, variables } = req.body as GQLRequest;
|
||||
@ -56,4 +61,67 @@ export class ProxyController {
|
||||
|
||||
return reply.send(data);
|
||||
}
|
||||
|
||||
@Get('/get-queries')
|
||||
public async getQueriesList(@Res() reply: FastifyReply) {
|
||||
const res = await this.getAllQueries();
|
||||
|
||||
return reply.send(res);
|
||||
}
|
||||
|
||||
private async getAllQueries() {
|
||||
const list = await this.cacheManager.store.keys('*');
|
||||
|
||||
return (Object.keys(queryTTL) as Array<keyof typeof queryTTL>).reduce(
|
||||
(acc, queryName) => {
|
||||
const queries = list.filter((x) => x.split(' ').at(0) === queryName);
|
||||
if (queries.length) {
|
||||
const ttl = queryTTL[queryName];
|
||||
acc[queryName] = { queries, ttl };
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, QueryItem>,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('/delete-query')
|
||||
public async deleteQuery(
|
||||
@Query('queryKey') queryKey: string,
|
||||
@Res() reply: FastifyReply,
|
||||
) {
|
||||
try {
|
||||
await this.cacheManager.del(queryKey);
|
||||
|
||||
return reply.send('ok');
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete('/reset')
|
||||
public async reset(@Res() reply: FastifyReply) {
|
||||
try {
|
||||
await this.cacheManager.reset();
|
||||
|
||||
return reply.send('ok');
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/get-query')
|
||||
public async getQueryValue(
|
||||
@Query('queryKey') queryKey: string,
|
||||
@Res() reply: FastifyReply,
|
||||
) {
|
||||
try {
|
||||
const value = await this.cacheManager.get(queryKey);
|
||||
|
||||
return reply.send(value);
|
||||
} catch (error) {
|
||||
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
apps/web/Components/Admin/Cache/Query.tsx
Normal file
77
apps/web/Components/Admin/Cache/Query.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import * as cacheApi from '@/api/cache/query';
|
||||
import { min } from '@/styles/mq';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { memo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Collapse } from 'ui/elements';
|
||||
import { Flex } from 'ui/grid';
|
||||
|
||||
type QueryProps = {
|
||||
readonly onDeleteQuery: () => Promise<void>;
|
||||
readonly queryKey: string;
|
||||
};
|
||||
|
||||
const StyledPre = styled.pre`
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
${min('desktop')} {
|
||||
max-height: 800px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Query = memo(({ onDeleteQuery, queryKey }: QueryProps) => {
|
||||
const { data, refetch } = useQuery({
|
||||
enabled: false,
|
||||
queryFn: ({ signal }) => signal && cacheApi.getQueryValue(queryKey, { signal }),
|
||||
queryKey: ['admin', 'cache', 'query', queryKey],
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const [activeKey, setActiveKey] = useState<string | undefined>(undefined);
|
||||
const [deletePending, setDeletePending] = useState(false);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<StyledPre>{JSON.stringify(data, null, 2)}</StyledPre>
|
||||
<Flex justifyContent="flex-end">
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
disabled={deletePending}
|
||||
onClick={() => {
|
||||
setDeletePending(true);
|
||||
onDeleteQuery().finally(() => {
|
||||
setDeletePending(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Удалить
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
bordered={false}
|
||||
activeKey={activeKey}
|
||||
items={[
|
||||
{
|
||||
children: data ? content : 'Загрузка...',
|
||||
key: queryKey,
|
||||
label: queryKey,
|
||||
},
|
||||
]}
|
||||
onChange={() => {
|
||||
if (activeKey) {
|
||||
setActiveKey(undefined);
|
||||
} else {
|
||||
setActiveKey(queryKey);
|
||||
|
||||
refetch();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
25
apps/web/Components/Admin/Cache/QueryList.tsx
Normal file
25
apps/web/Components/Admin/Cache/QueryList.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { Query } from './Query';
|
||||
import * as cacheApi from '@/api/cache/query';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import type { QueryItem } from 'shared/types/cache';
|
||||
|
||||
type QueryListProps = QueryItem;
|
||||
|
||||
export const QueryList = memo(({ queries }: QueryListProps) => {
|
||||
const [deletedQueries, setDeletedQueries] = useState<QueryItem['queries']>([]);
|
||||
|
||||
const activeQueries = useMemo(
|
||||
() => queries.filter((queryKey) => !deletedQueries.includes(queryKey)),
|
||||
[deletedQueries, queries]
|
||||
);
|
||||
|
||||
function handleDeleteQuery(queryKey: string) {
|
||||
return cacheApi
|
||||
.deleteQuery(queryKey)
|
||||
.then(() => setDeletedQueries([...deletedQueries, queryKey]));
|
||||
}
|
||||
|
||||
return activeQueries.map((queryKey) => (
|
||||
<Query key={queryKey} queryKey={queryKey} onDeleteQuery={() => handleDeleteQuery(queryKey)} />
|
||||
));
|
||||
});
|
||||
24
apps/web/Components/Admin/Cache/ReloadButton.tsx
Normal file
24
apps/web/Components/Admin/Cache/ReloadButton.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from 'ui/elements';
|
||||
import { ReloadOutlined } from 'ui/elements/icons';
|
||||
|
||||
export function ReloadButton({ onClick }: { readonly onClick: () => Promise<unknown> }) {
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
return (
|
||||
<Button
|
||||
loading={pending}
|
||||
onClick={() => {
|
||||
setPending(true);
|
||||
onClick().finally(() => {
|
||||
setTimeout(() => {
|
||||
setPending(false);
|
||||
}, 1000);
|
||||
});
|
||||
}}
|
||||
icon={<ReloadOutlined rev="" />}
|
||||
>
|
||||
Обновить
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
58
apps/web/Components/Admin/Cache/index.tsx
Normal file
58
apps/web/Components/Admin/Cache/index.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import Background from '../../Layout/Background';
|
||||
import { useFilteredQueries } from './lib/hooks';
|
||||
import { QueryList } from './QueryList';
|
||||
import { ReloadButton } from './ReloadButton';
|
||||
import { min } from '@/styles/mq';
|
||||
import styled from 'styled-components';
|
||||
import { Collapse, Divider, Input } from 'ui/elements';
|
||||
|
||||
const Wrapper = styled(Background)`
|
||||
padding: 4px 6px;
|
||||
width: 100vw;
|
||||
|
||||
${min('tablet')} {
|
||||
min-height: 790px;
|
||||
}
|
||||
|
||||
${min('laptop')} {
|
||||
padding: 4px 18px 10px;
|
||||
width: 1280px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
export function Cache() {
|
||||
const { filteredQueries, refetch, setFilterString } = useFilteredQueries();
|
||||
|
||||
if (!filteredQueries) {
|
||||
return <div>Загрузка...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Divider>Управление кэшем</Divider>
|
||||
<Flex>
|
||||
<Input
|
||||
placeholder="Поиск по запросу"
|
||||
allowClear
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
/>
|
||||
<ReloadButton onClick={refetch} />
|
||||
</Flex>
|
||||
<Collapse
|
||||
accordion
|
||||
items={Object.keys(filteredQueries).map((queryGroupName) => ({
|
||||
children: <QueryList {...filteredQueries[queryGroupName]} />,
|
||||
key: queryGroupName,
|
||||
label: queryGroupName,
|
||||
}))}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
30
apps/web/Components/Admin/Cache/lib/hooks.tsx
Normal file
30
apps/web/Components/Admin/Cache/lib/hooks.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { filterQueries } from './utils';
|
||||
import * as cacheApi from '@/api/cache/query';
|
||||
import type { ResponseQueries } from '@/api/cache/types';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
export function useFilteredQueries() {
|
||||
const { data: queries, refetch } = useQuery({
|
||||
enabled: false,
|
||||
queryFn: ({ signal }) => signal && cacheApi.getQueries({ signal }),
|
||||
queryKey: ['admin', 'cache', 'queries'],
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const [filteredQueries, setFilteredQueries] = useState<ResponseQueries | undefined>(queries);
|
||||
const [filterString, setFilterString] = useState('');
|
||||
const [debouncedFilterString] = useDebounce(filterString, 350);
|
||||
|
||||
useEffect(() => {
|
||||
if (!debouncedFilterString) {
|
||||
setFilteredQueries(queries);
|
||||
}
|
||||
if (queries && debouncedFilterString) {
|
||||
setFilteredQueries(filterQueries(queries, debouncedFilterString));
|
||||
}
|
||||
}, [debouncedFilterString, queries]);
|
||||
|
||||
return { filteredQueries, queries, refetch, setFilterString };
|
||||
}
|
||||
23
apps/web/Components/Admin/Cache/lib/utils.tsx
Normal file
23
apps/web/Components/Admin/Cache/lib/utils.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import type { ResponseQueries } from '@/api/cache/types';
|
||||
|
||||
export function filterQueries(queriesObj: ResponseQueries, searchStr: string): ResponseQueries {
|
||||
const filteredObj: ResponseQueries = {};
|
||||
|
||||
for (const key in queriesObj) {
|
||||
if (key.includes(searchStr)) {
|
||||
filteredObj[key] = queriesObj[key];
|
||||
} else {
|
||||
const queries: string[] = [];
|
||||
queriesObj[key].queries.forEach((queryKey) => {
|
||||
if (queryKey.toLowerCase().includes(searchStr.toLowerCase())) {
|
||||
queries.push(queryKey);
|
||||
}
|
||||
});
|
||||
if (queries.length) {
|
||||
filteredObj[key] = { ...queriesObj[key], queries };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredObj;
|
||||
}
|
||||
17
apps/web/Components/Admin/Layout.tsx
Normal file
17
apps/web/Components/Admin/Layout.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { min } from '@/styles/mq';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${min('laptop')} {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export function Layout({ children }: PropsWithChildren) {
|
||||
return <Flex>{children}</Flex>;
|
||||
}
|
||||
2
apps/web/Components/Admin/index.ts
Normal file
2
apps/web/Components/Admin/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Cache';
|
||||
export * from './Layout';
|
||||
@ -44,13 +44,13 @@ const ComponentWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
function Form({ prune }) {
|
||||
export function Form({ prune }) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Tabs type="card" tabBarGutter="5px">
|
||||
{formTabs
|
||||
.filter((tab) => !prune?.includes(tab.id))
|
||||
.map(({ id, title, Component }) => (
|
||||
.map(({ Component, id, title }) => (
|
||||
<Tabs.TabPane tab={title} key={id}>
|
||||
<ComponentWrapper>
|
||||
<Component />
|
||||
@ -61,5 +61,3 @@ function Form({ prune }) {
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default Form;
|
||||
|
||||
@ -2,7 +2,7 @@ import { min } from '@/styles/mq';
|
||||
import styled from 'styled-components';
|
||||
import { Box } from 'ui/grid';
|
||||
|
||||
export const Grid = styled(Box)`
|
||||
export const Layout = styled(Box)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
@ -17,8 +17,4 @@ export const Grid = styled(Box)`
|
||||
grid-template-columns: 2fr 1fr 1.5fr;
|
||||
/* margin: 8px 5%; */
|
||||
}
|
||||
|
||||
${min('desktop-xl')} {
|
||||
margin: 8px 10% !important;
|
||||
}
|
||||
`;
|
||||
@ -22,7 +22,7 @@ const Wrapper = styled.div`
|
||||
`;
|
||||
|
||||
const Results = observer(() => {
|
||||
const { $results, $process } = useStore();
|
||||
const { $process, $results } = useStore();
|
||||
|
||||
const resultsValues = toJS($results.values);
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
@ -41,7 +41,7 @@ function getElementsErrors({ $calculation, $process }) {
|
||||
});
|
||||
}
|
||||
|
||||
function getPaymentsTableErrors({ $tables, $process }) {
|
||||
function getPaymentsTableErrors({ $process, $tables }) {
|
||||
const { payments } = $tables;
|
||||
const errors = payments.validation.getErrors();
|
||||
const title = payments.validation.params.err_title;
|
||||
@ -58,7 +58,7 @@ function getPaymentsTableErrors({ $tables, $process }) {
|
||||
));
|
||||
}
|
||||
|
||||
function getInsuranceTableErrors({ $tables, $process }) {
|
||||
function getInsuranceTableErrors({ $process, $tables }) {
|
||||
const { insurance } = $tables;
|
||||
const errors = insurance.validation.getErrors();
|
||||
const title = insurance.validation.params.err_title;
|
||||
@ -75,7 +75,7 @@ function getInsuranceTableErrors({ $tables, $process }) {
|
||||
));
|
||||
}
|
||||
|
||||
function getFingapTableErrors({ $tables, $process }) {
|
||||
function getFingapTableErrors({ $process, $tables }) {
|
||||
const { fingap } = $tables;
|
||||
const errors = fingap.validation.getErrors();
|
||||
const title = fingap.validation.params.err_title;
|
||||
@ -42,7 +42,7 @@ const Wrapper = styled(Background)`
|
||||
}
|
||||
`;
|
||||
|
||||
const Output = observer(() => {
|
||||
export const Output = observer(() => {
|
||||
const { $results } = useStore();
|
||||
const [activeKey, setActiveKey] = useState(undefined);
|
||||
const { hasErrors } = useErrors();
|
||||
@ -69,5 +69,3 @@ const Output = observer(() => {
|
||||
</Wrapper>
|
||||
);
|
||||
});
|
||||
|
||||
export default Output;
|
||||
@ -17,7 +17,7 @@ const Wrapper = styled(Background)`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Settings() {
|
||||
export function Settings() {
|
||||
const { $process } = useStore();
|
||||
|
||||
const mainRows = $process.has('Unlimited')
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
export { default as Form } from './Form';
|
||||
export { default as Settings } from './Settings';
|
||||
4
apps/web/Components/Calculation/index.ts
Normal file
4
apps/web/Components/Calculation/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './Form';
|
||||
export * from './Layout';
|
||||
export * from './Output';
|
||||
export * from './Settings';
|
||||
@ -45,13 +45,7 @@ const Logout = styled.a`
|
||||
|
||||
function Auth() {
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
alignItems="flex-end"
|
||||
alignSelf={['flex-start']}
|
||||
justifyContent="space-between"
|
||||
height="100%"
|
||||
>
|
||||
<Flex flexDirection="column" alignItems="flex-end" justifyContent="flex-start" height="100%">
|
||||
<User />
|
||||
<Logout href="/logout">Выход</Logout>
|
||||
</Flex>
|
||||
|
||||
@ -7,6 +7,7 @@ import { Flex } from 'ui/grid';
|
||||
const HeaderContent = styled(Flex)`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 14px 12px;
|
||||
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
@ -15,9 +16,7 @@ const HeaderContent = styled(Flex)`
|
||||
var(--color-tertiarty) 100%
|
||||
);
|
||||
|
||||
padding: 14px 12px;
|
||||
${min('laptop')} {
|
||||
padding: 14px 12px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -5,6 +5,7 @@ import getColors from '@/styles/colors';
|
||||
import { min } from '@/styles/mq';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import logo from 'public/assets/images/logo-primary.svg';
|
||||
import styled from 'styled-components';
|
||||
import { Tag } from 'ui/elements';
|
||||
@ -33,8 +34,9 @@ const LogoText = styled.h3`
|
||||
const TagWrapper = styled.div`
|
||||
font-family: 'Montserrat';
|
||||
font-weight: 500;
|
||||
* {
|
||||
font-size: 0.7rem;
|
||||
|
||||
${min('tablet')} {
|
||||
margin: 0 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -42,12 +44,11 @@ const { COLOR_PRIMARY } = getColors();
|
||||
|
||||
const UnlimitedTag = observer(() => {
|
||||
const { $process } = useStore();
|
||||
|
||||
if ($process.has('Unlimited')) {
|
||||
return (
|
||||
<TagWrapper>
|
||||
<Tag color={COLOR_PRIMARY} style={{ margin: '0 5px' }}>
|
||||
без ограничений
|
||||
</Tag>
|
||||
<Tag color={COLOR_PRIMARY}>без ограничений</Tag>
|
||||
</TagWrapper>
|
||||
);
|
||||
}
|
||||
@ -59,9 +60,11 @@ function Logo() {
|
||||
return (
|
||||
<Flex flexDirection="column" alignItems="flex-start" justifyContent="space-between">
|
||||
<ImageWrapper>
|
||||
<Link href="/">
|
||||
<Image priority className={styles.logo} alt="logo" src={logo} layout="responsive" />
|
||||
</Link>
|
||||
</ImageWrapper>
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Flex flexDirection={['column', 'row']} alignItems={[undefined, 'center']}>
|
||||
<LogoText>Лизинговый Калькулятор</LogoText>
|
||||
<UnlimitedTag />
|
||||
</Flex>
|
||||
|
||||
48
apps/web/Components/Layout/Navigation.tsx
Normal file
48
apps/web/Components/Layout/Navigation.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import Background from './Background';
|
||||
import type { MenuProps } from 'antd';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Menu } from 'ui/elements';
|
||||
import { AppstoreOutlined, SettingOutlined } from 'ui/elements/icons';
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
// icon: <HomeOutlined />,
|
||||
key: '/',
|
||||
label: <Link href="/">Главная</Link>,
|
||||
},
|
||||
{
|
||||
// icon: <PlusSquareOutlined />,
|
||||
key: '/unlimited',
|
||||
label: <Link href="/unlimited">Без ограничений</Link>,
|
||||
},
|
||||
],
|
||||
icon: <AppstoreOutlined />,
|
||||
key: 'home',
|
||||
label: 'Приложение',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
// icon: <DatabaseOutlined />,
|
||||
key: '/admin/cache',
|
||||
label: <Link href="/admin/cache">Управление кэшем</Link>,
|
||||
},
|
||||
],
|
||||
icon: <SettingOutlined />,
|
||||
key: 'admin',
|
||||
label: 'Панель управления',
|
||||
},
|
||||
];
|
||||
|
||||
export function AppNavigation() {
|
||||
const { pathname } = useRouter();
|
||||
|
||||
return (
|
||||
<Background>
|
||||
<Menu selectedKeys={[pathname]} mode="horizontal" items={items} />
|
||||
</Background>
|
||||
);
|
||||
}
|
||||
@ -1,11 +1,22 @@
|
||||
import Header from './Header';
|
||||
import { Flex } from 'ui/grid';
|
||||
import { AppNavigation } from './Navigation';
|
||||
import { min } from '@/styles/mq';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const Main = styled.main`
|
||||
margin: 8px 0;
|
||||
|
||||
${min('desktop-xl')} {
|
||||
margin: 8px 10%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Layout({ children, user }) {
|
||||
return (
|
||||
<Flex flexDirection="column">
|
||||
<>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
</Flex>
|
||||
{user?.admin ? <AppNavigation /> : false}
|
||||
<Main>{children}</Main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
42
apps/web/api/cache/query.ts
vendored
Normal file
42
apps/web/api/cache/query.ts
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
import type { ResponseQueries } from './types';
|
||||
import getUrls from '@/config/urls';
|
||||
import { withHandleError } from '@/utils/axios';
|
||||
import axios from 'axios';
|
||||
|
||||
const {
|
||||
URL_CACHE_GET_QUERIES,
|
||||
URL_CACHE_DELETE_QUERY,
|
||||
URL_CACHE_RESET_QUERIES,
|
||||
URL_CACHE_GET_QUERY,
|
||||
} = getUrls();
|
||||
|
||||
export function getQueries({ signal }: { signal: AbortSignal }) {
|
||||
return withHandleError(axios.get<ResponseQueries>(URL_CACHE_GET_QUERIES, { signal })).then(
|
||||
({ data }) => data
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteQuery(queryKey: string) {
|
||||
return withHandleError(
|
||||
axios.delete(URL_CACHE_DELETE_QUERY, {
|
||||
params: {
|
||||
queryKey,
|
||||
},
|
||||
})
|
||||
).then(({ data }) => data);
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
return withHandleError(axios.delete(URL_CACHE_RESET_QUERIES)).then(({ data }) => data);
|
||||
}
|
||||
|
||||
export function getQueryValue(queryKey: string, { signal }: { signal: AbortSignal }) {
|
||||
return withHandleError(
|
||||
axios.get<object>(URL_CACHE_GET_QUERY, {
|
||||
params: {
|
||||
queryKey,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
).then(({ data }) => data);
|
||||
}
|
||||
3
apps/web/api/cache/types.ts
vendored
Normal file
3
apps/web/api/cache/types.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import type { QueryItem } from 'shared/types/cache';
|
||||
|
||||
export type ResponseQueries = Record<string, QueryItem>;
|
||||
@ -7,12 +7,16 @@ const envSchema = z.object({
|
||||
SENTRY_DSN: z.string(),
|
||||
SENTRY_ENVIRONMENT: z.string(),
|
||||
URL_1C_TRANSTAX_DIRECT: z.string(),
|
||||
URL_CACHE_DELETE_QUERY_DIRECT: z.string().default('http://api:3001/proxy/delete-query'),
|
||||
URL_CACHE_GET_QUERIES_DIRECT: z.string().default('http://api:3001/proxy/get-queries'),
|
||||
URL_CACHE_GET_QUERY_DIRECT: z.string().default('http://api:3001/proxy/get-query'),
|
||||
URL_CACHE_RESET_QUERIES_DIRECT: z.string().default('http://api:3001/proxy/reset'),
|
||||
URL_CORE_CALCULATE_DIRECT: z.string(),
|
||||
URL_CORE_FINGAP_DIRECT: z.string(),
|
||||
URL_CRM_CREATEKP_DIRECT: z.string(),
|
||||
URL_CRM_DOWNLOADKP_BASE: z.string(),
|
||||
URL_CRM_GRAPHQL_DIRECT: z.string(),
|
||||
URL_CRM_GRAPHQL_PROXY: z.string(),
|
||||
URL_CRM_GRAPHQL_PROXY: z.string().default('http://api:3001/proxy/graphql'),
|
||||
URL_ELT_KASKO_DIRECT: z.string(),
|
||||
URL_ELT_OSAGO_DIRECT: z.string(),
|
||||
URL_GET_USER_DIRECT: z.string(),
|
||||
|
||||
@ -13,6 +13,10 @@ const serverRuntimeConfigSchema = envSchema.pick({
|
||||
SENTRY_DSN: true,
|
||||
SENTRY_ENVIRONMENT: true,
|
||||
URL_1C_TRANSTAX_DIRECT: true,
|
||||
URL_CACHE_DELETE_QUERY_DIRECT: true,
|
||||
URL_CACHE_GET_QUERIES_DIRECT: true,
|
||||
URL_CACHE_GET_QUERY_DIRECT: true,
|
||||
URL_CACHE_RESET_QUERIES_DIRECT: true,
|
||||
URL_CORE_CALCULATE_DIRECT: true,
|
||||
URL_CORE_FINGAP_DIRECT: true,
|
||||
URL_CRM_CREATEKP_DIRECT: true,
|
||||
|
||||
@ -22,6 +22,10 @@ function getUrls() {
|
||||
URL_ELT_KASKO_DIRECT,
|
||||
URL_ELT_OSAGO_DIRECT,
|
||||
URL_CRM_GRAPHQL_PROXY,
|
||||
URL_CACHE_GET_QUERIES_DIRECT,
|
||||
URL_CACHE_DELETE_QUERY_DIRECT,
|
||||
URL_CACHE_RESET_QUERIES_DIRECT,
|
||||
URL_CACHE_GET_QUERY_DIRECT,
|
||||
} = serverRuntimeConfigSchema.parse(serverRuntimeConfig);
|
||||
|
||||
return {
|
||||
@ -29,6 +33,10 @@ function getUrls() {
|
||||
PORT,
|
||||
SENTRY_DSN,
|
||||
URL_1C_TRANSTAX: URL_1C_TRANSTAX_DIRECT,
|
||||
URL_CACHE_DELETE_QUERY: URL_CACHE_DELETE_QUERY_DIRECT,
|
||||
URL_CACHE_GET_QUERIES: URL_CACHE_GET_QUERIES_DIRECT,
|
||||
URL_CACHE_GET_QUERY: URL_CACHE_GET_QUERY_DIRECT,
|
||||
URL_CACHE_RESET_QUERIES: URL_CACHE_RESET_QUERIES_DIRECT,
|
||||
URL_CORE_CALCULATE: URL_CORE_CALCULATE_DIRECT,
|
||||
URL_CORE_FINGAP: URL_CORE_FINGAP_DIRECT,
|
||||
URL_CRM_CREATEKP: URL_CRM_CREATEKP_DIRECT,
|
||||
@ -44,6 +52,10 @@ function getUrls() {
|
||||
BASE_PATH,
|
||||
SENTRY_DSN,
|
||||
URL_1C_TRANSTAX: withBasePath(urls.URL_1C_TRANSTAX_PROXY),
|
||||
URL_CACHE_DELETE_QUERY: withBasePath(urls.URL_CACHE_DELETE_QUERY_PROXY),
|
||||
URL_CACHE_GET_QUERIES: withBasePath(urls.URL_CACHE_GET_QUERIES_PROXY),
|
||||
URL_CACHE_GET_QUERY: withBasePath(urls.URL_CACHE_GET_QUERY_PROXY),
|
||||
URL_CACHE_RESET_QUERIES: withBasePath(urls.URL_CACHE_RESET_QUERIES_PROXY),
|
||||
URL_CORE_CALCULATE: withBasePath(urls.URL_CORE_CALCULATE_PROXY),
|
||||
URL_CORE_FINGAP: withBasePath(urls.URL_CORE_FINGAP_PROXY),
|
||||
URL_CRM_CREATEKP: withBasePath(urls.URL_CRM_CREATEKP_PROXY),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export const unlimitedRoles = ['Калькулятор без ограничений'];
|
||||
export const defaultRoles = ['Лизинговый калькулятор', 'МПЛ', 'Управляющий подразделением'];
|
||||
export const adminRoles = ['Калькулятор без ограничений', 'Системный администратор'];
|
||||
export const usersSuper = ['akalinina', 'vchikalkin'];
|
||||
|
||||
3
apps/web/constants/page.js
Normal file
3
apps/web/constants/page.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const PAGE_TITLE = 'Лизинговый калькулятор - Эволюция';
|
||||
export const PAGE_DESCRIPTION =
|
||||
'Лизинговый калькулятор - Эволюция - Расчет лизинговых платежей - Создание КП';
|
||||
@ -1,5 +1,9 @@
|
||||
module.exports = {
|
||||
URL_1C_TRANSTAX_PROXY: '/api/1c/transtax',
|
||||
URL_CACHE_DELETE_QUERY_PROXY: '/api/admin/cache/delete',
|
||||
URL_CACHE_GET_QUERIES_PROXY: '/api/admin/cache/get-queries',
|
||||
URL_CACHE_GET_QUERY_PROXY: '/api/admin/cache/get-query',
|
||||
URL_CACHE_RESET_QUERIES_PROXY: '/api/admin/cache/reset',
|
||||
URL_CORE_CALCULATE_PROXY: '/api/core/calculate',
|
||||
URL_CORE_FINGAP_PROXY: '/api/core/fingap',
|
||||
URL_CRM_CREATEKP_PROXY: '/api/crm/create-kp',
|
||||
|
||||
@ -36,6 +36,17 @@ module.exports = withSentryConfig(
|
||||
output: 'standalone',
|
||||
publicRuntimeConfig: publicRuntimeConfigSchema.parse(env),
|
||||
reactStrictMode: true,
|
||||
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/admin',
|
||||
destination: '/admin/cache',
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
@ -66,6 +77,22 @@ module.exports = withSentryConfig(
|
||||
destination: env.URL_ELT_OSAGO_DIRECT,
|
||||
source: urls.URL_ELT_OSAGO_PROXY,
|
||||
},
|
||||
{
|
||||
destination: env.URL_CACHE_GET_QUERIES_DIRECT,
|
||||
source: urls.URL_CACHE_GET_QUERIES_PROXY,
|
||||
},
|
||||
{
|
||||
destination: env.URL_CACHE_DELETE_QUERY_DIRECT + '/:path*',
|
||||
source: urls.URL_CACHE_DELETE_QUERY_PROXY + '/:path*',
|
||||
},
|
||||
{
|
||||
destination: env.URL_CACHE_RESET_QUERIES_DIRECT,
|
||||
source: urls.URL_CACHE_RESET_QUERIES_PROXY,
|
||||
},
|
||||
{
|
||||
destination: env.URL_CACHE_GET_QUERY_DIRECT,
|
||||
source: urls.URL_CACHE_GET_QUERY_PROXY,
|
||||
},
|
||||
...favicons.map((fileName) => buildFaviconRewrite(`/${fileName}`)),
|
||||
];
|
||||
},
|
||||
|
||||
@ -58,6 +58,7 @@
|
||||
"jest": "^29.4.3",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"msw": "^1.1.0",
|
||||
"shared": "workspace:*",
|
||||
"ts-jest": "^29.0.5",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
|
||||
@ -52,7 +52,7 @@ function App({ Component, pageProps }) {
|
||||
}}
|
||||
>
|
||||
<Notification>
|
||||
<Layout>
|
||||
<Layout {...pageProps}>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</Notification>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
|
||||
import { metaFavicon } from '@/config/meta';
|
||||
import { withBasePath } from '@/config/urls';
|
||||
import { PAGE_DESCRIPTION } from '@/constants/page';
|
||||
import Document, { Head, Html, Main, NextScript } from 'next/document';
|
||||
import { ServerStyleSheet } from 'styled-components';
|
||||
import { createCache, doExtraStyle, StyleProvider } from 'ui/tools';
|
||||
@ -43,7 +45,7 @@ export default class MyDocument extends Document {
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Лизинговый калькулятор - Эволюция" />
|
||||
<meta name="description" content={PAGE_DESCRIPTION} />
|
||||
{metaFavicon}
|
||||
</Head>
|
||||
<body>
|
||||
|
||||
69
apps/web/pages/admin/cache.jsx
Normal file
69
apps/web/pages/admin/cache.jsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { getQueries } from '@/api/cache/query';
|
||||
import initializeApollo from '@/apollo/client';
|
||||
import * as Admin from '@/Components/Admin';
|
||||
import { Error } from '@/Components/Common/Error';
|
||||
import { getPageTitle } from '@/utils/page';
|
||||
import { makeGetUserType } from '@/utils/user';
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query';
|
||||
import Head from 'next/head';
|
||||
|
||||
function Content() {
|
||||
return (
|
||||
<Admin.Layout>
|
||||
<Head>
|
||||
<title>{getPageTitle('Управление кэшем')}</title>
|
||||
</Head>
|
||||
<Admin.Cache />
|
||||
</Admin.Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Page(props) {
|
||||
if (props.statusCode !== 200) return <Error {...props} />;
|
||||
|
||||
return <Content />;
|
||||
}
|
||||
|
||||
/** @type {import('next').GetServerSideProps} */
|
||||
export async function getServerSideProps({ req }) {
|
||||
const { cookie = '' } = req.headers;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const apolloClient = initializeApollo();
|
||||
const getUserType = makeGetUserType({ apolloClient, queryClient });
|
||||
|
||||
try {
|
||||
const user = await getUserType({ cookie });
|
||||
|
||||
if (!user.admin) {
|
||||
return {
|
||||
props: {
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
statusCode: 403,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await queryClient.prefetchQuery(['admin', 'cache', 'queries'], ({ signal }) =>
|
||||
getQueries({ signal })
|
||||
);
|
||||
|
||||
return {
|
||||
props: {
|
||||
calculation: {},
|
||||
initialApolloState: apolloClient.cache.extract(),
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
statusCode: 200,
|
||||
user,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
props: {
|
||||
error: JSON.stringify(error),
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
statusCode: 500,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,9 @@
|
||||
import { getUser } from '@/api/user/query';
|
||||
import initializeApollo from '@/apollo/client';
|
||||
import * as Calculation from '@/Components/Calculation';
|
||||
import { Error } from '@/Components/Common/Error';
|
||||
import { Grid } from '@/Components/Layout/Page';
|
||||
import Output from '@/Components/Output';
|
||||
import { defaultRoles } from '@/config/users';
|
||||
import * as CRMTypes from '@/graphql/crm.types';
|
||||
import * as hooks from '@/process/hooks';
|
||||
import { getPageTitle } from '@/utils/page';
|
||||
import { makeGetUserType } from '@/utils/user';
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query';
|
||||
import Head from 'next/head';
|
||||
|
||||
@ -17,54 +14,35 @@ function Content() {
|
||||
hooks.useReactions();
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Calculation.Layout>
|
||||
<Head>
|
||||
<title>Лизинговый калькулятор - Эволюция</title>
|
||||
<title>{getPageTitle()}</title>
|
||||
</Head>
|
||||
<Calculation.Form prune={['unlimited']} />
|
||||
<Calculation.Settings />
|
||||
<Output />
|
||||
</Grid>
|
||||
<Calculation.Output />
|
||||
</Calculation.Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home(props) {
|
||||
export default function Page(props) {
|
||||
if (props.statusCode !== 200) return <Error {...props} />;
|
||||
|
||||
return <Content />;
|
||||
}
|
||||
|
||||
export const makeGetServerSideProps = ({ roles }) =>
|
||||
/** @type {import('next').GetServerSideProps} */
|
||||
(
|
||||
async function ({ req }) {
|
||||
export async function getServerSideProps({ req }) {
|
||||
const { cookie = '' } = req.headers;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const user = await queryClient.fetchQuery(['user'], ({ signal }) =>
|
||||
getUser({
|
||||
headers: {
|
||||
cookie,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
);
|
||||
|
||||
const apolloClient = initializeApollo();
|
||||
const getUserType = makeGetUserType({ apolloClient, queryClient });
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { systemuser },
|
||||
} = await apolloClient.query({
|
||||
fetchPolicy: 'network-only',
|
||||
query: CRMTypes.GetSystemUserDocument,
|
||||
variables: {
|
||||
domainname: user.domainName,
|
||||
},
|
||||
});
|
||||
const user = await getUserType({ cookie });
|
||||
|
||||
if (!systemuser?.roles?.some((x) => x?.name && roles.includes(x.name))) {
|
||||
if (!user.default) {
|
||||
return {
|
||||
props: {
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
@ -79,6 +57,7 @@ export const makeGetServerSideProps = ({ roles }) =>
|
||||
initialApolloState: apolloClient.cache.extract(),
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
statusCode: 200,
|
||||
user,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
@ -91,6 +70,3 @@ export const makeGetServerSideProps = ({ roles }) =>
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const getServerSideProps = makeGetServerSideProps({ roles: defaultRoles });
|
||||
|
||||
@ -1,17 +1,13 @@
|
||||
import { makeGetServerSideProps } from '.';
|
||||
import initializeApollo from '@/apollo/client';
|
||||
import * as Calculation from '@/Components/Calculation';
|
||||
import { Error } from '@/Components/Common/Error';
|
||||
import { Grid } from '@/Components/Layout/Page';
|
||||
import Output from '@/Components/Output';
|
||||
import { unlimitedRoles } from '@/config/users';
|
||||
import * as hooks from '@/process/hooks';
|
||||
import { useStore } from '@/stores/hooks';
|
||||
import { getPageTitle } from '@/utils/page';
|
||||
import { makeGetUserType } from '@/utils/user';
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query';
|
||||
import Head from 'next/head';
|
||||
|
||||
function Content() {
|
||||
const store = useStore();
|
||||
store.$process.add('Unlimited');
|
||||
|
||||
hooks.useSentryScope();
|
||||
hooks.useMainData();
|
||||
hooks.useGetUsers();
|
||||
@ -19,24 +15,60 @@ function Content() {
|
||||
hooks.useReactions();
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Calculation.Layout>
|
||||
<Head>
|
||||
<title>Лизинговый калькулятор без ограничений - Эволюция</title>
|
||||
<meta name="description" content="Лизинговый калькулятор без ограничений - Эволюция" />
|
||||
<title>{getPageTitle('Без ограничений')}</title>
|
||||
</Head>
|
||||
<Calculation.Form />
|
||||
<Calculation.Settings />
|
||||
<Output />
|
||||
</Grid>
|
||||
<Calculation.Output />
|
||||
</Calculation.Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Unlimited(props) {
|
||||
export default function Page(props) {
|
||||
if (props.statusCode !== 200) return <Error {...props} />;
|
||||
|
||||
return <Content />;
|
||||
}
|
||||
|
||||
export const getServerSideProps = makeGetServerSideProps({
|
||||
roles: unlimitedRoles,
|
||||
});
|
||||
/** @type {import('next').GetServerSideProps} */
|
||||
export async function getServerSideProps({ req }) {
|
||||
const { cookie = '' } = req.headers;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const apolloClient = initializeApollo();
|
||||
const getUserType = makeGetUserType({ apolloClient, queryClient });
|
||||
|
||||
try {
|
||||
const user = await getUserType({ cookie });
|
||||
|
||||
if (!user.unlimited) {
|
||||
return {
|
||||
props: {
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
statusCode: 403,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
calculation: {},
|
||||
initialApolloState: apolloClient.cache.extract(),
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
mode: 'unlimited',
|
||||
statusCode: 200,
|
||||
user,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
props: {
|
||||
error: JSON.stringify(error),
|
||||
initialQueryState: dehydrate(queryClient),
|
||||
statusCode: 500,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Лизинговый калькулятор | Эволюция",
|
||||
"short_name": "Лизинговый калькулятор | Эволюция",
|
||||
"name": "Лизинговый калькулятор - Эволюция",
|
||||
"short_name": "Калькулятор",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
|
||||
@ -10,7 +10,7 @@ export function initializeStore(initialData) {
|
||||
const _store = store ?? new RootStore();
|
||||
|
||||
if (initialData) {
|
||||
const { calculation, tables } = initialData;
|
||||
const { calculation, tables, mode } = initialData;
|
||||
|
||||
if (calculation?.values) _store.$calculation.$values.hydrate(calculation.values);
|
||||
if (calculation?.statuses) _store.$calculation.$status.hydrate(calculation.statuses);
|
||||
@ -23,6 +23,9 @@ export function initializeStore(initialData) {
|
||||
values: tables.insurance.values,
|
||||
});
|
||||
}
|
||||
|
||||
_store.$process.clear();
|
||||
if (mode === 'unlimited') _store.$process.add('Unlimited');
|
||||
}
|
||||
|
||||
if (isServer()) return _store;
|
||||
|
||||
7
apps/web/utils/page.tsx
Normal file
7
apps/web/utils/page.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { PAGE_TITLE } from '@/constants/page';
|
||||
|
||||
export function getPageTitle(title?: string) {
|
||||
if (!title) return PAGE_TITLE;
|
||||
|
||||
return `${title} - ${PAGE_TITLE}`;
|
||||
}
|
||||
51
apps/web/utils/user.ts
Normal file
51
apps/web/utils/user.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { getUser } from '@/api/user/query';
|
||||
import { adminRoles, defaultRoles, unlimitedRoles } from '@/config/users';
|
||||
import * as CRMTypes from '@/graphql/crm.types';
|
||||
import type { ApolloClient, NormalizedCache } from '@apollo/client';
|
||||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import { sift } from 'radash';
|
||||
|
||||
type GetUserTypeProps = {
|
||||
cookie: string;
|
||||
};
|
||||
|
||||
type UserType = {
|
||||
admin: boolean;
|
||||
default: boolean;
|
||||
unlimited: boolean;
|
||||
};
|
||||
|
||||
type MakeGetUserTypeProps = {
|
||||
apolloClient: ApolloClient<NormalizedCache>;
|
||||
queryClient: QueryClient;
|
||||
};
|
||||
export function makeGetUserType({ apolloClient, queryClient }: MakeGetUserTypeProps) {
|
||||
return async function ({ cookie }: GetUserTypeProps): Promise<UserType> {
|
||||
const user = await queryClient.fetchQuery(['user'], ({ signal }) =>
|
||||
getUser({
|
||||
headers: {
|
||||
cookie,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
);
|
||||
|
||||
const {
|
||||
data: { systemuser },
|
||||
} = await apolloClient.query({
|
||||
fetchPolicy: 'network-only',
|
||||
query: CRMTypes.GetSystemUserDocument,
|
||||
variables: {
|
||||
domainname: user.domainName,
|
||||
},
|
||||
});
|
||||
|
||||
const roles = systemuser?.roles ? sift(systemuser?.roles)?.map((x) => x?.name) : [];
|
||||
|
||||
return {
|
||||
admin: adminRoles.some((x) => roles?.includes(x)),
|
||||
default: defaultRoles.some((x) => roles?.includes(x)),
|
||||
unlimited: unlimitedRoles.some((x) => roles?.includes(x)),
|
||||
};
|
||||
};
|
||||
}
|
||||
10
packages/shared/.eslintrc.js
Normal file
10
packages/shared/.eslintrc.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { createConfig } = require('@vchikalkin/eslint-config-awesome');
|
||||
|
||||
module.exports = createConfig('typescript', {
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
ignorePatterns: ['*.config.js', '.eslintrc.js'],
|
||||
root: true,
|
||||
});
|
||||
13
packages/shared/package.json
Normal file
13
packages/shared/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "shared",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"lint": "TIMING=1 eslint \"**/*.ts*\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vchikalkin/eslint-config-awesome": "^1.1.6",
|
||||
"eslint": "^8.52.0",
|
||||
"tsconfig": "workspace:*"
|
||||
}
|
||||
}
|
||||
6
packages/shared/tsconfig.json
Normal file
6
packages/shared/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "tsconfig/common.json",
|
||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
|
||||
"exclude": ["dist", "build", "node_modules"],
|
||||
"compilerOptions": { "outDir": "./build", "lib": ["DOM", "ES2020"] }
|
||||
}
|
||||
4
packages/shared/types/cache.ts
Normal file
4
packages/shared/types/cache.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type QueryItem = {
|
||||
queries: string[];
|
||||
ttl: number | false;
|
||||
};
|
||||
@ -11,9 +11,11 @@ export * from './Text';
|
||||
export {
|
||||
Alert,
|
||||
Badge,
|
||||
Collapse,
|
||||
Divider,
|
||||
Form,
|
||||
InputNumber,
|
||||
Menu,
|
||||
message,
|
||||
notification,
|
||||
Result,
|
||||
|
||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -96,6 +96,9 @@ importers:
|
||||
prettier:
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.5
|
||||
shared:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared
|
||||
source-map-support:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
@ -250,6 +253,9 @@ importers:
|
||||
msw:
|
||||
specifier: ^1.1.0
|
||||
version: 1.3.2(typescript@5.3.3)
|
||||
shared:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared
|
||||
ts-jest:
|
||||
specifier: ^29.0.5
|
||||
version: 29.1.1(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3)
|
||||
@ -257,6 +263,18 @@ importers:
|
||||
specifier: ^5.3.3
|
||||
version: 5.3.3
|
||||
|
||||
packages/shared:
|
||||
devDependencies:
|
||||
'@vchikalkin/eslint-config-awesome':
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.6(@babel/eslint-plugin@7.23.5)(@babel/plugin-syntax-flow@7.23.3)(@babel/plugin-transform-react-jsx@7.23.4)(eslint-plugin-import@2.29.1)(eslint@8.57.0)(graphql@16.8.1)(prettier@3.2.5)(typescript@5.3.3)
|
||||
eslint:
|
||||
specifier: ^8.52.0
|
||||
version: 8.57.0
|
||||
tsconfig:
|
||||
specifier: workspace:*
|
||||
version: link:../tsconfig
|
||||
|
||||
packages/tools:
|
||||
devDependencies:
|
||||
'@vchikalkin/eslint-config-awesome':
|
||||
@ -6907,7 +6925,7 @@ packages:
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3)
|
||||
eslint: 8.57.0
|
||||
jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.19.18)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@ -8151,7 +8169,7 @@ packages:
|
||||
'@graphql-tools/json-file-loader': 7.4.18(graphql@16.8.1)
|
||||
'@graphql-tools/load': 7.8.14(graphql@16.8.1)
|
||||
'@graphql-tools/merge': 8.4.2(graphql@16.8.1)
|
||||
'@graphql-tools/url-loader': 7.17.18(@types/node@18.19.18)(graphql@16.8.1)
|
||||
'@graphql-tools/url-loader': 7.17.18(@types/node@20.11.20)(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
|
||||
cosmiconfig: 8.0.0
|
||||
graphql: 16.8.1
|
||||
@ -13175,7 +13193,7 @@ packages:
|
||||
'@babel/core': 7.23.9
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 29.7.0(@types/node@20.11.20)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@18.19.18)
|
||||
jest-util: 29.7.0
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user