update handle
This commit is contained in:
parent
b869b83a3f
commit
db080ad3f2
|
|
@ -1,10 +1,10 @@
|
||||||
import { Box, Button, Image, Text } from '@mantine/core';
|
import { Box, Button, Image, Text } from '@mantine/core';
|
||||||
import { IBid, IWebBid } from '../../system/type';
|
|
||||||
import { Socket } from 'socket.io-client';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import ShowImageModal from './show-image-modal';
|
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
import { IBid, IWebBid } from '../../system/type';
|
||||||
|
import ShowImageModal from './show-image-modal';
|
||||||
|
|
||||||
export interface IWorkingPageProps {
|
export interface IWorkingPageProps {
|
||||||
data: (IBid | IWebBid) & { type: string };
|
data: (IBid | IWebBid) & { type: string };
|
||||||
|
|
@ -20,6 +20,10 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
||||||
|
|
||||||
const [lastUpdate, setLastUpdate] = useState(new Date());
|
const [lastUpdate, setLastUpdate] = useState(new Date());
|
||||||
|
|
||||||
|
function isIBid(obj: IBid | IWebBid): obj is IBid {
|
||||||
|
return 'name' in obj;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateImage = ({ type, id, filename }: { type: string; filename: string; id: IBid['id'] }) => {
|
const updateImage = ({ type, id, filename }: { type: string; filename: string; id: IBid['id'] }) => {
|
||||||
if (type == data.type && id == data.id) {
|
if (type == data.type && id == data.id) {
|
||||||
|
|
@ -49,11 +53,24 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box className="absolute bg-black/60 inset-0 flex items-center justify-center text-white font-bold flex-col gap-3 p-4 rounded-lg transition duration-300 hover:bg-opacity-70">
|
<Box className="absolute bg-black/60 inset-0 flex items-center justify-center text-white font-bold flex-col gap-3 p-4 rounded-lg transition duration-300 hover:bg-opacity-70">
|
||||||
<Text className="text-lg uppercase tracking-wide">{data.type}</Text>
|
<Text className="text-lg tracking-wide text-center font-bold">{isIBid(data) ? data.name : 'Tracking page'}</Text>
|
||||||
|
{isIBid(data) && <Text className="text-xs tracking-wide">{`Max price: $${data.max_price}`}</Text>}
|
||||||
|
{isIBid(data) && <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>}
|
||||||
<Text className="text-sm italic opacity-80">{moment(lastUpdate).format('HH:mm:ss DD/MM/YYYY')}</Text>
|
<Text className="text-sm italic opacity-80">{moment(lastUpdate).format('HH:mm:ss DD/MM/YYYY')}</Text>
|
||||||
<Button onClick={open} className="bg-white text-black px-4 py-2 rounded-md shadow-md hover:bg-gray-200 transition">
|
<Box className="flex items-center gap-4">
|
||||||
Show
|
<Button size="xs" color="green" onClick={open} className="bg-white text-black px-4 py-2 rounded-md shadow-md hover:bg-gray-200 transition">
|
||||||
</Button>
|
Show
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
target="_blank"
|
||||||
|
component="a"
|
||||||
|
size="xs"
|
||||||
|
href={data.url || '/'}
|
||||||
|
className="bg-white text-black px-4 py-2 rounded-md shadow-md hover:bg-gray-200 transition"
|
||||||
|
>
|
||||||
|
Link
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { AppShell, Box, NavLink, ScrollArea } from '@mantine/core';
|
import { AppShell, Box, Button } from '@mantine/core';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { Outlet, useLocation, useNavigate } from 'react-router';
|
import { Outlet, useLocation, useNavigate } from 'react-router';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { me } from '../apis/auth';
|
import { me } from '../apis/auth';
|
||||||
import { Logo, UserMenu } from '../components';
|
import { Logo, UserMenu } from '../components';
|
||||||
import Links from '../system/links';
|
import Links from '../system/links';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
export default function PrivateLayout() {
|
export default function PrivateLayout() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
@ -19,19 +19,36 @@ export default function PrivateLayout() {
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// checkAuth();
|
checkAuth();
|
||||||
}, [checkAuth]);
|
}, [checkAuth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell header={{ height: 60 }} navbar={{ width: 300, breakpoint: 'sm' }} padding="md">
|
<AppShell header={{ height: 60 }} navbar={{ width: 300, breakpoint: 'sm', collapsed: { mobile: true, desktop: true } }} padding="md">
|
||||||
<AppShell.Header>
|
<AppShell.Header>
|
||||||
<Box className="flex items-center justify-between h-full px-4">
|
<Box className="flex items-center justify-between h-full px-4">
|
||||||
<Logo />
|
<Logo />
|
||||||
|
|
||||||
|
<Box className="flex items-center gap-4">
|
||||||
|
{Links.MENUS.map((menu, index) => (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
component={Link}
|
||||||
|
className="rounded-sm"
|
||||||
|
key={menu.path + index}
|
||||||
|
to={menu.path}
|
||||||
|
// label={menu.title}
|
||||||
|
variant={location.pathname === menu.path ? 'filled' : 'outline'}
|
||||||
|
leftSection={<menu.icon size={16} stroke={1.5} />}
|
||||||
|
>
|
||||||
|
{menu.title}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
</Box>
|
</Box>
|
||||||
</AppShell.Header>
|
</AppShell.Header>
|
||||||
<AppShell.Navbar px={'md'} pb={'md'}>
|
{/* <AppShell.Navbar px={'md'} pb={'md'}>
|
||||||
<AppShell.Section grow my="md" component={ScrollArea}>
|
<AppShell.Section grow my="md" component={ScrollArea}>
|
||||||
<div className="w-full h-full flex flex-col gap-3">
|
<div className="w-full h-full flex flex-col gap-3">
|
||||||
{Links.MENUS.map((menu, index) => (
|
{Links.MENUS.map((menu, index) => (
|
||||||
|
|
@ -48,7 +65,7 @@ export default function PrivateLayout() {
|
||||||
</div>
|
</div>
|
||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
<AppShell.Section className="text-xs text-center">{new Date().getFullYear()}</AppShell.Section>
|
<AppShell.Section className="text-xs text-center">{new Date().getFullYear()}</AppShell.Section>
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar> */}
|
||||||
<AppShell.Main>
|
<AppShell.Main>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ export interface TableProps<R extends Record<string, string | number>> extends M
|
||||||
onFetched?: (response: AxiosResponse<R[]>) => void;
|
onFetched?: (response: AxiosResponse<R[]>) => void;
|
||||||
onChooses?: (chooses: R[]) => void;
|
onChooses?: (chooses: R[]) => void;
|
||||||
onSearch?: (value: ITableFilter<R>[]) => void;
|
onSearch?: (value: ITableFilter<R>[]) => void;
|
||||||
|
onClickRow?: (row: R) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableIcon = ({
|
const TableIcon = ({
|
||||||
|
|
@ -115,6 +116,7 @@ const Table = <R extends Record<string, any>>({
|
||||||
onFetchError,
|
onFetchError,
|
||||||
onChooses,
|
onChooses,
|
||||||
onSearch,
|
onSearch,
|
||||||
|
onClickRow,
|
||||||
...props
|
...props
|
||||||
}: TableProps<R>) => {
|
}: TableProps<R>) => {
|
||||||
const paramsUrl = new URLSearchParams(window.location.search);
|
const paramsUrl = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -507,6 +509,7 @@ const Table = <R extends Record<string, any>>({
|
||||||
|
|
||||||
handleSetChoosesData(newDataChooses);
|
handleSetChoosesData(newDataChooses);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[rowsData, chooses, handleSetChoosesData],
|
[rowsData, chooses, handleSetChoosesData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -576,10 +579,6 @@ const Table = <R extends Record<string, any>>({
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [options]);
|
}, [options]);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// console.log('filter', filter);
|
|
||||||
// }, [filter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chooses.length < rowsData.length) {
|
if (chooses.length < rowsData.length) {
|
||||||
setCheckedAll(false);
|
setCheckedAll(false);
|
||||||
|
|
@ -691,7 +690,7 @@ const Table = <R extends Record<string, any>>({
|
||||||
<MTable.Tbody {...tbody} className="relative">
|
<MTable.Tbody {...tbody} className="relative">
|
||||||
{rowsData.length > 0 &&
|
{rowsData.length > 0 &&
|
||||||
rowsData.map((row, index) => (
|
rowsData.map((row, index) => (
|
||||||
<MTable.Tr {...trbody} key={row[rowKey]}>
|
<MTable.Tr {...trbody} onClick={onClickRow ? () => onClickRow(row) : undefined} key={row[rowKey]}>
|
||||||
{showChooses && (
|
{showChooses && (
|
||||||
<MTable.Td key={'__choose_' + index} {...td} {...chooseOptions?.defaultBodyProps}>
|
<MTable.Td key={'__choose_' + index} {...td} {...chooseOptions?.defaultBodyProps}>
|
||||||
{chooseOptions?.renderBody ? (
|
{chooseOptions?.renderBody ? (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
import { ActionIcon, Box, Menu } from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { IconEdit, IconMenu, IconPassword, IconTrash } from '@tabler/icons-react';
|
||||||
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
import { deleteAdmin, getAdmins } from '../apis/admin';
|
||||||
|
import { AdminModal, GrantNewPasswordModal } from '../components/admin';
|
||||||
|
import Table from '../lib/table/table';
|
||||||
|
import { IColumn, TRefTableFn } from '../lib/table/type';
|
||||||
|
import { useConfirmStore } from '../lib/zustand/use-confirm';
|
||||||
|
import { IAdmin } from '../system/type';
|
||||||
|
import { formatTime } from '../utils';
|
||||||
|
|
||||||
|
export default function Admins() {
|
||||||
|
const { setConfirm } = useConfirmStore();
|
||||||
|
|
||||||
|
const refTableFn: TRefTableFn<IAdmin> = useRef({});
|
||||||
|
|
||||||
|
const [adminModelOpened, adminModel] = useDisclosure(false);
|
||||||
|
const [grantOpened, grantModel] = useDisclosure(false);
|
||||||
|
const [clickData, setClickData] = useState<IAdmin | null>(null);
|
||||||
|
|
||||||
|
const columns: IColumn<IAdmin>[] = [
|
||||||
|
{
|
||||||
|
key: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
typeFilter: 'number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'username',
|
||||||
|
title: 'Username',
|
||||||
|
typeFilter: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'email',
|
||||||
|
title: 'Email',
|
||||||
|
typeFilter: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'created_at',
|
||||||
|
title: 'Created at',
|
||||||
|
typeFilter: 'none',
|
||||||
|
renderRow(row) {
|
||||||
|
return <span>{formatTime(row.created_at)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'updated_at',
|
||||||
|
title: 'Update at',
|
||||||
|
typeFilter: 'none',
|
||||||
|
renderRow(row) {
|
||||||
|
return <span>{formatTime(row.updated_at)}</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleDelete = (admin: IAdmin) => {
|
||||||
|
setConfirm({
|
||||||
|
title: 'Delete ?',
|
||||||
|
message: 'This admin will be delete',
|
||||||
|
handleOk: async () => {
|
||||||
|
await deleteAdmin(admin);
|
||||||
|
|
||||||
|
if (refTableFn.current?.fetchData) {
|
||||||
|
refTableFn.current.fetchData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAdminsFn = useCallback(getAdmins, []);
|
||||||
|
|
||||||
|
const table = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
actionsOptions={{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
key: 'add',
|
||||||
|
title: 'Add',
|
||||||
|
callback: () => {
|
||||||
|
adminModel.open();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
refTableFn={refTableFn}
|
||||||
|
striped
|
||||||
|
showLoading={true}
|
||||||
|
highlightOnHover
|
||||||
|
styleDefaultHead={{
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
width: 'fit-content',
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
query: getAdminsFn,
|
||||||
|
pathToData: 'data.data',
|
||||||
|
keyOptions: {
|
||||||
|
last_page: 'lastPage',
|
||||||
|
per_page: 'perPage',
|
||||||
|
from: 'from',
|
||||||
|
to: 'to',
|
||||||
|
total: 'total',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rows={[]}
|
||||||
|
withColumnBorders
|
||||||
|
showChooses={false}
|
||||||
|
withTableBorder
|
||||||
|
columns={columns}
|
||||||
|
actions={{
|
||||||
|
title: <Box className="w-full text-center">Action</Box>,
|
||||||
|
body: (row) => {
|
||||||
|
return (
|
||||||
|
<Menu shadow="md" width={200}>
|
||||||
|
<Menu.Target>
|
||||||
|
<Box className="flex w-full items-center justify-center">
|
||||||
|
<ActionIcon size="sm" variant="light">
|
||||||
|
<IconMenu size={14} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Box>
|
||||||
|
</Menu.Target>
|
||||||
|
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setClickData(row);
|
||||||
|
adminModel.open();
|
||||||
|
}}
|
||||||
|
leftSection={<IconEdit size={14} />}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={() => {
|
||||||
|
setClickData(row);
|
||||||
|
grantModel.open();
|
||||||
|
}}
|
||||||
|
leftSection={<IconPassword size={14} />}
|
||||||
|
>
|
||||||
|
Grant new password
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item disabled={row.is_system_account} onClick={() => handleDelete(row)} leftSection={<IconTrash color="red" size={14} />}>
|
||||||
|
Delete
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rowKey="id"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{table}
|
||||||
|
|
||||||
|
<AdminModal
|
||||||
|
onUpdated={refTableFn.current.fetchData}
|
||||||
|
data={clickData}
|
||||||
|
opened={adminModelOpened}
|
||||||
|
onClose={() => {
|
||||||
|
setClickData(null);
|
||||||
|
adminModel.close();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GrantNewPasswordModal
|
||||||
|
opened={grantOpened}
|
||||||
|
data={clickData}
|
||||||
|
onClose={() => {
|
||||||
|
setClickData(null);
|
||||||
|
grantModel.close();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -8,9 +8,9 @@ import { BidModal, ShowHistoriesBidGraysApiModal, ShowHistoriesModal } from '../
|
||||||
import Table from '../lib/table/table';
|
import Table from '../lib/table/table';
|
||||||
import { IColumn, TRefTableFn } from '../lib/table/type';
|
import { IColumn, TRefTableFn } from '../lib/table/type';
|
||||||
import { useConfirmStore } from '../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../lib/zustand/use-confirm';
|
||||||
|
import { mappingStatusColors } from '../system/constants';
|
||||||
import { IBid } from '../system/type';
|
import { IBid } from '../system/type';
|
||||||
import { formatTime } from '../utils';
|
import { formatTime } from '../utils';
|
||||||
import { mappingStatusColors } from '../system/constants';
|
|
||||||
|
|
||||||
export default function Bids() {
|
export default function Bids() {
|
||||||
const refTableFn: TRefTableFn<IBid> = useRef({});
|
const refTableFn: TRefTableFn<IBid> = useRef({});
|
||||||
|
|
@ -108,15 +108,6 @@ export default function Bids() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
|
||||||
// key: 'updated_at',
|
|
||||||
// title: 'Update at',
|
|
||||||
// typeFilter: 'none',
|
|
||||||
// renderRow(row) {
|
|
||||||
// return <span className="text-sm">{formatTime(row.updated_at)}</span>;
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleDelete = (bid: IBid) => {
|
const handleDelete = (bid: IBid) => {
|
||||||
|
|
@ -156,6 +147,14 @@ export default function Bids() {
|
||||||
const table = useMemo(() => {
|
const table = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
|
onClickRow={(row) => {
|
||||||
|
window.open(row.url, '_blank');
|
||||||
|
}}
|
||||||
|
tableChildProps={{
|
||||||
|
trbody: {
|
||||||
|
className: 'cursor-pointer',
|
||||||
|
},
|
||||||
|
}}
|
||||||
actionsOptions={{
|
actionsOptions={{
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
|
|
@ -217,14 +216,14 @@ export default function Bids() {
|
||||||
return (
|
return (
|
||||||
<Menu shadow="md" width={200}>
|
<Menu shadow="md" width={200}>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<Box className="flex w-full items-center justify-center">
|
<Box onClick={(e) => e.stopPropagation()} className="flex w-full items-center justify-center">
|
||||||
<ActionIcon size="sm" variant="light">
|
<ActionIcon size="sm" variant="light">
|
||||||
<IconMenu size={14} />
|
<IconMenu size={14} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Box>
|
</Box>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown onClick={(e) => e.stopPropagation()}>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setClickData(row);
|
setClickData(row);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import { IconHammer, IconHome2, IconMessage, IconOutlet, IconPageBreak } from '@tabler/icons-react';
|
import { IconHammer, IconHome2, IconMessage, IconOutlet, IconPageBreak, IconUserCheck } from '@tabler/icons-react';
|
||||||
import { Bids, Dashboard, OutBidsLog } from '../pages';
|
import { Bids, Dashboard, OutBidsLog } from '../pages';
|
||||||
import WebBids from '../pages/web-bids';
|
import WebBids from '../pages/web-bids';
|
||||||
import SendMessageHistories from '../pages/send-message-histories';
|
import SendMessageHistories from '../pages/send-message-histories';
|
||||||
|
import Admins from '../pages/admins';
|
||||||
export default class Links {
|
export default class Links {
|
||||||
public static DASHBOARD = '/dashboard';
|
public static DASHBOARD = '/dashboard';
|
||||||
public static BIDS = '/bids';
|
public static BIDS = '/bids';
|
||||||
public static WEBS = '/webs';
|
public static WEBS = '/webs';
|
||||||
public static OUT_BIDS_LOG = '/out-bids-log';
|
public static OUT_BIDS_LOG = '/out-bids-log';
|
||||||
public static SEND_MESSAGE_HISTORIES = '/send-message-histories';
|
public static SEND_MESSAGE_HISTORIES = '/send-message-histories';
|
||||||
|
public static ADMINS = '/admins';
|
||||||
|
|
||||||
public static HOME = '/';
|
public static HOME = '/';
|
||||||
public static LOGIN = '/login';
|
public static LOGIN = '/login';
|
||||||
|
|
@ -19,6 +21,12 @@ export default class Links {
|
||||||
icon: IconHome2,
|
icon: IconHome2,
|
||||||
element: Dashboard,
|
element: Dashboard,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: this.ADMINS,
|
||||||
|
title: 'Admins',
|
||||||
|
icon: IconUserCheck,
|
||||||
|
element: Admins,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: this.WEBS,
|
path: this.WEBS,
|
||||||
title: 'Webs',
|
title: 'Webs',
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,18 @@
|
||||||
"@nestjs/config": "^4.0.1",
|
"@nestjs/config": "^4.0.1",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/event-emitter": "^3.0.1",
|
"@nestjs/event-emitter": "^3.0.1",
|
||||||
|
"@nestjs/jwt": "^11.0.0",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/platform-express": "^10.4.15",
|
"@nestjs/platform-express": "^10.4.15",
|
||||||
"@nestjs/platform-socket.io": "^11.0.11",
|
"@nestjs/platform-socket.io": "^11.0.11",
|
||||||
|
"@nestjs/throttler": "^6.4.0",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"@nestjs/websockets": "^11.0.11",
|
"@nestjs/websockets": "^11.0.11",
|
||||||
"axios": "^1.8.3",
|
"axios": "^1.8.3",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"mysql2": "^3.13.0",
|
"mysql2": "^3.13.0",
|
||||||
|
|
@ -34,6 +38,8 @@
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
"@nestjs/schematics": "^10.0.0",
|
"@nestjs/schematics": "^10.0.0",
|
||||||
"@nestjs/testing": "^10.0.0",
|
"@nestjs/testing": "^10.0.0",
|
||||||
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
|
|
@ -2076,6 +2082,50 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mapbox/node-pre-gyp": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.0",
|
||||||
|
"https-proxy-agent": "^5.0.0",
|
||||||
|
"make-dir": "^3.1.0",
|
||||||
|
"node-fetch": "^2.6.7",
|
||||||
|
"nopt": "^5.0.0",
|
||||||
|
"npmlog": "^5.0.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"semver": "^7.3.5",
|
||||||
|
"tar": "^6.1.11"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-pre-gyp": "bin/node-pre-gyp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@microsoft/tsdoc": {
|
"node_modules/@microsoft/tsdoc": {
|
||||||
"version": "0.15.1",
|
"version": "0.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
|
||||||
|
|
@ -2309,6 +2359,19 @@
|
||||||
"@nestjs/core": "^10.0.0 || ^11.0.0"
|
"@nestjs/core": "^10.0.0 || ^11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/jwt": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jsonwebtoken": "9.0.7",
|
||||||
|
"jsonwebtoken": "9.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/mapped-types": {
|
"node_modules/@nestjs/mapped-types": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
|
||||||
|
|
@ -2483,6 +2546,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/throttler": {
|
||||||
|
"version": "6.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.4.0.tgz",
|
||||||
|
"integrity": "sha512-osL67i0PUuwU5nqSuJjtUJZMkxAnYB4VldgYUMGzvYRJDCqGRFMWbsbzm/CkUtPLRL30I8T74Xgt/OQxnYokiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||||
|
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||||
|
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/typeorm": {
|
"node_modules/@nestjs/typeorm": {
|
||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz",
|
||||||
|
|
@ -2718,6 +2792,16 @@
|
||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/bcrypt": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/body-parser": {
|
||||||
"version": "1.19.5",
|
"version": "1.19.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||||
|
|
@ -2739,6 +2823,16 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie-parser": {
|
||||||
|
"version": "1.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.8.tgz",
|
||||||
|
"integrity": "sha512-l37JqFrOJ9yQfRQkljb41l0xVphc7kg5JTjjr+pLRZ0IyZ49V4BQ8vbF4Ut2C2e+WH4al3xD3ZwYwIUfnbT4NQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/cookiejar": {
|
"node_modules/@types/cookiejar": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
|
||||||
|
|
@ -2872,6 +2966,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jsonwebtoken": {
|
||||||
|
"version": "9.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
|
||||||
|
"integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/methods": {
|
"node_modules/@types/methods": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||||
|
|
@ -3383,6 +3486,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/abbrev": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/abstract-logging": {
|
"node_modules/abstract-logging": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||||
|
|
@ -3439,6 +3548,18 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "8.12.0",
|
"version": "8.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
|
|
@ -3600,6 +3721,40 @@
|
||||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/aproba": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/are-we-there-yet": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||||
|
"deprecated": "This package is no longer supported.",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"delegates": "^1.0.0",
|
||||||
|
"readable-stream": "^3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/are-we-there-yet/node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/arg": {
|
"node_modules/arg": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||||
|
|
@ -3858,6 +4013,20 @@
|
||||||
"node": "^4.5.0 || >= 5.9"
|
"node": "^4.5.0 || >= 5.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bcrypt": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||||
|
"node-addon-api": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
|
|
@ -4040,6 +4209,12 @@
|
||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
|
@ -4213,6 +4388,15 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chownr": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chrome-trace-event": {
|
"node_modules/chrome-trace-event": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
||||||
|
|
@ -4415,6 +4599,15 @@
|
||||||
"simple-swizzle": "^0.2.2"
|
"simple-swizzle": "^0.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/color-support": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"color-support": "bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
|
@ -4468,7 +4661,6 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/concat-stream": {
|
"node_modules/concat-stream": {
|
||||||
|
|
@ -4492,6 +4684,12 @@
|
||||||
"integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==",
|
"integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/console-control-strings": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
|
@ -4529,6 +4727,28 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||||
|
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
|
@ -4726,6 +4946,12 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delegates": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/denque": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
|
@ -4887,6 +5113,15 @@
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
|
@ -6042,6 +6277,36 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs-minipass": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"minipass": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fs-minipass/node_modules/minipass": {
|
||||||
|
"version": "3.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||||
|
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fs-minipass/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/fs-monkey": {
|
"node_modules/fs-monkey": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz",
|
||||||
|
|
@ -6053,7 +6318,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
|
|
@ -6080,6 +6344,33 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gauge": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||||
|
"deprecated": "This package is no longer supported.",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"aproba": "^1.0.3 || ^2.0.0",
|
||||||
|
"color-support": "^1.1.2",
|
||||||
|
"console-control-strings": "^1.0.0",
|
||||||
|
"has-unicode": "^2.0.1",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"signal-exit": "^3.0.0",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wide-align": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gauge/node_modules/signal-exit": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/generate-function": {
|
"node_modules/generate-function": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||||
|
|
@ -6345,6 +6636,12 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-unicode": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
|
@ -6390,6 +6687,19 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||||
|
|
@ -6494,7 +6804,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"once": "^1.3.0",
|
"once": "^1.3.0",
|
||||||
|
|
@ -7664,6 +7973,49 @@
|
||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jws": "^3.2.2",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.isboolean": "^3.0.3",
|
||||||
|
"lodash.isinteger": "^4.0.4",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isplainobject": "^4.0.6",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.once": "^4.0.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^1.4.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
|
|
@ -7785,6 +8137,42 @@
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isboolean": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isinteger": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isplainobject": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
|
|
@ -7799,6 +8187,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.once": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/log-symbols": {
|
"node_modules/log-symbols": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||||
|
|
@ -8063,6 +8457,37 @@
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minizlib": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minipass": "^3.0.0",
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minizlib/node_modules/minipass": {
|
||||||
|
"version": "3.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||||
|
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minizlib/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
"version": "0.5.6",
|
"version": "0.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
|
|
@ -8205,6 +8630,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-emoji": {
|
"node_modules/node-emoji": {
|
||||||
"version": "1.11.0",
|
"version": "1.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
|
||||||
|
|
@ -8249,6 +8680,21 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nopt": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"abbrev": "1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nopt": "bin/nopt.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
|
|
@ -8272,6 +8718,19 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/npmlog": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||||
|
"deprecated": "This package is no longer supported.",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"are-we-there-yet": "^2.0.0",
|
||||||
|
"console-control-strings": "^1.1.0",
|
||||||
|
"gauge": "^3.0.0",
|
||||||
|
"set-blocking": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
|
@ -8328,7 +8787,6 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
|
|
@ -8505,7 +8963,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -9155,7 +9612,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
|
|
@ -9171,7 +9627,6 @@
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
|
|
@ -9183,7 +9638,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
|
|
@ -9204,7 +9658,6 @@
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
|
|
@ -9472,6 +9925,12 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-blocking": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/set-cookie-parser": {
|
"node_modules/set-cookie-parser": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||||
|
|
@ -10145,6 +10604,50 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tar": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"chownr": "^2.0.0",
|
||||||
|
"fs-minipass": "^2.0.0",
|
||||||
|
"minipass": "^5.0.0",
|
||||||
|
"minizlib": "^2.1.1",
|
||||||
|
"mkdirp": "^1.0.3",
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar/node_modules/minipass": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar/node_modules/mkdirp": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"mkdirp": "bin/cmd.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.39.0",
|
"version": "5.39.0",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
||||||
|
|
@ -11107,6 +11610,15 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wide-align": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
|
@ -11154,7 +11666,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/write-file-atomic": {
|
"node_modules/write-file-atomic": {
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,30 @@
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
|
"typeorm": "ts-node ./node_modules/typeorm/cli",
|
||||||
|
"migration:run": "npm run typeorm migration:run -- -d ./src/config/typeorm.ts",
|
||||||
|
"migration:generate": "npm run typeorm -- -d ./src/config/typeorm.ts migration:generate ./src/migrations/$npm_config_name",
|
||||||
|
"migration:create": "npm run typeorm -- migration:create ./src/migrations/$npm_config_name",
|
||||||
|
"migration:revert": "npm run typeorm -- -d ./src/config/typeorm.ts migration:revert"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^4.0.1",
|
"@nestjs/config": "^4.0.1",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/event-emitter": "^3.0.1",
|
"@nestjs/event-emitter": "^3.0.1",
|
||||||
|
"@nestjs/jwt": "^11.0.0",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/platform-express": "^10.4.15",
|
"@nestjs/platform-express": "^10.4.15",
|
||||||
"@nestjs/platform-socket.io": "^11.0.11",
|
"@nestjs/platform-socket.io": "^11.0.11",
|
||||||
|
"@nestjs/throttler": "^6.4.0",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"@nestjs/websockets": "^11.0.11",
|
"@nestjs/websockets": "^11.0.11",
|
||||||
"axios": "^1.8.3",
|
"axios": "^1.8.3",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"mysql2": "^3.13.0",
|
"mysql2": "^3.13.0",
|
||||||
|
|
@ -45,6 +54,8 @@
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
"@nestjs/schematics": "^10.0.0",
|
"@nestjs/schematics": "^10.0.0",
|
||||||
"@nestjs/testing": "^10.0.0",
|
"@nestjs/testing": "^10.0.0",
|
||||||
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,36 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||||
import { BidsModule } from './modules/bids/bids.module';
|
import { BidsModule } from './modules/bids/bids.module';
|
||||||
import { DatabasesModule } from './modules/databases/databases.module';
|
import { DatabasesModule } from './modules/databases/databases.module';
|
||||||
import { AppConfigsModule } from './modules/app-configs/app-configs.module';
|
import { AppConfigsModule } from './modules/app-configs/app-configs.module';
|
||||||
|
import { AppValidatorsModule } from './modules/app-validators/app-validators.module';
|
||||||
|
import { AuthModule } from './modules/auth/auth.module';
|
||||||
|
import { AdminsModule } from './modules/admins/admins.module';
|
||||||
|
import { AuthenticationMiddleware } from './modules/auth/middlewares/authentication.middleware';
|
||||||
|
import { excludeAuth, excludeAuthor } from './system/routes/exclude-route';
|
||||||
|
import { AuthorizationMiddleware } from './modules/admins/middlewares/authorization.middleware';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [BidsModule, DatabasesModule, AppConfigsModule],
|
imports: [
|
||||||
|
BidsModule,
|
||||||
|
DatabasesModule,
|
||||||
|
AppConfigsModule,
|
||||||
|
AppValidatorsModule,
|
||||||
|
AuthModule,
|
||||||
|
AdminsModule,
|
||||||
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {
|
||||||
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
consumer
|
||||||
|
.apply(AuthenticationMiddleware)
|
||||||
|
.exclude(...excludeAuth)
|
||||||
|
.forRoutes({ path: 'admin/*', method: RequestMethod.ALL });
|
||||||
|
|
||||||
|
consumer
|
||||||
|
.apply(AuthorizationMiddleware)
|
||||||
|
.exclude(...excludeAuthor)
|
||||||
|
.forRoutes({ path: 'admin/*', method: RequestMethod.ALL });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
import { config as dotenvConfig } from 'dotenv';
|
||||||
|
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||||
|
|
||||||
|
dotenvConfig({ path: '.env' });
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
type: 'mysql',
|
||||||
|
host: `${process.env.DB_HOST}`,
|
||||||
|
port: `${process.env.DB_PORT}`,
|
||||||
|
username: `${process.env.DB_USERNAME}`,
|
||||||
|
password: `${process.env.DB_PASSWORD}`,
|
||||||
|
database: `${process.env.DB_NAME}`,
|
||||||
|
entities: ['dist/**/*.entity{.ts,.js}'],
|
||||||
|
migrations: ['dist/migrations/*{.ts,.js}'],
|
||||||
|
autoLoadEntities: true,
|
||||||
|
synchronize: false,
|
||||||
|
retryAttempts: 3,
|
||||||
|
retryDelay: 3000,
|
||||||
|
logging: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default registerAs('typeorm', () => config);
|
||||||
|
export const connectionSource = new DataSource(config as DataSourceOptions);
|
||||||
|
|
@ -3,6 +3,8 @@ import { AppModule } from './app.module';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { BadRequestExceptionFilter } from './system/filters/bad-request-exception-filter';
|
import { BadRequestExceptionFilter } from './system/filters/bad-request-exception-filter';
|
||||||
import { IoAdapter } from '@nestjs/platform-socket.io';
|
import { IoAdapter } from '@nestjs/platform-socket.io';
|
||||||
|
import * as cookieParser from 'cookie-parser';
|
||||||
|
import { useContainer } from 'class-validator';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
@ -16,9 +18,10 @@ async function bootstrap() {
|
||||||
credentials: true,
|
credentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
app.setGlobalPrefix(prefix_version, {});
|
app.setGlobalPrefix(prefix_version, {});
|
||||||
|
|
||||||
// Sử dụng WebSocket adapter
|
|
||||||
app.useWebSocketAdapter(new IoAdapter(app));
|
app.useWebSocketAdapter(new IoAdapter(app));
|
||||||
|
|
||||||
app.useGlobalFilters(new BadRequestExceptionFilter());
|
app.useGlobalFilters(new BadRequestExceptionFilter());
|
||||||
|
|
@ -31,6 +34,8 @@ async function bootstrap() {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useContainer(app.select(AppModule), { fallbackOnErrors: true });
|
||||||
|
|
||||||
await app.listen(process.env.PORT);
|
await app.listen(process.env.PORT);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateAdminTable1742778498009 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
INSERT INTO admins (email, username, password, is_system_account) VALUES
|
||||||
|
('admin@gmail.com', 'admin', '$2b$10$eF7K4Msw32e5ZC2cU78KgOqxMJygQcPDt5xXZP29inBBIV9KEsoyO', 1);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE FROM ktq_admins WHERE email = 'admin@gmail.com';
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreatePermissionTable1742778709701 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
INSERT INTO permissions (name, description) VALUES
|
||||||
|
('GET', 'Read data from the API'),
|
||||||
|
('POST', 'Create new records'),
|
||||||
|
('PUT', 'Update existing records'),
|
||||||
|
('DELETE', 'Remove records');
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE permissions;`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AdminsService } from './services/admins.service';
|
||||||
|
import Admin from './entities/admin.entity';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { AdminsController } from './controllers/admins.controller';
|
||||||
|
import Permission from './entities/permission.entity';
|
||||||
|
import { PermissionService } from './services/permission.service';
|
||||||
|
import { PermissionsController } from './controllers/permissions.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Admin, Permission])],
|
||||||
|
providers: [AdminsService, PermissionService],
|
||||||
|
exports: [AdminsService, PermissionService],
|
||||||
|
controllers: [AdminsController, PermissionsController],
|
||||||
|
})
|
||||||
|
export class AdminsModule {}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Req,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AdminsService } from '../services/admins.service';
|
||||||
|
import { Paginate, PaginateQuery } from 'nestjs-paginate';
|
||||||
|
import Admin from '../entities/admin.entity';
|
||||||
|
import { UpdateDto } from '../dtos/update.dto';
|
||||||
|
import { DeletesDto } from '../dtos/deletes.dto';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { CreateDto } from '../dtos/create.dto';
|
||||||
|
import Permission from '../entities/permission.entity';
|
||||||
|
import { GrantNewPasswordDto } from '../dtos/grant-new-password.dto';
|
||||||
|
import { SystemAccountGuard } from '@/system/guards/system-account.guard';
|
||||||
|
|
||||||
|
@Controller('admin/admins')
|
||||||
|
export class AdminsController {
|
||||||
|
constructor(private readonly adminService: AdminsService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async index(@Paginate() query: PaginateQuery) {
|
||||||
|
return await this.adminService.index(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async delete(@Param('id') id: Admin['id']) {
|
||||||
|
return await this.adminService.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id')
|
||||||
|
async update(@Param('id') id: Admin['id'], @Body() data: UpdateDto) {
|
||||||
|
const permissions = data.permissions
|
||||||
|
? data.permissions.map((item) => {
|
||||||
|
const permission = new Permission();
|
||||||
|
permission.id = item.id;
|
||||||
|
permission.name = item.name;
|
||||||
|
return permission;
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
return await this.adminService.update(id, { ...data, permissions });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('')
|
||||||
|
async create(@Body() data: CreateDto) {
|
||||||
|
return await this.adminService.create(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('grant-new-password/:id')
|
||||||
|
@UseGuards(SystemAccountGuard)
|
||||||
|
async grantNewPassword(
|
||||||
|
@Param('id') id: Admin['id'],
|
||||||
|
@Body() data: GrantNewPasswordDto,
|
||||||
|
@Req() request: Request,
|
||||||
|
) {
|
||||||
|
return await this.adminService.grantNewPassword(id, data.password, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('deletes')
|
||||||
|
async deletes(@Body() data: DeletesDto, @Req() request: Request) {
|
||||||
|
return await this.adminService.deletes(data.ids, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { Paginate, PaginateQuery } from 'nestjs-paginate';
|
||||||
|
import { PermissionService } from '../services/permission.service';
|
||||||
|
|
||||||
|
@Controller('admin/permissions')
|
||||||
|
export class PermissionsController {
|
||||||
|
constructor(private readonly permissionService: PermissionService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async index(@Paginate() query: PaginateQuery) {
|
||||||
|
return await this.permissionService.index(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { IsUnique } from '@/system/validators/decorators/is-unique';
|
||||||
|
import { IsEmail, IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateDto {
|
||||||
|
@IsString()
|
||||||
|
@IsEmail()
|
||||||
|
@IsUnique({ tableName: 'admins', column: 'email' })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
fullname: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsUnique({ tableName: 'admins', column: 'username' })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MinLength(6)
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { IsNumber } from 'class-validator';
|
||||||
|
|
||||||
|
export class DeletesDto {
|
||||||
|
@IsNumber({}, { each: true })
|
||||||
|
ids: number[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class GrantNewPasswordDto {
|
||||||
|
@IsString()
|
||||||
|
@MinLength(6)
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { HasExisted } from '@/system/validators/decorators/has-existed';
|
||||||
|
import { IsNumber, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class PermissionDto {
|
||||||
|
@IsNumber()
|
||||||
|
@HasExisted({ tableName: 'permissions', column: 'id' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { IsArray, IsEmail, IsOptional, IsString } from 'class-validator';
|
||||||
|
import { PermissionDto } from './permission.dto';
|
||||||
|
|
||||||
|
export class UpdateDto {
|
||||||
|
@IsString()
|
||||||
|
@IsEmail()
|
||||||
|
@IsOptional()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
fullname: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
permissions: PermissionDto[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Exclude } from 'class-transformer';
|
||||||
|
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
import Permission from './permission.entity';
|
||||||
|
import { Timestamp } from './timestamp';
|
||||||
|
|
||||||
|
@Entity('admins')
|
||||||
|
export default class Admin extends Timestamp {
|
||||||
|
@PrimaryGeneratedColumn('increment')
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', unique: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', unique: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', default: null, nullable: true })
|
||||||
|
fullname: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar' })
|
||||||
|
@Exclude()
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: false })
|
||||||
|
is_system_account: boolean;
|
||||||
|
|
||||||
|
@ManyToMany(() => Permission, (permission) => permission.admins)
|
||||||
|
permissions: Permission[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
JoinTable,
|
||||||
|
ManyToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import Admin from './admin.entity';
|
||||||
|
|
||||||
|
@Entity('permissions')
|
||||||
|
export default class Permission {
|
||||||
|
@PrimaryGeneratedColumn('increment')
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', unique: true })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ManyToMany(() => Admin, (admin) => admin.permissions)
|
||||||
|
@JoinTable()
|
||||||
|
admins: Admin[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
export abstract class Timestamp {
|
||||||
|
@CreateDateColumn({ type: 'timestamp', name: 'created_at' })
|
||||||
|
created_at: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ type: 'timestamp', name: 'updated_at' })
|
||||||
|
updated_at: Date;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
// auth.middleware.ts
|
||||||
|
|
||||||
|
import Admin from '@/modules/admins/entities/admin.entity';
|
||||||
|
import { AdminsService } from '@/modules/admins/services/admins.service';
|
||||||
|
import { AuthService } from '@/modules/auth/auth.service';
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
import {
|
||||||
|
ForbiddenException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
NestMiddleware,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { PermissionService } from '../services/permission.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthorizationMiddleware implements NestMiddleware {
|
||||||
|
constructor(
|
||||||
|
private readonly jwtService: JwtService,
|
||||||
|
private readonly adminService: AdminsService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly permissionService: PermissionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async use(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const admin: Admin | undefined = ({} = req['admin']);
|
||||||
|
|
||||||
|
if (!admin) {
|
||||||
|
next(
|
||||||
|
new UnauthorizedException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'Un authentication',
|
||||||
|
status_code: HttpStatus.UNAUTHORIZED,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (admin.is_system_account) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = await this.permissionService.getPermissionsByAdmin(
|
||||||
|
admin.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permissions.some((item) => item.name === req.method)) {
|
||||||
|
next(
|
||||||
|
new ForbiddenException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'Forbidden',
|
||||||
|
status_code: HttpStatus.FORBIDDEN,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import {
|
||||||
|
FilterOperator,
|
||||||
|
FilterSuffix,
|
||||||
|
paginate,
|
||||||
|
PaginateQuery,
|
||||||
|
} from 'nestjs-paginate';
|
||||||
|
import { Column } from 'nestjs-paginate/lib/helper';
|
||||||
|
import { In, Not, Repository } from 'typeorm';
|
||||||
|
import Admin from '../entities/admin.entity';
|
||||||
|
import Permission from '../entities/permission.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdminsService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Admin)
|
||||||
|
readonly adminRepo: Repository<Admin>,
|
||||||
|
@InjectRepository(Permission)
|
||||||
|
readonly permissionRepo: Repository<Permission>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async index(query: PaginateQuery) {
|
||||||
|
const filterableColumns: {
|
||||||
|
[key in Column<Admin> | (string & {})]?:
|
||||||
|
| (FilterOperator | FilterSuffix)[]
|
||||||
|
| true;
|
||||||
|
} = {
|
||||||
|
id: true,
|
||||||
|
username: [FilterOperator.ILIKE],
|
||||||
|
fullname: [FilterOperator.ILIKE],
|
||||||
|
email: [FilterOperator.ILIKE],
|
||||||
|
};
|
||||||
|
|
||||||
|
query.filter = AppResponse.processFilters(query.filter, filterableColumns);
|
||||||
|
|
||||||
|
const data = await paginate(query, this.adminRepo, {
|
||||||
|
sortableColumns: ['id', 'username', 'email', 'created_at', 'updated_at'],
|
||||||
|
searchableColumns: ['id', 'username', 'email'],
|
||||||
|
defaultLimit: 15,
|
||||||
|
filterableColumns,
|
||||||
|
defaultSortBy: [['id', 'ASC']],
|
||||||
|
maxLimit: 100,
|
||||||
|
relations: {
|
||||||
|
permissions: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return AppResponse.toPagination<Admin>(data, true, Admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: Admin['id']) {
|
||||||
|
const admin = await this.adminRepo.findOne({ where: { id } });
|
||||||
|
|
||||||
|
if (!admin)
|
||||||
|
throw new NotFoundException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: 'Admin is not found',
|
||||||
|
status_code: HttpStatus.NOT_FOUND,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (admin.is_system_account) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: "Can't delete this account",
|
||||||
|
status_code: HttpStatus.BAD_REQUEST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.adminRepo.delete({ id: admin.id });
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Delete success !' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletes(ids: Admin['id'][], request: Request) {
|
||||||
|
const curAdmin = request['admin'];
|
||||||
|
|
||||||
|
let newIds = ids;
|
||||||
|
|
||||||
|
if (curAdmin) {
|
||||||
|
newIds = ids.filter((item) => item !== curAdmin?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.adminRepo.delete({
|
||||||
|
id: In(ids),
|
||||||
|
is_system_account: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.affected) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: 'No items have been deleted yet.',
|
||||||
|
status_code: HttpStatus.BAD_REQUEST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Delete success !' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async create({
|
||||||
|
password,
|
||||||
|
...data
|
||||||
|
}: Omit<
|
||||||
|
Admin,
|
||||||
|
| 'id'
|
||||||
|
| 'created_at'
|
||||||
|
| 'updated_at'
|
||||||
|
| 'is_system_account'
|
||||||
|
| 'permissions'
|
||||||
|
| 'posts'
|
||||||
|
>) {
|
||||||
|
const hashPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
const newAdmin = await this.adminRepo.save({
|
||||||
|
...data,
|
||||||
|
password: hashPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newAdmin)
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: "Can't create account",
|
||||||
|
status_code: HttpStatus.BAD_REQUEST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Create success' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async grantNewPassword(id: Admin['id'], password: string, request: Request) {
|
||||||
|
const hashPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
const admin = request['admin'];
|
||||||
|
|
||||||
|
if (admin && admin.id == id) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message:
|
||||||
|
'This future not support for this account, Please use change password !',
|
||||||
|
status_code: HttpStatus.BAD_REQUEST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.adminRepo.update(id, {
|
||||||
|
password: hashPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: "Can't create account",
|
||||||
|
status_code: HttpStatus.BAD_REQUEST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Grant success' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: Admin['id'], { permissions, ...data }: Partial<Admin>) {
|
||||||
|
try {
|
||||||
|
const admin = await this.adminRepo.findOne({ where: { id } });
|
||||||
|
|
||||||
|
if (!admin)
|
||||||
|
throw new NotFoundException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: 'Admin is not found',
|
||||||
|
status_code: HttpStatus.NOT_FOUND,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.email) {
|
||||||
|
const isDuplicateAdmin = await this.adminRepo.findOne({
|
||||||
|
where: { email: data.email, id: Not(id) },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDuplicateAdmin) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: 'Email is already exits!',
|
||||||
|
status_code: HttpStatus.BAD_REQUEST,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissions) {
|
||||||
|
const permissionIds = permissions.map((p) => p.id);
|
||||||
|
const permissionsData = await this.permissionRepo.findBy({
|
||||||
|
id: In(permissionIds),
|
||||||
|
});
|
||||||
|
|
||||||
|
admin.permissions = permissionsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.fullname) {
|
||||||
|
admin.fullname = data.fullname;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.adminRepo.save(admin);
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Update success !' });
|
||||||
|
} catch (error) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: error.message,
|
||||||
|
status_code: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import {
|
||||||
|
FilterOperator,
|
||||||
|
FilterSuffix,
|
||||||
|
paginate,
|
||||||
|
PaginateQuery,
|
||||||
|
} from 'nestjs-paginate';
|
||||||
|
import { Column } from 'nestjs-paginate/lib/helper';
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
import Permission from '../entities/permission.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PermissionService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Permission)
|
||||||
|
readonly permissionRepo: Repository<Permission>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getPermissionsByAdmin(adminId: number): Promise<Permission[]> {
|
||||||
|
return await this.permissionRepo
|
||||||
|
.createQueryBuilder('permission')
|
||||||
|
.innerJoin('permission.admins', 'admin')
|
||||||
|
.where('admin.id = :adminId', { adminId })
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async index(query: PaginateQuery) {
|
||||||
|
const filterableColumns: {
|
||||||
|
[key in Column<Permission> | (string & {})]?:
|
||||||
|
| (FilterOperator | FilterSuffix)[]
|
||||||
|
| true;
|
||||||
|
} = {
|
||||||
|
id: true,
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
query.filter = AppResponse.processFilters(query.filter, filterableColumns);
|
||||||
|
|
||||||
|
const data = await paginate(query, this.permissionRepo, {
|
||||||
|
sortableColumns: ['id'],
|
||||||
|
searchableColumns: ['id'],
|
||||||
|
defaultLimit: 15,
|
||||||
|
filterableColumns,
|
||||||
|
defaultSortBy: [['id', 'DESC']],
|
||||||
|
maxLimit: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
return AppResponse.toPagination<Permission>(data, true, Permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { HasExistedValidator } from '@/system/validators/has-existed.validator';
|
||||||
|
import { IsUniqueValidator } from '@/system/validators/is-unique.validator';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [],
|
||||||
|
providers: [IsUniqueValidator, HasExistedValidator],
|
||||||
|
exports: [IsUniqueValidator, HasExistedValidator],
|
||||||
|
})
|
||||||
|
export class AppValidatorsModule {}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Req,
|
||||||
|
UseGuards,
|
||||||
|
Res,
|
||||||
|
Get,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { LoginDto } from './dto/login.dto';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||||
|
|
||||||
|
@Controller('admin/auth')
|
||||||
|
export class AuthController {
|
||||||
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
|
@Post('login')
|
||||||
|
async login(
|
||||||
|
@Body() loginDto: LoginDto,
|
||||||
|
@Res({ passthrough: true }) response: Response,
|
||||||
|
) {
|
||||||
|
return this.authService
|
||||||
|
.validateAdmin(loginDto.username, loginDto.password)
|
||||||
|
.then((admin) => this.authService.login(admin, response));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('logout')
|
||||||
|
async logout(@Res({ passthrough: true }) response: Response) {
|
||||||
|
return await this.authService.logout(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('change-password')
|
||||||
|
async changePassword(
|
||||||
|
@Res({ passthrough: true }) response: Response,
|
||||||
|
@Req() request: Request,
|
||||||
|
@Body() data: ChangePasswordDto,
|
||||||
|
) {
|
||||||
|
return await this.authService.changePassword(data, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('me')
|
||||||
|
async me(@Req() request: Request) {
|
||||||
|
return this.authService.me(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { AdminsModule } from '../admins/admins.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
JwtModule.register({
|
||||||
|
global: true,
|
||||||
|
secret: process.env.SECRET_KEY,
|
||||||
|
signOptions: { expiresIn: '15m' },
|
||||||
|
}),
|
||||||
|
AdminsModule,
|
||||||
|
],
|
||||||
|
controllers: [AuthController],
|
||||||
|
providers: [AuthService],
|
||||||
|
exports: [AuthService],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
Res,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { AdminsService } from '../admins/services/admins.service';
|
||||||
|
import Admin from '../admins/entities/admin.entity';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { Constant } from './ultils/constant';
|
||||||
|
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private readonly adminService: AdminsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validateAdmin(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
message = 'Username or Password is cornet',
|
||||||
|
) {
|
||||||
|
const user = await this.adminService.adminRepo.findOne({
|
||||||
|
where: { username },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message,
|
||||||
|
status_code: HttpStatus.UNAUTHORIZED,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(user: Admin, response: Response) {
|
||||||
|
const payload = { _id: user.id };
|
||||||
|
const accessToken = this.jwtService.sign(payload, { expiresIn: '15m' });
|
||||||
|
const refreshToken = this.jwtService.sign(
|
||||||
|
{ refresh_key: true, ...payload },
|
||||||
|
{ expiresIn: '7d' },
|
||||||
|
);
|
||||||
|
|
||||||
|
response.cookie(Constant.ACCESS_TOKEN, accessToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: 15 * 60 * 1000,
|
||||||
|
sameSite: 'lax',
|
||||||
|
});
|
||||||
|
|
||||||
|
response.cookie(Constant.REFRESH_TOKEN, refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||||
|
sameSite: 'lax',
|
||||||
|
});
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Login Success' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout(@Res() response: Response) {
|
||||||
|
response.cookie(Constant.ACCESS_TOKEN, '', {
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: 0,
|
||||||
|
sameSite: 'lax',
|
||||||
|
});
|
||||||
|
|
||||||
|
response.cookie(Constant.REFRESH_TOKEN, '', {
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: 0,
|
||||||
|
sameSite: 'lax',
|
||||||
|
});
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, { message: 'Logout Success' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async changePassword(
|
||||||
|
data: ChangePasswordDto,
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
) {
|
||||||
|
const admin = request['admin'];
|
||||||
|
|
||||||
|
if (!admin) throw new UnauthorizedException(AppResponse.toResponse(false));
|
||||||
|
|
||||||
|
await this.validateAdmin(
|
||||||
|
admin.username,
|
||||||
|
data.password,
|
||||||
|
'Password is invalid !',
|
||||||
|
);
|
||||||
|
|
||||||
|
const newPassword = await bcrypt.hash(data.newPassword, 10);
|
||||||
|
const result = await this.adminService.adminRepo.update(admin.id, {
|
||||||
|
password: newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.affected)
|
||||||
|
throw new BadRequestException(AppResponse.toResponse(false));
|
||||||
|
|
||||||
|
await this.logout(response);
|
||||||
|
|
||||||
|
return AppResponse.toResponse(true, {
|
||||||
|
message: 'Change password success. Please re-login !',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async me(request: Request) {
|
||||||
|
const admin = request['admin'];
|
||||||
|
|
||||||
|
if (!admin) throw new UnauthorizedException(AppResponse.toResponse(null));
|
||||||
|
|
||||||
|
return AppResponse.toResponse(admin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class ChangePasswordDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
newPassword: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class LoginDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
// auth.middleware.ts
|
||||||
|
|
||||||
|
import {
|
||||||
|
ForbiddenException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
NestMiddleware,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { AdminsService } from '@/modules/admins/services/admins.service';
|
||||||
|
import { AuthService } from '@/modules/auth/auth.service';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
|
import Admin from '@/modules/admins/entities/admin.entity';
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthenticationMiddleware implements NestMiddleware {
|
||||||
|
constructor(
|
||||||
|
private readonly jwtService: JwtService,
|
||||||
|
private readonly adminService: AdminsService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validate(result: Record<string, any>, next: NextFunction) {
|
||||||
|
const admin = await this.adminService.adminRepo.findOne({
|
||||||
|
where: { id: result?._id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!admin) {
|
||||||
|
return next(
|
||||||
|
new UnauthorizedException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'User is invalid',
|
||||||
|
status_code: HttpStatus.UNAUTHORIZED,
|
||||||
|
bonus: { redirect: true },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return admin;
|
||||||
|
}
|
||||||
|
async use(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { access_token, refresh_token }: Record<string, any> = req.cookies;
|
||||||
|
|
||||||
|
if (!access_token && !refresh_token) {
|
||||||
|
return next(
|
||||||
|
new UnauthorizedException(
|
||||||
|
AppResponse.toResponse(false, {
|
||||||
|
message: 'Please login to app',
|
||||||
|
status_code: HttpStatus.UNAUTHORIZED,
|
||||||
|
bonus: { redirect: true },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.jwtService.verify(access_token);
|
||||||
|
|
||||||
|
if (result.refresh_key || !result?._id) {
|
||||||
|
return next(
|
||||||
|
new ForbiddenException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'Token is invalid',
|
||||||
|
status_code: HttpStatus.FORBIDDEN,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const admin = await this.validate(result, next);
|
||||||
|
|
||||||
|
req['admin'] = plainToClass(Admin, admin);
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
const result = await this.jwtService.verify(refresh_token);
|
||||||
|
const admin = (await this.validate(result, next)) as Admin;
|
||||||
|
|
||||||
|
await this.authService.login(admin, res);
|
||||||
|
req['admin'] = plainToClass(Admin, admin);
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
return next(
|
||||||
|
new UnauthorizedException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'Token is expired',
|
||||||
|
status_code: HttpStatus.UNAUTHORIZED,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export class Constant {
|
||||||
|
public static ACCESS_TOKEN = 'access_token';
|
||||||
|
public static REFRESH_TOKEN = 'refresh_token';
|
||||||
|
}
|
||||||
|
|
@ -5,8 +5,9 @@ export class ClientUpdateBidDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
lot_id: string;
|
lot_id: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
close_time: string;
|
close_time: string | null;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export class BidsService {
|
||||||
model: true,
|
model: true,
|
||||||
lot_id: true,
|
lot_id: true,
|
||||||
close_time: true,
|
close_time: true,
|
||||||
|
name: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
query.filter = AppResponse.processFilters(query.filter, filterableColumns);
|
query.filter = AppResponse.processFilters(query.filter, filterableColumns);
|
||||||
|
|
@ -64,8 +65,9 @@ export class BidsService {
|
||||||
'lot_id',
|
'lot_id',
|
||||||
'max_price',
|
'max_price',
|
||||||
'status',
|
'status',
|
||||||
|
'name',
|
||||||
],
|
],
|
||||||
searchableColumns: ['id', 'status', 'model', 'lot_id'],
|
searchableColumns: ['id', 'status', 'model', 'lot_id', 'name'],
|
||||||
defaultLimit: 15,
|
defaultLimit: 15,
|
||||||
filterableColumns,
|
filterableColumns,
|
||||||
defaultSortBy: [['id', 'DESC']],
|
defaultSortBy: [['id', 'DESC']],
|
||||||
|
|
@ -185,7 +187,10 @@ export class BidsService {
|
||||||
return AppResponse.toResponse(true);
|
return AppResponse.toResponse(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clientUpdate(id: Bid['id'], data: ClientUpdateBidDto) {
|
async clientUpdate(
|
||||||
|
id: Bid['id'],
|
||||||
|
{ close_time, ...data }: ClientUpdateBidDto,
|
||||||
|
) {
|
||||||
const bid = await this.bidsRepo.findOne({ where: { id } });
|
const bid = await this.bidsRepo.findOne({ where: { id } });
|
||||||
|
|
||||||
if (!bid)
|
if (!bid)
|
||||||
|
|
@ -198,14 +203,28 @@ export class BidsService {
|
||||||
|
|
||||||
if (!bid.close_time && !bid.start_bid_time) {
|
if (!bid.close_time && !bid.start_bid_time) {
|
||||||
// bid.start_bid_time = new Date().toUTCString();
|
// bid.start_bid_time = new Date().toUTCString();
|
||||||
bid.start_bid_time = subtractMinutes(data.close_time, 5);
|
bid.start_bid_time = subtractMinutes(close_time, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.current_price > bid.max_price + bid.plus_price) {
|
if (
|
||||||
|
data.current_price >= bid.max_price + bid.plus_price ||
|
||||||
|
(bid.close_time && isTimeReached(bid.close_time))
|
||||||
|
) {
|
||||||
|
console.log({
|
||||||
|
a: data.current_price >= bid.max_price + bid.plus_price,
|
||||||
|
b: bid.close_time && !close_time,
|
||||||
|
c: bid.close_time && isTimeReached(bid.close_time),
|
||||||
|
});
|
||||||
|
|
||||||
bid.status = 'out-bid';
|
bid.status = 'out-bid';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Update ' + id);
|
if (
|
||||||
|
close_time &&
|
||||||
|
new Date(close_time).getTime() > new Date(bid.close_time).getTime()
|
||||||
|
) {
|
||||||
|
bid.close_time = close_time;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.bidsRepo.save({
|
const result = await this.bidsRepo.save({
|
||||||
...bid,
|
...bid,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
ExecutionContext,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ThrottlerGuard, ThrottlerLimitDetail } from '@nestjs/throttler';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RateLimitGuard extends ThrottlerGuard {
|
||||||
|
protected async throwThrottlingException(
|
||||||
|
context: ExecutionContext,
|
||||||
|
throttlerLimitDetail: ThrottlerLimitDetail,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new HttpException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'To many request',
|
||||||
|
status_code: HttpStatus.TOO_MANY_REQUESTS,
|
||||||
|
}),
|
||||||
|
HttpStatus.TOO_MANY_REQUESTS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import AppResponse from '@/response/app-response';
|
||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
ForbiddenException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SystemAccountGuard implements CanActivate {
|
||||||
|
canActivate(context: ExecutionContext): boolean {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
|
if (!request.admin || !request.admin.is_system_account) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
AppResponse.toResponse(null, {
|
||||||
|
message: 'You are not allowed to access this resource.',
|
||||||
|
status_code: HttpStatus.FORBIDDEN,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { RequestMethod } from '@nestjs/common';
|
||||||
|
import { RouteInfo } from '@nestjs/common/interfaces';
|
||||||
|
|
||||||
|
export const excludeAuth = [
|
||||||
|
{
|
||||||
|
path: 'admin/auth/login',
|
||||||
|
method: RequestMethod.POST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin/auth/forgot-password',
|
||||||
|
method: RequestMethod.POST,
|
||||||
|
},
|
||||||
|
] as (string | RouteInfo)[];
|
||||||
|
|
||||||
|
export const excludeAuthor = [
|
||||||
|
...excludeAuth,
|
||||||
|
{
|
||||||
|
path: 'admin/auth/change-password',
|
||||||
|
method: RequestMethod.POST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin/auth/logout',
|
||||||
|
method: RequestMethod.POST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin/auth/me',
|
||||||
|
method: RequestMethod.GET,
|
||||||
|
},
|
||||||
|
] as (string | RouteInfo)[];
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { registerDecorator, ValidationOptions } from 'class-validator';
|
||||||
|
import { HasExistedValidator } from '../has-existed.validator';
|
||||||
|
|
||||||
|
export type IsExitedInput = {
|
||||||
|
tableName: string;
|
||||||
|
column: string;
|
||||||
|
queryOption?: Record<string, any>;
|
||||||
|
message?: string;
|
||||||
|
each?: boolean;
|
||||||
|
update?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HasExisted(options: IsExitedInput, validationOptions?: ValidationOptions) {
|
||||||
|
return function (object: any, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'has-exited',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
options: validationOptions,
|
||||||
|
constraints: [options],
|
||||||
|
validator: HasExistedValidator,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { registerDecorator, ValidationOptions } from 'class-validator';
|
||||||
|
import { IsUniqueValidator } from '../is-unique.validator';
|
||||||
|
|
||||||
|
export type IsUniqueInput = {
|
||||||
|
tableName: string;
|
||||||
|
column: string;
|
||||||
|
queryOption?: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function IsUnique(options: IsUniqueInput, validationOptions?: ValidationOptions) {
|
||||||
|
return function (object: any, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'is-unique',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
options: validationOptions,
|
||||||
|
constraints: [options],
|
||||||
|
validator: IsUniqueValidator,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
||||||
|
import { EntityManager, In } from 'typeorm';
|
||||||
|
import { IsExitedInput } from './decorators/has-existed';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@ValidatorConstraint({ name: 'isUnique', async: true })
|
||||||
|
export class HasExistedValidator implements ValidatorConstraintInterface {
|
||||||
|
constructor(private readonly entityManager: EntityManager) {}
|
||||||
|
|
||||||
|
async validate(value: any, args: ValidationArguments) {
|
||||||
|
const { column, tableName, queryOption, each }: IsExitedInput = args.constraints[0];
|
||||||
|
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
if (!each) {
|
||||||
|
result = await this.entityManager
|
||||||
|
.getRepository(tableName)
|
||||||
|
.createQueryBuilder(tableName)
|
||||||
|
.where({ [column]: value, ...(queryOption || {}) })
|
||||||
|
.getOne();
|
||||||
|
} else {
|
||||||
|
const response = await this.entityManager
|
||||||
|
.getRepository(tableName)
|
||||||
|
.createQueryBuilder(tableName)
|
||||||
|
.where({ [column]: In(value), ...(queryOption || {}) })
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
result = response.length === value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!result;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage(args: ValidationArguments) {
|
||||||
|
const { column, message }: IsExitedInput = args.constraints[0];
|
||||||
|
|
||||||
|
return message || `${column} $value is not found`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
||||||
|
import { EntityManager } from 'typeorm';
|
||||||
|
import { IsUniqueInput } from './decorators/is-unique';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@ValidatorConstraint({ name: 'isUnique', async: true })
|
||||||
|
export class IsUniqueValidator implements ValidatorConstraintInterface {
|
||||||
|
constructor(private readonly entityManager: EntityManager) {}
|
||||||
|
|
||||||
|
async validate(value: string, args: ValidationArguments) {
|
||||||
|
const { column, tableName, queryOption }: IsUniqueInput = args.constraints[0];
|
||||||
|
|
||||||
|
const result = await this.entityManager
|
||||||
|
.getRepository(tableName)
|
||||||
|
.createQueryBuilder(tableName)
|
||||||
|
.where({ [column]: value, ...(queryOption || {}) })
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
return !result;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage(args: ValidationArguments) {
|
||||||
|
const { column }: IsUniqueInput = args.constraints[0];
|
||||||
|
|
||||||
|
return `${column} $value already exists`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": false,
|
||||||
"noFallthroughCasesInSwitch": false
|
"noFallthroughCasesInSwitch": false,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { io } from 'socket.io-client';
|
||||||
import { createApiBid, createBidProduct, deleteProfile, shouldUpdateProductTab } from './service/app-service.js';
|
import { createApiBid, createBidProduct, deleteProfile, shouldUpdateProductTab } from './service/app-service.js';
|
||||||
import browser from './system/browser.js';
|
import browser from './system/browser.js';
|
||||||
import configs from './system/config.js';
|
import configs from './system/config.js';
|
||||||
import { isTimeReached, safeClosePage } from './system/utils.js';
|
import { delay, isTimeReached, safeClosePage } from './system/utils.js';
|
||||||
|
|
||||||
let MANAGER_BIDS = [];
|
let MANAGER_BIDS = [];
|
||||||
|
|
||||||
|
|
@ -58,10 +58,10 @@ const handleUpdateProductTabs = (data) => {
|
||||||
// const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
// const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
||||||
|
|
||||||
// for (const productTab of productTabs) {
|
// for (const productTab of productTabs) {
|
||||||
|
// // Tìm parent context nếu chưa có
|
||||||
// if (!productTab.parent_browser_context) {
|
// if (!productTab.parent_browser_context) {
|
||||||
// const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
// const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
||||||
|
// productTab.parent_browser_context = parent?.browser_context;
|
||||||
// productTab.parent_browser_context = parent.browser_context;
|
|
||||||
|
|
||||||
// if (!productTab.parent_browser_context) {
|
// if (!productTab.parent_browser_context) {
|
||||||
// console.log(`🔄 Waiting for parent process to start... (Product ID: ${productTab.id})`);
|
// console.log(`🔄 Waiting for parent process to start... (Product ID: ${productTab.id})`);
|
||||||
|
|
@ -69,57 +69,37 @@ const handleUpdateProductTabs = (data) => {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if (!productTab.first_bid) {
|
// // Kết nối Puppeteer nếu chưa có page_context
|
||||||
// console.log(`🎯 Tracking out-bid event for Product ID: ${productTab.id}`);
|
|
||||||
|
|
||||||
// const updatedAt = new Date(productTab.updated_at).getTime();
|
|
||||||
// const now = Date.now();
|
|
||||||
|
|
||||||
// if (!productTab.page_context) {
|
|
||||||
// await productTab.puppeteer_connect();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (productTab.page_context.url() !== productTab.url) {
|
|
||||||
// await productTab.gotoLink();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (now - updatedAt < ONE_MINUTE) {
|
|
||||||
// console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// await productTab.update();
|
|
||||||
// console.log(`🔄 Updating Product ID: ${productTab.id}...`);
|
|
||||||
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) {
|
|
||||||
// console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`);
|
|
||||||
|
|
||||||
// const updatedAt = new Date(productTab.updated_at).getTime();
|
|
||||||
// const now = Date.now();
|
|
||||||
|
|
||||||
// if (!productTab.page_context) {
|
|
||||||
// await productTab.puppeteer_connect();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (productTab.page_context.url() !== productTab.url) {
|
|
||||||
// await productTab.gotoLink();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (now - updatedAt < ONE_MINUTE) {
|
|
||||||
// console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// await productTab.update();
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!productTab.page_context) {
|
// if (!productTab.page_context) {
|
||||||
// console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`);
|
// console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`);
|
||||||
// await productTab.puppeteer_connect();
|
// await productTab.puppeteer_connect();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// // Nếu URL thay đổi, điều hướng đến URL mới
|
||||||
|
// if (productTab.page_context.url() !== productTab.url) {
|
||||||
|
// await productTab.gotoLink();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Kiểm tra nếu cần cập nhật trước khi gọi update()
|
||||||
|
// if (shouldUpdateProductTab(productTab)) {
|
||||||
|
// console.log(`🔄 Updating Product ID: ${productTab.id}...`);
|
||||||
|
// await productTab.update();
|
||||||
|
// } else {
|
||||||
|
// console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Nếu chưa có first_bid (trạng thái chưa đặt giá)
|
||||||
|
// if (!productTab.first_bid) {
|
||||||
|
// console.log(`🎯 Tracking out-bid event for Product ID: ${productTab.id}`);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Nếu chưa đến giờ bid
|
||||||
|
// if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) {
|
||||||
|
// console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
// console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
// console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
||||||
// await productTab.action();
|
// await productTab.action();
|
||||||
// }
|
// }
|
||||||
|
|
@ -127,22 +107,29 @@ const handleUpdateProductTabs = (data) => {
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const tracking = async () => {
|
const tracking = async () => {
|
||||||
if (_INTERVAL_TRACKING_ID) {
|
console.log('🚀 Tracking process started...');
|
||||||
clearInterval(_INTERVAL_TRACKING_ID);
|
|
||||||
_INTERVAL_TRACKING_ID = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_INTERVAL_TRACKING_ID = setInterval(async () => {
|
while (true) {
|
||||||
|
console.log('🔍 Scanning active bids...');
|
||||||
const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
||||||
|
|
||||||
|
for (const apiBid of MANAGER_BIDS) {
|
||||||
|
if (apiBid.page_context) continue;
|
||||||
|
|
||||||
|
console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
|
||||||
|
await apiBid.listen_events();
|
||||||
|
}
|
||||||
|
|
||||||
for (const productTab of productTabs) {
|
for (const productTab of productTabs) {
|
||||||
|
console.log(`📌 Processing Product ID: ${productTab.id}`);
|
||||||
|
|
||||||
// Tìm parent context nếu chưa có
|
// Tìm parent context nếu chưa có
|
||||||
if (!productTab.parent_browser_context) {
|
if (!productTab.parent_browser_context) {
|
||||||
const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
||||||
productTab.parent_browser_context = parent?.browser_context;
|
productTab.parent_browser_context = parent?.browser_context;
|
||||||
|
|
||||||
if (!productTab.parent_browser_context) {
|
if (!productTab.parent_browser_context) {
|
||||||
console.log(`🔄 Waiting for parent process to start... (Product ID: ${productTab.id})`);
|
console.log(`⏳ Waiting for parent process to start... (Product ID: ${productTab.id})`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +142,7 @@ const tracking = async () => {
|
||||||
|
|
||||||
// Nếu URL thay đổi, điều hướng đến URL mới
|
// Nếu URL thay đổi, điều hướng đến URL mới
|
||||||
if (productTab.page_context.url() !== productTab.url) {
|
if (productTab.page_context.url() !== productTab.url) {
|
||||||
|
console.log(`🔄 Redirecting to new URL for Product ID: ${productTab.id}`);
|
||||||
await productTab.gotoLink();
|
await productTab.gotoLink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +156,7 @@ const tracking = async () => {
|
||||||
|
|
||||||
// Nếu chưa có first_bid (trạng thái chưa đặt giá)
|
// Nếu chưa có first_bid (trạng thái chưa đặt giá)
|
||||||
if (!productTab.first_bid) {
|
if (!productTab.first_bid) {
|
||||||
console.log(`🎯 Tracking out-bid event for Product ID: ${productTab.id}`);
|
console.log(`🎯 Waiting for first bid for Product ID: ${productTab.id}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,81 +169,70 @@ const tracking = async () => {
|
||||||
console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
||||||
await productTab.action();
|
await productTab.action();
|
||||||
}
|
}
|
||||||
}, configs.AUTO_TRACKING_DELAY);
|
|
||||||
|
console.log('🧹 Cleaning up unused tabs...');
|
||||||
|
await clearLazyTab();
|
||||||
|
|
||||||
|
console.log('📊 Tracking work status...');
|
||||||
|
workTracking();
|
||||||
|
|
||||||
|
console.log(`⏳ Waiting ${configs.AUTO_TRACKING_DELAY / 1000} seconds before the next iteration...`);
|
||||||
|
await delay(configs.AUTO_TRACKING_DELAY);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearLazyTab = async () => {
|
const clearLazyTab = async () => {
|
||||||
if (_CLEAR_LAZY_TAB_ID) {
|
if (!global.IS_CLEANING) return;
|
||||||
clearInterval(_CLEAR_LAZY_TAB_ID);
|
|
||||||
_CLEAR_LAZY_TAB_ID = null;
|
if (!browser) {
|
||||||
|
console.warn('⚠️ Browser is not available or disconnected.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_CLEAR_LAZY_TAB_ID = setInterval(async () => {
|
const pages = await browser.pages();
|
||||||
if (!global.IS_CLEANING) return;
|
|
||||||
|
|
||||||
if (!browser) {
|
// Lấy danh sách URL từ flattenedArray
|
||||||
console.warn('⚠️ Browser is not available or disconnected.');
|
const activeUrls = _.flatMap(MANAGER_BIDS, (item) => [item.url, ...item.children.map((child) => child.url)]).filter(Boolean); // Lọc bỏ null hoặc undefined
|
||||||
clearInterval(_CLEAR_LAZY_TAB_ID);
|
|
||||||
_CLEAR_LAZY_TAB_ID = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
console.log(
|
||||||
const pages = await browser.pages();
|
'🔍 Page URLs:',
|
||||||
|
pages.map((page) => page.url()),
|
||||||
|
);
|
||||||
|
|
||||||
// Lấy danh sách URL từ flattenedArray
|
for (const page of pages) {
|
||||||
const activeUrls = _.flatMap(MANAGER_BIDS, (item) => [item.url, ...item.children.map((child) => child.url)]).filter(Boolean); // Lọc bỏ null hoặc undefined
|
const pageUrl = page.url();
|
||||||
|
|
||||||
console.log(
|
// 🔥 Bỏ qua tab 'about:blank' hoặc tab không có URL
|
||||||
'🔍 Page URLs:',
|
if (!pageUrl || pageUrl === 'about:blank') continue;
|
||||||
pages.map((page) => page.url()),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const page of pages) {
|
if (!activeUrls.includes(pageUrl)) {
|
||||||
const pageUrl = page.url();
|
if (!page.isClosed() && browser.isConnected()) {
|
||||||
|
try {
|
||||||
// 🔥 Bỏ qua tab 'about:blank' hoặc tab không có URL
|
await page.close();
|
||||||
if (!pageUrl || pageUrl === 'about:blank') continue;
|
console.log(`🛑 Closing unused tab: ${pageUrl}`);
|
||||||
|
} catch (err) {
|
||||||
if (!activeUrls.includes(pageUrl)) {
|
console.warn(`⚠️ Error closing tab ${pageUrl}:`, err.message);
|
||||||
if (!page.isClosed() && browser.isConnected()) {
|
|
||||||
try {
|
|
||||||
await page.close();
|
|
||||||
console.log(`🛑 Closing unused tab: ${pageUrl}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`⚠️ Error closing tab ${pageUrl}:`, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error('❌ Error in clearLazyTab:', err.message);
|
|
||||||
}
|
}
|
||||||
}, configs.AUTO_TRACKING_CLEANING);
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.log('CLEAR LAZY TAB ERROR: ', error.message);
|
console.error('❌ Error in clearLazyTab:', err.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const workTracking = () => {
|
const workTracking = async () => {
|
||||||
try {
|
try {
|
||||||
if (_WORK_TRACKING_ID) {
|
const activeData = _.flatMap(MANAGER_BIDS, (item) => [item, ...item.children]);
|
||||||
clearInterval(_WORK_TRACKING_ID);
|
|
||||||
_WORK_TRACKING_ID = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_WORK_TRACKING_ID = setInterval(() => {
|
for (const item of activeData) {
|
||||||
const activeData = _.flatMap(MANAGER_BIDS, (item) => [item, ...item.children]);
|
if (item.page_context && !item.page_context.isClosed()) {
|
||||||
|
item.handleTakeWorkSnapshot();
|
||||||
for (const item of activeData) {
|
|
||||||
if (item.page_context && !item.page_context.isClosed()) {
|
|
||||||
item.handleTakeWorkSnapshot();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 10000);
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Loi oi day');
|
console.log('Lỗi rồi:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -277,7 +254,7 @@ const workTracking = () => {
|
||||||
|
|
||||||
handleUpdateProductTabs(data);
|
handleUpdateProductTabs(data);
|
||||||
|
|
||||||
await Promise.all(MANAGER_BIDS.map((apiBid) => apiBid.listen_events()));
|
// await Promise.all(MANAGER_BIDS.map((apiBid) => apiBid.listen_events()));
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('webUpdated', async (data) => {
|
socket.on('webUpdated', async (data) => {
|
||||||
|
|
@ -303,7 +280,7 @@ const workTracking = () => {
|
||||||
// AUTO TRACKING
|
// AUTO TRACKING
|
||||||
tracking();
|
tracking();
|
||||||
|
|
||||||
clearLazyTab();
|
// clearLazyTab();
|
||||||
|
|
||||||
workTracking();
|
// workTracking();
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import CONSTANTS from '../system/constants.js';
|
import CONSTANTS from '../system/constants.js';
|
||||||
import { takeSnapshot } from '../system/utils.js';
|
import { takeSnapshot } from '../system/utils.js';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export class Bid {
|
export class Bid {
|
||||||
type;
|
type;
|
||||||
|
|
@ -14,9 +15,14 @@ export class Bid {
|
||||||
this.puppeteer_connect = puppeteer_connect;
|
this.puppeteer_connect = puppeteer_connect;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleTakeWorkSnapshot() {
|
handleTakeWorkSnapshot = _.debounce(async () => {
|
||||||
if (!this.page_context) return;
|
if (!this.page_context) return;
|
||||||
|
|
||||||
await takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
|
try {
|
||||||
}
|
console.log(`✅ Page loaded. Taking snapshot for Product ID: ${this.id}`);
|
||||||
|
takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error taking snapshot for Product ID: ${this.id}:`, error.message);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
if (!isNumber(price_value)) {
|
if (!isNumber(price_value)) {
|
||||||
console.log("Can't get PRICE_VALUE ❌");
|
console.log("Can't get PRICE_VALUE ❌");
|
||||||
await takeSnapshot(page, this, 'price-value-null');
|
await takeSnapshot(page, this, 'price-value-null');
|
||||||
// await safeClosePage(this);
|
|
||||||
|
|
||||||
return { result: false, bid_price: 0 };
|
return { result: false, bid_price: 0 };
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +26,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
if (bid_price > this.max_price) {
|
if (bid_price > this.max_price) {
|
||||||
console.log('PRICE BID is more than MAX_VALUE => STOP BID THIS PRODUCT ❌');
|
console.log('PRICE BID is more than MAX_VALUE => STOP BID THIS PRODUCT ❌');
|
||||||
await takeSnapshot(page, this, 'price-bid-more-than');
|
await takeSnapshot(page, this, 'price-bid-more-than');
|
||||||
// await safeClosePage(this);
|
|
||||||
|
|
||||||
await outBid(this.id);
|
await outBid(this.id);
|
||||||
|
|
||||||
|
|
@ -83,7 +81,7 @@ export class GraysProductBid extends ProductBid {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async isCloseProduct(page) {
|
async isCloseProduct() {
|
||||||
const close_time = await this.getCloseTime();
|
const close_time = await this.getCloseTime();
|
||||||
|
|
||||||
if (!close_time) {
|
if (!close_time) {
|
||||||
|
|
@ -153,24 +151,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update = async () => {
|
|
||||||
// if (!this.page_context) return;
|
|
||||||
|
|
||||||
// const page = this.page_context;
|
|
||||||
|
|
||||||
// const close_time = await this.getCloseTime();
|
|
||||||
// const price_value = (await page.$eval('#priceValue', (el) => el.value)) || null;
|
|
||||||
// const lot_id = await page.$eval('#lotId', (el) => el.value);
|
|
||||||
// const name = (await page.$eval('#placebid-sticky > div:nth-child(2) > div > h3', (el) => el.innerText)) || null;
|
|
||||||
// const current_price =
|
|
||||||
// (await page.$eval('#biddableLot > form > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div > span > span', (el) => el.innerText)) || null;
|
|
||||||
|
|
||||||
// console.log(`📌 Product Info: Lot ID: ${lot_id}, Name: ${name}, Current Price: ${current_price}, Reserve price ${price_value}`);
|
|
||||||
|
|
||||||
// this.handleUpdateBid({ lot_id, reserve_price: price_value, close_time, name, current_price: current_price ? extractNumber(current_price) : null });
|
|
||||||
|
|
||||||
// return { price_value, lot_id, name, current_price };
|
|
||||||
// };
|
|
||||||
update = async () => {
|
update = async () => {
|
||||||
if (!this.page_context) return;
|
if (!this.page_context) return;
|
||||||
|
|
||||||
|
|
@ -198,13 +178,16 @@ export class GraysProductBid extends ProductBid {
|
||||||
|
|
||||||
console.log(`📌 Product Info: Lot ID: ${lot_id}, Name: ${name}, Current Price: ${current_price}, Reserve price: ${price_value}`);
|
console.log(`📌 Product Info: Lot ID: ${lot_id}, Name: ${name}, Current Price: ${current_price}, Reserve price: ${price_value}`);
|
||||||
|
|
||||||
const data = removeFalsyValues({
|
const data = removeFalsyValues(
|
||||||
lot_id,
|
{
|
||||||
reserve_price: price_value,
|
lot_id,
|
||||||
close_time: String(close_time),
|
reserve_price: price_value,
|
||||||
name,
|
close_time: close_time ? String(close_time) : null,
|
||||||
current_price: current_price ? extractNumber(current_price) : null,
|
name,
|
||||||
});
|
current_price: current_price ? extractNumber(current_price) : null,
|
||||||
|
},
|
||||||
|
['close_time'],
|
||||||
|
);
|
||||||
|
|
||||||
this.handleUpdateBid(data);
|
this.handleUpdateBid(data);
|
||||||
|
|
||||||
|
|
@ -223,7 +206,7 @@ export class GraysProductBid extends ProductBid {
|
||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|
||||||
const { close_time, ...isCloseProduct } = await this.isCloseProduct(page);
|
const { close_time, ...isCloseProduct } = await this.isCloseProduct();
|
||||||
if (isCloseProduct.result) {
|
if (isCloseProduct.result) {
|
||||||
console.log('❌ The product is closed, cannot place a bid.');
|
console.log('❌ The product is closed, cannot place a bid.');
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -135,14 +135,34 @@ export class ProductBid extends Bid {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async gotoLink() {
|
||||||
|
// const page = this.page_context;
|
||||||
|
// console.log('🔄 Starting the bidding process...');
|
||||||
|
|
||||||
|
// await page.goto(this.url, { waitUntil: 'networkidle2' });
|
||||||
|
// console.log(`✅ Navigated to: ${this.url}`);
|
||||||
|
|
||||||
|
// await page.bringToFront();
|
||||||
|
// console.log('👀 Brought the tab to the foreground.');
|
||||||
|
// }
|
||||||
async gotoLink() {
|
async gotoLink() {
|
||||||
const page = this.page_context;
|
const page = this.page_context;
|
||||||
|
|
||||||
|
if (page.isClosed()) {
|
||||||
|
console.error('❌ Page has been closed, cannot navigate.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('🔄 Starting the bidding process...');
|
console.log('🔄 Starting the bidding process...');
|
||||||
|
|
||||||
await page.goto(this.url, { waitUntil: 'networkidle2' });
|
try {
|
||||||
console.log(`✅ Navigated to: ${this.url}`);
|
await page.goto(this.url, { waitUntil: 'networkidle2' });
|
||||||
|
console.log(`✅ Navigated to: ${this.url}`);
|
||||||
|
|
||||||
await page.bringToFront();
|
await page.bringToFront();
|
||||||
console.log('👀 Brought the tab to the foreground.');
|
console.log('👀 Brought the tab to the foreground.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error during navigation:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export const takeSnapshot = async (page, item, imageName, type = CONSTANTS.TYPE_
|
||||||
if (!page || page.isClosed()) return;
|
if (!page || page.isClosed()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
global.IS_CLEANING = false;
|
||||||
const baseDir = path.join(CONSTANTS.ERROR_IMAGES_PATH, item.type, String(item.id)); // Thư mục theo lot_id
|
const baseDir = path.join(CONSTANTS.ERROR_IMAGES_PATH, item.type, String(item.id)); // Thư mục theo lot_id
|
||||||
const typeDir = path.join(baseDir, type); // Thư mục con theo type
|
const typeDir = path.join(baseDir, type); // Thư mục con theo type
|
||||||
|
|
||||||
|
|
@ -48,6 +49,8 @@ export const takeSnapshot = async (page, item, imageName, type = CONSTANTS.TYPE_
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error when snapshot: ' + error.message);
|
console.log('Error when snapshot: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
global.IS_CLEANING = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue