Deploy to production #11
			
				
			
		
		
		
	| 
						 | 
					@ -19,6 +19,20 @@ export const login = async (credentials: { username: string; password: string })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const checkStatus = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const { data } = await axios({
 | 
				
			||||||
 | 
					            url: 'auth/check-status',
 | 
				
			||||||
 | 
					            withCredentials: true,
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data as IResponse<boolean>;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        handleError(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const logout = async () => {
 | 
					export const logout = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const { data } = await axios({
 | 
					        const { data } = await axios({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					import { generateNestParams, handleError, handleSuccess } from '.';
 | 
				
			||||||
 | 
					import axios from '../lib/axios';
 | 
				
			||||||
 | 
					import { IKey } from '../system/type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getKeys = async (params: Record<string, string | number>) => {
 | 
				
			||||||
 | 
					    return await axios({
 | 
				
			||||||
 | 
					        url: 'keys',
 | 
				
			||||||
 | 
					        params: generateNestParams(params),
 | 
				
			||||||
 | 
					        withCredentials: true,
 | 
				
			||||||
 | 
					        method: 'GET',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createKey = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const { data } = await axios({
 | 
				
			||||||
 | 
					            url: 'keys',
 | 
				
			||||||
 | 
					            withCredentials: true,
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleSuccess(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        handleError(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteKey = async (key: IKey) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const { data } = await axios({
 | 
				
			||||||
 | 
					            url: 'keys/' + key.id,
 | 
				
			||||||
 | 
					            withCredentials: true,
 | 
				
			||||||
 | 
					            method: 'DELETE',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleSuccess(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        handleError(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deletesKey = async (keys: IKey[]) => {
 | 
				
			||||||
 | 
					    const ids = keys.reduce((prev, cur) => {
 | 
				
			||||||
 | 
					        prev.push(cur.id);
 | 
				
			||||||
 | 
					        return prev;
 | 
				
			||||||
 | 
					    }, [] as number[]);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        const { data } = await axios({
 | 
				
			||||||
 | 
					            url: 'keys/deletes',
 | 
				
			||||||
 | 
					            withCredentials: true,
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            data: {
 | 
				
			||||||
 | 
					                ids,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handleSuccess(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        handleError(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,14 @@
 | 
				
			||||||
import { Avatar, Button, LoadingOverlay, Menu, Modal, PasswordInput } from '@mantine/core';
 | 
					import { Avatar, Button, LoadingOverlay, Menu, Modal, PasswordInput } from '@mantine/core';
 | 
				
			||||||
import { useForm, zodResolver } from '@mantine/form';
 | 
					import { useForm, zodResolver } from '@mantine/form';
 | 
				
			||||||
import { useDisclosure } from '@mantine/hooks';
 | 
					import { useDisclosure } from '@mantine/hooks';
 | 
				
			||||||
import { IconLogout, IconSettings, IconUser } from '@tabler/icons-react';
 | 
					import { IconKey, IconLogout, IconSettings, IconUser } from '@tabler/icons-react';
 | 
				
			||||||
 | 
					import { useState } from 'react';
 | 
				
			||||||
import { useNavigate } from 'react-router';
 | 
					import { useNavigate } from 'react-router';
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
import { z } from 'zod';
 | 
					import { z } from 'zod';
 | 
				
			||||||
import { changePassword, logout } from '../apis/auth';
 | 
					import { changePassword, logout } from '../apis/auth';
 | 
				
			||||||
import { useConfirmStore } from '../lib/zustand/use-confirm';
 | 
					import { useConfirmStore } from '../lib/zustand/use-confirm';
 | 
				
			||||||
import Links from '../system/links';
 | 
					import Links from '../system/links';
 | 
				
			||||||
import { useState } from 'react';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const schema = z
 | 
					const schema = z
 | 
				
			||||||
    .object({
 | 
					    .object({
 | 
				
			||||||
| 
						 | 
					@ -92,6 +93,9 @@ export default function UserMenu() {
 | 
				
			||||||
                    <Menu.Item onClick={open} leftSection={<IconSettings size={14} />}>
 | 
					                    <Menu.Item onClick={open} leftSection={<IconSettings size={14} />}>
 | 
				
			||||||
                        Change password
 | 
					                        Change password
 | 
				
			||||||
                    </Menu.Item>
 | 
					                    </Menu.Item>
 | 
				
			||||||
 | 
					                    <Menu.Item component={Link} to={Links.GENERATE_KEYS} leftSection={<IconKey size={14} />}>
 | 
				
			||||||
 | 
					                        Keys
 | 
				
			||||||
 | 
					                    </Menu.Item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <Menu.Divider />
 | 
					                    <Menu.Divider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ export default function PrivateLayout() {
 | 
				
			||||||
                    <Logo />
 | 
					                    <Logo />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <Box className="flex items-center gap-4">
 | 
					                    <Box className="flex items-center gap-4">
 | 
				
			||||||
                        {Links.MENUS.map((menu, index) => (
 | 
					                        {Links.MENUS.filter((i) => i.show).map((menu, index) => (
 | 
				
			||||||
                            <Button
 | 
					                            <Button
 | 
				
			||||||
                                size="xs"
 | 
					                                size="xs"
 | 
				
			||||||
                                component={Link}
 | 
					                                component={Link}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,12 @@
 | 
				
			||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
 | 
					/* eslint-disable @typescript-eslint/no-explicit-any */
 | 
				
			||||||
import { useEffect, useState } from 'react';
 | 
					 | 
				
			||||||
import { Box, Text, Title } from '@mantine/core';
 | 
					import { Box, Text, Title } from '@mantine/core';
 | 
				
			||||||
 | 
					import { useEffect, useRef, useState } from 'react';
 | 
				
			||||||
import io from 'socket.io-client';
 | 
					import io from 'socket.io-client';
 | 
				
			||||||
import { WorkingPage } from '../components/dashboard';
 | 
					import { WorkingPage } from '../components/dashboard';
 | 
				
			||||||
import { IBid, IWebBid } from '../system/type';
 | 
					import { IBid, IWebBid } from '../system/type';
 | 
				
			||||||
 | 
					import { checkStatus } from '../apis/auth';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const socket = io(import.meta.env.VITE_SOCKET_URL, {
 | 
					const socket = io(`${import.meta.env.VITE_SOCKET_URL}/admin-bid-ws`, {
 | 
				
			||||||
    autoConnect: true,
 | 
					    autoConnect: true,
 | 
				
			||||||
    transports: ['websocket'],
 | 
					    transports: ['websocket'],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -13,18 +14,24 @@ const socket = io(import.meta.env.VITE_SOCKET_URL, {
 | 
				
			||||||
export default function DashBoard() {
 | 
					export default function DashBoard() {
 | 
				
			||||||
    const [workingData, setWorkingData] = useState<(IWebBid & { type: string })[] | (IBid & { type: string })[]>([]);
 | 
					    const [workingData, setWorkingData] = useState<(IWebBid & { type: string })[] | (IBid & { type: string })[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const RETRY_CONNECT = useRef(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        socket.connect();
 | 
					        socket.connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        socket.on('connect', () => {
 | 
					        socket.on('connect', () => {
 | 
				
			||||||
            console.log('✅ WebSocket connected:', socket.id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 🔥 Gửi yêu cầu lấy dữ liệu ngay khi kết nối
 | 
					 | 
				
			||||||
            socket.emit('getBidsData');
 | 
					            socket.emit('getBidsData');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        socket.on('disconnect', () => {
 | 
					        socket.on('disconnect', async () => {
 | 
				
			||||||
            console.log('❌ WebSocket disconnected');
 | 
					            if (RETRY_CONNECT.current > 0) {
 | 
				
			||||||
 | 
					                await checkStatus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                socket.connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                RETRY_CONNECT.current--;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        socket.on('adminBidsUpdated', (data: IWebBid[]) => {
 | 
					        socket.on('adminBidsUpdated', (data: IWebBid[]) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,168 @@
 | 
				
			||||||
 | 
					import { ActionIcon, Box, CopyButton, Menu, TextInput } from '@mantine/core';
 | 
				
			||||||
 | 
					import { IconCopy, IconMenu, IconTrash } from '@tabler/icons-react';
 | 
				
			||||||
 | 
					import { useMemo, useRef } from 'react';
 | 
				
			||||||
 | 
					import { createKey, deleteKey, deletesKey, getKeys } from '../apis/generate-key';
 | 
				
			||||||
 | 
					import Table from '../lib/table/table';
 | 
				
			||||||
 | 
					import { IColumn, TRefTableFn } from '../lib/table/type';
 | 
				
			||||||
 | 
					import { useConfirmStore } from '../lib/zustand/use-confirm';
 | 
				
			||||||
 | 
					import { IKey } from '../system/type';
 | 
				
			||||||
 | 
					import { formatTime } from '../utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function GenerateKeys() {
 | 
				
			||||||
 | 
					    const refTableFn: TRefTableFn<IKey> = useRef({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { setConfirm } = useConfirmStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const columns: IColumn<IKey>[] = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: 'id',
 | 
				
			||||||
 | 
					            title: 'ID',
 | 
				
			||||||
 | 
					            typeFilter: 'number',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: 'client_key',
 | 
				
			||||||
 | 
					            title: 'Client key',
 | 
				
			||||||
 | 
					            typeFilter: 'text',
 | 
				
			||||||
 | 
					            renderRow(row) {
 | 
				
			||||||
 | 
					                return (
 | 
				
			||||||
 | 
					                    <CopyButton value={row.client_key}>
 | 
				
			||||||
 | 
					                        {({ copied, copy }) => (
 | 
				
			||||||
 | 
					                            <TextInput
 | 
				
			||||||
 | 
					                                size="xs"
 | 
				
			||||||
 | 
					                                value={row.client_key}
 | 
				
			||||||
 | 
					                                rightSection={
 | 
				
			||||||
 | 
					                                    <ActionIcon className="z-30 cursor-pointer" size="xs" color={copied ? 'teal' : 'blue'} onClick={copy}>
 | 
				
			||||||
 | 
					                                        <IconCopy size={12} />
 | 
				
			||||||
 | 
					                                    </ActionIcon>
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
 | 
					                    </CopyButton>
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            key: 'created_at',
 | 
				
			||||||
 | 
					            title: 'Create at',
 | 
				
			||||||
 | 
					            typeFilter: 'none',
 | 
				
			||||||
 | 
					            renderRow(row) {
 | 
				
			||||||
 | 
					                return <span className="text-sm">{formatTime(row.created_at)}</span>;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleDelete = (bid: IKey) => {
 | 
				
			||||||
 | 
					        setConfirm({
 | 
				
			||||||
 | 
					            title: 'Delete ?',
 | 
				
			||||||
 | 
					            message: 'This key will be delete',
 | 
				
			||||||
 | 
					            handleOk: async () => {
 | 
				
			||||||
 | 
					                await deleteKey(bid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (refTableFn.current?.fetchData) {
 | 
				
			||||||
 | 
					                    refTableFn.current.fetchData();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const table = useMemo(() => {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <Table
 | 
				
			||||||
 | 
					                actionsOptions={{
 | 
				
			||||||
 | 
					                    actions: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            key: 'create',
 | 
				
			||||||
 | 
					                            title: 'New key',
 | 
				
			||||||
 | 
					                            callback: async () => {
 | 
				
			||||||
 | 
					                                setConfirm({
 | 
				
			||||||
 | 
					                                    okButton: {
 | 
				
			||||||
 | 
					                                        color: 'lime',
 | 
				
			||||||
 | 
					                                        value: 'Ok',
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                    title: 'A new key will generate',
 | 
				
			||||||
 | 
					                                    handleOk: async () => {
 | 
				
			||||||
 | 
					                                        await createKey();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        if (!refTableFn.current?.fetchData) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        refTableFn.current.fetchData();
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            key: 'delete',
 | 
				
			||||||
 | 
					                            title: 'Delete',
 | 
				
			||||||
 | 
					                            callback: (data) => {
 | 
				
			||||||
 | 
					                                if (!data.length) return;
 | 
				
			||||||
 | 
					                                setConfirm({
 | 
				
			||||||
 | 
					                                    title: 'Delete',
 | 
				
			||||||
 | 
					                                    message: `${data.length} keys will be delete`,
 | 
				
			||||||
 | 
					                                    handleOk: async () => {
 | 
				
			||||||
 | 
					                                        const result = await deletesKey(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        if (!result) return;
 | 
				
			||||||
 | 
					                                        if (refTableFn.current.fetchData) {
 | 
				
			||||||
 | 
					                                            refTableFn.current.fetchData();
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            disabled: (data) => data.length <= 0,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                refTableFn={refTableFn}
 | 
				
			||||||
 | 
					                striped
 | 
				
			||||||
 | 
					                showLoading={true}
 | 
				
			||||||
 | 
					                highlightOnHover
 | 
				
			||||||
 | 
					                styleDefaultHead={{
 | 
				
			||||||
 | 
					                    justifyContent: 'flex-start',
 | 
				
			||||||
 | 
					                    width: 'fit-content',
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                options={{
 | 
				
			||||||
 | 
					                    query: getKeys,
 | 
				
			||||||
 | 
					                    pathToData: 'data.data',
 | 
				
			||||||
 | 
					                    keyOptions: {
 | 
				
			||||||
 | 
					                        last_page: 'lastPage',
 | 
				
			||||||
 | 
					                        per_page: 'perPage',
 | 
				
			||||||
 | 
					                        from: 'from',
 | 
				
			||||||
 | 
					                        to: 'to',
 | 
				
			||||||
 | 
					                        total: 'total',
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                actions={{
 | 
				
			||||||
 | 
					                    title: <Box className="w-full text-center">Action</Box>,
 | 
				
			||||||
 | 
					                    body: (row) => {
 | 
				
			||||||
 | 
					                        return (
 | 
				
			||||||
 | 
					                            <Menu shadow="md" width={200}>
 | 
				
			||||||
 | 
					                                <Menu.Target>
 | 
				
			||||||
 | 
					                                    <Box onClick={(e) => e.stopPropagation()} className="flex w-full items-center justify-center">
 | 
				
			||||||
 | 
					                                        <ActionIcon size="sm" variant="light">
 | 
				
			||||||
 | 
					                                            <IconMenu size={14} />
 | 
				
			||||||
 | 
					                                        </ActionIcon>
 | 
				
			||||||
 | 
					                                    </Box>
 | 
				
			||||||
 | 
					                                </Menu.Target>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                <Menu.Dropdown onClick={(e) => e.stopPropagation()}>
 | 
				
			||||||
 | 
					                                    <Menu.Item onClick={() => handleDelete(row)} leftSection={<IconTrash color="red" size={14} />}>
 | 
				
			||||||
 | 
					                                        Delete
 | 
				
			||||||
 | 
					                                    </Menu.Item>
 | 
				
			||||||
 | 
					                                </Menu.Dropdown>
 | 
				
			||||||
 | 
					                            </Menu>
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                rows={[]}
 | 
				
			||||||
 | 
					                withColumnBorders
 | 
				
			||||||
 | 
					                showChooses={true}
 | 
				
			||||||
 | 
					                withTableBorder
 | 
				
			||||||
 | 
					                columns={columns}
 | 
				
			||||||
 | 
					                rowKey="id"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <Box>{table}</Box>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,16 @@
 | 
				
			||||||
import { IconHammer, IconHome2, IconMessage, IconOutlet, IconPageBreak, IconUserCheck } from '@tabler/icons-react';
 | 
					import { IconHammer, IconHome2, IconKey, 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';
 | 
					import Admins from '../pages/admins';
 | 
				
			||||||
 | 
					import GenerateKeys from '../pages/generate-keys';
 | 
				
			||||||
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 GENERATE_KEYS = '/generate-keys';
 | 
				
			||||||
    public static ADMINS = '/admins';
 | 
					    public static ADMINS = '/admins';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static HOME = '/';
 | 
					    public static HOME = '/';
 | 
				
			||||||
| 
						 | 
					@ -20,36 +22,49 @@ export default class Links {
 | 
				
			||||||
            title: 'Dashboard',
 | 
					            title: 'Dashboard',
 | 
				
			||||||
            icon: IconHome2,
 | 
					            icon: IconHome2,
 | 
				
			||||||
            element: Dashboard,
 | 
					            element: Dashboard,
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            path: this.ADMINS,
 | 
					            path: this.ADMINS,
 | 
				
			||||||
            title: 'Admins',
 | 
					            title: 'Admins',
 | 
				
			||||||
            icon: IconUserCheck,
 | 
					            icon: IconUserCheck,
 | 
				
			||||||
            element: Admins,
 | 
					            element: Admins,
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            path: this.WEBS,
 | 
					            path: this.WEBS,
 | 
				
			||||||
            title: 'Webs',
 | 
					            title: 'Webs',
 | 
				
			||||||
            icon: IconPageBreak,
 | 
					            icon: IconPageBreak,
 | 
				
			||||||
            element: WebBids,
 | 
					            element: WebBids,
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            path: this.BIDS,
 | 
					            path: this.BIDS,
 | 
				
			||||||
            title: 'Bids',
 | 
					            title: 'Bids',
 | 
				
			||||||
            icon: IconHammer,
 | 
					            icon: IconHammer,
 | 
				
			||||||
            element: Bids,
 | 
					            element: Bids,
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            path: this.OUT_BIDS_LOG,
 | 
					            path: this.OUT_BIDS_LOG,
 | 
				
			||||||
            title: 'Out bids log',
 | 
					            title: 'Out bids log',
 | 
				
			||||||
            icon: IconOutlet,
 | 
					            icon: IconOutlet,
 | 
				
			||||||
            element: OutBidsLog,
 | 
					            element: OutBidsLog,
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            path: this.SEND_MESSAGE_HISTORIES,
 | 
					            path: this.SEND_MESSAGE_HISTORIES,
 | 
				
			||||||
            title: 'Send message histories',
 | 
					            title: 'Send message histories',
 | 
				
			||||||
            icon: IconMessage,
 | 
					            icon: IconMessage,
 | 
				
			||||||
            element: SendMessageHistories,
 | 
					            element: SendMessageHistories,
 | 
				
			||||||
 | 
					            show: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            path: this.GENERATE_KEYS,
 | 
				
			||||||
 | 
					            title: 'Generate keys',
 | 
				
			||||||
 | 
					            icon: IconKey,
 | 
				
			||||||
 | 
					            element: GenerateKeys,
 | 
				
			||||||
 | 
					            show: false,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,11 @@ export interface IAdmin extends ITimestamp {
 | 
				
			||||||
    permissions: [];
 | 
					    permissions: [];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IKey extends ITimestamp {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    client_key: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ITimestamp {
 | 
					export interface ITimestamp {
 | 
				
			||||||
    created_at: string;
 | 
					    created_at: string;
 | 
				
			||||||
    updated_at: string;
 | 
					    updated_at: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@
 | 
				
			||||||
        "bcrypt": "^5.1.1",
 | 
					        "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": "^1.0.2",
 | 
				
			||||||
        "cookie-parser": "^1.4.7",
 | 
					        "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",
 | 
				
			||||||
| 
						 | 
					@ -32,13 +33,15 @@
 | 
				
			||||||
        "reflect-metadata": "^0.2.0",
 | 
					        "reflect-metadata": "^0.2.0",
 | 
				
			||||||
        "rxjs": "^7.8.1",
 | 
					        "rxjs": "^7.8.1",
 | 
				
			||||||
        "sharp": "^0.33.5",
 | 
					        "sharp": "^0.33.5",
 | 
				
			||||||
        "typeorm": "^0.3.21"
 | 
					        "typeorm": "^0.3.21",
 | 
				
			||||||
 | 
					        "uuid": "^11.1.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "devDependencies": {
 | 
					      "devDependencies": {
 | 
				
			||||||
        "@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/bcrypt": "^5.0.2",
 | 
				
			||||||
 | 
					        "@types/cookie": "^0.6.0",
 | 
				
			||||||
        "@types/cookie-parser": "^1.4.8",
 | 
					        "@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",
 | 
				
			||||||
| 
						 | 
					@ -2823,6 +2826,13 @@
 | 
				
			||||||
        "@types/node": "*"
 | 
					        "@types/node": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@types/cookie": {
 | 
				
			||||||
 | 
					      "version": "0.6.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "license": "MIT"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/cookie-parser": {
 | 
					    "node_modules/@types/cookie-parser": {
 | 
				
			||||||
      "version": "1.4.8",
 | 
					      "version": "1.4.8",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.8.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.8.tgz",
 | 
				
			||||||
| 
						 | 
					@ -4719,12 +4729,12 @@
 | 
				
			||||||
      "license": "MIT"
 | 
					      "license": "MIT"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/cookie": {
 | 
					    "node_modules/cookie": {
 | 
				
			||||||
      "version": "0.7.1",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
 | 
					      "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
 | 
				
			||||||
      "license": "MIT",
 | 
					      "license": "MIT",
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.6"
 | 
					        "node": ">=18"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/cookie-parser": {
 | 
					    "node_modules/cookie-parser": {
 | 
				
			||||||
| 
						 | 
					@ -5736,6 +5746,15 @@
 | 
				
			||||||
        "url": "https://opencollective.com/express"
 | 
					        "url": "https://opencollective.com/express"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/express/node_modules/cookie": {
 | 
				
			||||||
 | 
					      "version": "0.7.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
 | 
				
			||||||
 | 
					      "license": "MIT",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 0.6"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/express/node_modules/debug": {
 | 
					    "node_modules/express/node_modules/debug": {
 | 
				
			||||||
      "version": "2.6.9",
 | 
					      "version": "2.6.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
 | 
				
			||||||
| 
						 | 
					@ -8088,16 +8107,6 @@
 | 
				
			||||||
        "set-cookie-parser": "^2.6.0"
 | 
					        "set-cookie-parser": "^2.6.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/light-my-request/node_modules/cookie": {
 | 
					 | 
				
			||||||
      "version": "1.0.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
 | 
					 | 
				
			||||||
      "license": "MIT",
 | 
					 | 
				
			||||||
      "peer": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					 | 
				
			||||||
        "node": ">=18"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/lines-and-columns": {
 | 
					    "node_modules/lines-and-columns": {
 | 
				
			||||||
      "version": "1.2.4",
 | 
					      "version": "1.2.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,7 @@
 | 
				
			||||||
    "bcrypt": "^5.1.1",
 | 
					    "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": "^1.0.2",
 | 
				
			||||||
    "cookie-parser": "^1.4.7",
 | 
					    "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",
 | 
				
			||||||
| 
						 | 
					@ -48,13 +49,15 @@
 | 
				
			||||||
    "reflect-metadata": "^0.2.0",
 | 
					    "reflect-metadata": "^0.2.0",
 | 
				
			||||||
    "rxjs": "^7.8.1",
 | 
					    "rxjs": "^7.8.1",
 | 
				
			||||||
    "sharp": "^0.33.5",
 | 
					    "sharp": "^0.33.5",
 | 
				
			||||||
    "typeorm": "^0.3.21"
 | 
					    "typeorm": "^0.3.21",
 | 
				
			||||||
 | 
					    "uuid": "^11.1.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@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/bcrypt": "^5.0.2",
 | 
				
			||||||
 | 
					    "@types/cookie": "^0.6.0",
 | 
				
			||||||
    "@types/cookie-parser": "^1.4.8",
 | 
					    "@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",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,13 @@ import { AppValidatorsModule } from './modules/app-validators/app-validators.mod
 | 
				
			||||||
import { AuthModule } from './modules/auth/auth.module';
 | 
					import { AuthModule } from './modules/auth/auth.module';
 | 
				
			||||||
import { AdminsModule } from './modules/admins/admins.module';
 | 
					import { AdminsModule } from './modules/admins/admins.module';
 | 
				
			||||||
import { AuthenticationMiddleware } from './modules/auth/middlewares/authentication.middleware';
 | 
					import { AuthenticationMiddleware } from './modules/auth/middlewares/authentication.middleware';
 | 
				
			||||||
import { excludeAuth, excludeAuthor } from './system/routes/exclude-route';
 | 
					import {
 | 
				
			||||||
 | 
					  clientExcludeAuth,
 | 
				
			||||||
 | 
					  excludeAuth,
 | 
				
			||||||
 | 
					  excludeAuthor,
 | 
				
			||||||
 | 
					} from './system/routes/exclude-route';
 | 
				
			||||||
import { AuthorizationMiddleware } from './modules/admins/middlewares/authorization.middleware';
 | 
					import { AuthorizationMiddleware } from './modules/admins/middlewares/authorization.middleware';
 | 
				
			||||||
 | 
					import { ClientAuthenticationMiddleware } from './modules/auth/middlewares/client-authentication.middleware';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
| 
						 | 
					@ -32,5 +37,10 @@ export class AppModule {
 | 
				
			||||||
      .apply(AuthorizationMiddleware)
 | 
					      .apply(AuthorizationMiddleware)
 | 
				
			||||||
      .exclude(...excludeAuthor)
 | 
					      .exclude(...excludeAuthor)
 | 
				
			||||||
      .forRoutes({ path: 'admin/*', method: RequestMethod.ALL });
 | 
					      .forRoutes({ path: 'admin/*', method: RequestMethod.ALL });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    consumer
 | 
				
			||||||
 | 
					      .apply(ClientAuthenticationMiddleware)
 | 
				
			||||||
 | 
					      .exclude(...clientExcludeAuth)
 | 
				
			||||||
 | 
					      .forRoutes({ path: '/*', method: RequestMethod.ALL });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ async function bootstrap() {
 | 
				
			||||||
  const prefix_version = '/api/v1';
 | 
					  const prefix_version = '/api/v1';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  app.enableCors({
 | 
					  app.enableCors({
 | 
				
			||||||
    origin: process.env.CORS.split(', '),
 | 
					    origin: process.env.CORS.split(', ') || '*',
 | 
				
			||||||
    methods: ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'],
 | 
					    methods: ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'],
 | 
				
			||||||
    allowedHeaders: 'Content-Type, Authorization',
 | 
					    allowedHeaders: 'Content-Type, Authorization',
 | 
				
			||||||
    credentials: true,
 | 
					    credentials: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,11 +6,18 @@ import { AdminsController } from './controllers/admins.controller';
 | 
				
			||||||
import Permission from './entities/permission.entity';
 | 
					import Permission from './entities/permission.entity';
 | 
				
			||||||
import { PermissionService } from './services/permission.service';
 | 
					import { PermissionService } from './services/permission.service';
 | 
				
			||||||
import { PermissionsController } from './controllers/permissions.controller';
 | 
					import { PermissionsController } from './controllers/permissions.controller';
 | 
				
			||||||
 | 
					import { GenerateKey } from './entities/generate-key.entity';
 | 
				
			||||||
 | 
					import { GenerateKeysController } from './controllers/generate-keys.controller';
 | 
				
			||||||
 | 
					import { GenerateKeysService } from './services/generate-key.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [TypeOrmModule.forFeature([Admin, Permission])],
 | 
					  imports: [TypeOrmModule.forFeature([Admin, Permission, GenerateKey])],
 | 
				
			||||||
  providers: [AdminsService, PermissionService],
 | 
					  providers: [AdminsService, PermissionService, GenerateKeysService],
 | 
				
			||||||
  exports: [AdminsService, PermissionService],
 | 
					  exports: [AdminsService, PermissionService, GenerateKeysService],
 | 
				
			||||||
  controllers: [AdminsController, PermissionsController],
 | 
					  controllers: [
 | 
				
			||||||
 | 
					    AdminsController,
 | 
				
			||||||
 | 
					    PermissionsController,
 | 
				
			||||||
 | 
					    GenerateKeysController,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AdminsModule {}
 | 
					export class AdminsModule {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					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';
 | 
				
			||||||
 | 
					import { GenerateKeysService } from '../services/generate-key.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Controller('admin/keys')
 | 
				
			||||||
 | 
					export class GenerateKeysController {
 | 
				
			||||||
 | 
					  constructor(private readonly generateKeyService: GenerateKeysService) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Get()
 | 
				
			||||||
 | 
					  async index(@Paginate() query: PaginateQuery) {
 | 
				
			||||||
 | 
					    return await this.generateKeyService.index(query);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Delete(':id')
 | 
				
			||||||
 | 
					  async delete(@Param('id') id: Admin['id']) {
 | 
				
			||||||
 | 
					    return await this.generateKeyService.delete(id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Post('')
 | 
				
			||||||
 | 
					  async create(@Req() request: Request) {
 | 
				
			||||||
 | 
					    return await this.generateKeyService.create(request);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Post('deletes')
 | 
				
			||||||
 | 
					  async deletes(@Body() data: DeletesDto) {
 | 
				
			||||||
 | 
					    return await this.generateKeyService.deletes(data.ids);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,15 @@
 | 
				
			||||||
import { Exclude } from 'class-transformer';
 | 
					import { Exclude } from 'class-transformer';
 | 
				
			||||||
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
 | 
					import {
 | 
				
			||||||
 | 
					  Column,
 | 
				
			||||||
 | 
					  Entity,
 | 
				
			||||||
 | 
					  ManyToMany,
 | 
				
			||||||
 | 
					  ManyToOne,
 | 
				
			||||||
 | 
					  OneToMany,
 | 
				
			||||||
 | 
					  PrimaryGeneratedColumn,
 | 
				
			||||||
 | 
					} from 'typeorm';
 | 
				
			||||||
import Permission from './permission.entity';
 | 
					import Permission from './permission.entity';
 | 
				
			||||||
import { Timestamp } from './timestamp';
 | 
					import { Timestamp } from './timestamp';
 | 
				
			||||||
 | 
					import { GenerateKey } from './generate-key.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Entity('admins')
 | 
					@Entity('admins')
 | 
				
			||||||
export default class Admin extends Timestamp {
 | 
					export default class Admin extends Timestamp {
 | 
				
			||||||
| 
						 | 
					@ -29,4 +37,9 @@ export default class Admin extends Timestamp {
 | 
				
			||||||
    onDelete: 'CASCADE',
 | 
					    onDelete: 'CASCADE',
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  permissions: Permission[];
 | 
					  permissions: Permission[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OneToMany(() => GenerateKey, (key) => key.admin, {
 | 
				
			||||||
 | 
					    cascade: true,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  generateKeys: GenerateKey[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					import { Exclude } from 'class-transformer';
 | 
				
			||||||
 | 
					import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
 | 
				
			||||||
 | 
					import Admin from './admin.entity';
 | 
				
			||||||
 | 
					import { Timestamp } from './timestamp';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity('generate_keys')
 | 
				
			||||||
 | 
					export class GenerateKey extends Timestamp {
 | 
				
			||||||
 | 
					  @PrimaryGeneratedColumn('increment')
 | 
				
			||||||
 | 
					  id: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Column({ unique: true })
 | 
				
			||||||
 | 
					  client_key: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @ManyToOne(() => Admin, (admin) => admin.generateKeys, {
 | 
				
			||||||
 | 
					    onDelete: 'CASCADE',
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  admin: Admin;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -119,6 +119,7 @@ export class AdminsService {
 | 
				
			||||||
    | 'is_system_account'
 | 
					    | 'is_system_account'
 | 
				
			||||||
    | 'permissions'
 | 
					    | 'permissions'
 | 
				
			||||||
    | 'posts'
 | 
					    | 'posts'
 | 
				
			||||||
 | 
					    | 'generateKeys'
 | 
				
			||||||
  >) {
 | 
					  >) {
 | 
				
			||||||
    const hashPassword = await bcrypt.hash(password, 10);
 | 
					    const hashPassword = await bcrypt.hash(password, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,106 @@
 | 
				
			||||||
 | 
					import AppResponse from '@/response/app-response';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  BadRequestException,
 | 
				
			||||||
 | 
					  HttpStatus,
 | 
				
			||||||
 | 
					  Injectable,
 | 
				
			||||||
 | 
					  NotFoundException,
 | 
				
			||||||
 | 
					} from '@nestjs/common';
 | 
				
			||||||
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
 | 
					import { Request } from 'express';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  FilterOperator,
 | 
				
			||||||
 | 
					  FilterSuffix,
 | 
				
			||||||
 | 
					  paginate,
 | 
				
			||||||
 | 
					  PaginateQuery,
 | 
				
			||||||
 | 
					} from 'nestjs-paginate';
 | 
				
			||||||
 | 
					import { Column } from 'nestjs-paginate/lib/helper';
 | 
				
			||||||
 | 
					import { In, Repository } from 'typeorm';
 | 
				
			||||||
 | 
					import { v1 as uuidV1 } from 'uuid';
 | 
				
			||||||
 | 
					import Admin from '../entities/admin.entity';
 | 
				
			||||||
 | 
					import { GenerateKey } from '../entities/generate-key.entity';
 | 
				
			||||||
 | 
					import Permission from '../entities/permission.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class GenerateKeysService {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @InjectRepository(GenerateKey)
 | 
				
			||||||
 | 
					    readonly generateKeyRepo: Repository<GenerateKey>,
 | 
				
			||||||
 | 
					    @InjectRepository(Permission)
 | 
				
			||||||
 | 
					    readonly permissionRepo: Repository<Permission>,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async index(query: PaginateQuery) {
 | 
				
			||||||
 | 
					    const filterableColumns: {
 | 
				
			||||||
 | 
					      [key in Column<Admin> | (string & {})]?:
 | 
				
			||||||
 | 
					        | (FilterOperator | FilterSuffix)[]
 | 
				
			||||||
 | 
					        | true;
 | 
				
			||||||
 | 
					    } = {
 | 
				
			||||||
 | 
					      id: true,
 | 
				
			||||||
 | 
					      client_key: [FilterOperator.ILIKE],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    query.filter = AppResponse.processFilters(query.filter, filterableColumns);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const data = await paginate(query, this.generateKeyRepo, {
 | 
				
			||||||
 | 
					      sortableColumns: ['id', 'client_key'],
 | 
				
			||||||
 | 
					      searchableColumns: ['id', 'client_key'],
 | 
				
			||||||
 | 
					      defaultLimit: 15,
 | 
				
			||||||
 | 
					      filterableColumns,
 | 
				
			||||||
 | 
					      defaultSortBy: [['id', 'DESC']],
 | 
				
			||||||
 | 
					      maxLimit: 100,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AppResponse.toPagination<GenerateKey>(data, true, GenerateKey);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async delete(id: Admin['id']) {
 | 
				
			||||||
 | 
					    const admin = await this.generateKeyRepo.findOne({ where: { id } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!admin)
 | 
				
			||||||
 | 
					      throw new NotFoundException(
 | 
				
			||||||
 | 
					        AppResponse.toResponse(false, {
 | 
				
			||||||
 | 
					          message: 'Key is not found',
 | 
				
			||||||
 | 
					          status_code: HttpStatus.NOT_FOUND,
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await this.generateKeyRepo.delete({ id: admin.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AppResponse.toResponse(true, { message: 'Delete success !' });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async deletes(ids: Admin['id'][]) {
 | 
				
			||||||
 | 
					    const result = await this.generateKeyRepo.delete({
 | 
				
			||||||
 | 
					      id: In(ids),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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(request: Request) {
 | 
				
			||||||
 | 
					    const admin = request['admin'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const clientKey = uuidV1();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = await this.generateKeyRepo.save({
 | 
				
			||||||
 | 
					      admin: { id: admin.id },
 | 
				
			||||||
 | 
					      client_key: `client-${clientKey}-${admin.id}`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!result)
 | 
				
			||||||
 | 
					      throw new BadRequestException(
 | 
				
			||||||
 | 
					        AppResponse.toResponse(null, { message: `Can't create key` }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return AppResponse.toResponse(result);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { Module } from '@nestjs/common';
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
import { JwtModule } from '@nestjs/jwt';
 | 
					import { JwtModule } from '@nestjs/jwt';
 | 
				
			||||||
import { AuthController } from './auth.controller';
 | 
					import { AuthController } from './controllers/auth.controller';
 | 
				
			||||||
import { AuthService } from './auth.service';
 | 
					import { AuthService } from './auth.service';
 | 
				
			||||||
import { AdminsModule } from '../admins/admins.module';
 | 
					import { AdminsModule } from '../admins/admins.module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,12 +14,14 @@ import { Request, Response } from 'express';
 | 
				
			||||||
import { Constant } from './ultils/constant';
 | 
					import { Constant } from './ultils/constant';
 | 
				
			||||||
import { ChangePasswordDto } from './dto/change-password.dto';
 | 
					import { ChangePasswordDto } from './dto/change-password.dto';
 | 
				
			||||||
import AppResponse from '@/response/app-response';
 | 
					import AppResponse from '@/response/app-response';
 | 
				
			||||||
 | 
					import { GenerateKeysService } from '../admins/services/generate-key.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class AuthService {
 | 
					export class AuthService {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private jwtService: JwtService,
 | 
					    private jwtService: JwtService,
 | 
				
			||||||
    private readonly adminService: AdminsService,
 | 
					    private readonly adminService: AdminsService,
 | 
				
			||||||
 | 
					    private readonly generateKeyService: GenerateKeysService,
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async validateAdmin(
 | 
					  async validateAdmin(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,10 +7,13 @@ import {
 | 
				
			||||||
  Res,
 | 
					  Res,
 | 
				
			||||||
  Get,
 | 
					  Get,
 | 
				
			||||||
} from '@nestjs/common';
 | 
					} from '@nestjs/common';
 | 
				
			||||||
import { AuthService } from './auth.service';
 | 
					import { AuthService } from '../auth.service';
 | 
				
			||||||
import { LoginDto } from './dto/login.dto';
 | 
					import { LoginDto } from '../dto/login.dto';
 | 
				
			||||||
import { Request, Response } from 'express';
 | 
					import { Request, Response } from 'express';
 | 
				
			||||||
import { ChangePasswordDto } from './dto/change-password.dto';
 | 
					import { ChangePasswordDto } from '../dto/change-password.dto';
 | 
				
			||||||
 | 
					import AppResponse from '@/response/app-response';
 | 
				
			||||||
 | 
					import { Throttle } from '@nestjs/throttler';
 | 
				
			||||||
 | 
					import { GenerateTokenDto } from '../dto/generate-token.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Controller('admin/auth')
 | 
					@Controller('admin/auth')
 | 
				
			||||||
export class AuthController {
 | 
					export class AuthController {
 | 
				
			||||||
| 
						 | 
					@ -26,6 +29,12 @@ export class AuthController {
 | 
				
			||||||
      .then((admin) => this.authService.login(admin, response));
 | 
					      .then((admin) => this.authService.login(admin, response));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Post('check-status')
 | 
				
			||||||
 | 
					  @Throttle({ default: { limit: 1, ttl: 30 * 1000 } })
 | 
				
			||||||
 | 
					  async checkStatus() {
 | 
				
			||||||
 | 
					    return AppResponse.toResponse(true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Post('logout')
 | 
					  @Post('logout')
 | 
				
			||||||
  async logout(@Res({ passthrough: true }) response: Response) {
 | 
					  async logout(@Res({ passthrough: true }) response: Response) {
 | 
				
			||||||
    return await this.authService.logout(response);
 | 
					    return await this.authService.logout(response);
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					import { IsString } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class GenerateTokenDto {
 | 
				
			||||||
 | 
					  @IsString()
 | 
				
			||||||
 | 
					  client_key: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					// auth.middleware.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { GenerateKeysService } from '@/modules/admins/services/generate-key.service';
 | 
				
			||||||
 | 
					import AppResponse from '@/response/app-response';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  HttpStatus,
 | 
				
			||||||
 | 
					  Injectable,
 | 
				
			||||||
 | 
					  NestMiddleware,
 | 
				
			||||||
 | 
					  UnauthorizedException,
 | 
				
			||||||
 | 
					} from '@nestjs/common';
 | 
				
			||||||
 | 
					import { NextFunction, Request, Response } from 'express';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class ClientAuthenticationMiddleware implements NestMiddleware {
 | 
				
			||||||
 | 
					  constructor(private readonly generateKeysService: GenerateKeysService) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async getKey(key: string) {
 | 
				
			||||||
 | 
					    if (!key) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const data = await this.generateKeysService.generateKeyRepo.findOne({
 | 
				
			||||||
 | 
					      where: { client_key: key },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async use(req: Request, res: Response, next: NextFunction) {
 | 
				
			||||||
 | 
					    const client_key: string = req.headers.authorization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const key = await this.getKey(client_key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!key) {
 | 
				
			||||||
 | 
					      return next(
 | 
				
			||||||
 | 
					        new UnauthorizedException(
 | 
				
			||||||
 | 
					          AppResponse.toResponse(null, {
 | 
				
			||||||
 | 
					            message: 'Unauthorized',
 | 
				
			||||||
 | 
					            status_code: HttpStatus.UNAUTHORIZED,
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    next();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					import { GenerateKeysService } from '@/modules/admins/services/generate-key.service';
 | 
				
			||||||
 | 
					import { Socket } from 'socket.io';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function clientGetWayMiddleware(
 | 
				
			||||||
 | 
					  client: Socket,
 | 
				
			||||||
 | 
					  generateKeysService: GenerateKeysService,
 | 
				
			||||||
 | 
					): Promise<void> {
 | 
				
			||||||
 | 
					  const client_key = client.handshake.headers.authorization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!client_key) {
 | 
				
			||||||
 | 
					    client.disconnect();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const key = await generateKeysService.generateKeyRepo.findOne({
 | 
				
			||||||
 | 
					    where: { client_key },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!key) {
 | 
				
			||||||
 | 
					    client.disconnect();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log(`✅ WebSocket authenticated: Client ID ${key.client_key}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					import { JwtService, TokenExpiredError } from '@nestjs/jwt';
 | 
				
			||||||
 | 
					import { AdminsService } from '@/modules/admins/services/admins.service';
 | 
				
			||||||
 | 
					import * as cookie from 'cookie';
 | 
				
			||||||
 | 
					import { Socket } from 'socket.io';
 | 
				
			||||||
 | 
					import { plainToClass } from 'class-transformer';
 | 
				
			||||||
 | 
					import Admin from '@/modules/admins/entities/admin.entity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getWayMiddleware(
 | 
				
			||||||
 | 
					  client: Socket,
 | 
				
			||||||
 | 
					  jwtService: JwtService,
 | 
				
			||||||
 | 
					  adminService: AdminsService,
 | 
				
			||||||
 | 
					): Promise<void> {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const cookies = cookie.parse(client.handshake.headers.cookie || '');
 | 
				
			||||||
 | 
					    const accessToken = cookies['access_token'];
 | 
				
			||||||
 | 
					    const refreshToken = cookies['refresh_token'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!accessToken && !refreshToken) {
 | 
				
			||||||
 | 
					      console.log('🚫 No tokens provided, disconnecting WebSocket');
 | 
				
			||||||
 | 
					      client.disconnect();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let admin: Admin | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await jwtService.verify(accessToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!result?._id || result.refresh_key) {
 | 
				
			||||||
 | 
					        throw new Error('Invalid access token');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      admin = await adminService.adminRepo.findOne({
 | 
				
			||||||
 | 
					        where: { id: result._id },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!admin) throw new Error('Admin not found');
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.log('❗ Invalid access token, disconnecting WebSocket');
 | 
				
			||||||
 | 
					      client.disconnect();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client.data.admin = plainToClass(Admin, admin);
 | 
				
			||||||
 | 
					    console.log(`✅ WebSocket authenticated: Admin ID ${admin.id}`);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.log('❌ WebSocket authentication error:', error);
 | 
				
			||||||
 | 
					    client.disconnect();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,9 @@ import { GraysApi } from './apis/grays.api';
 | 
				
			||||||
import { SendMessageHistory } from './entities/send-message-histories.entity';
 | 
					import { SendMessageHistory } from './entities/send-message-histories.entity';
 | 
				
			||||||
import { SendMessageHistoriesService } from './services/send-message-histories.service';
 | 
					import { SendMessageHistoriesService } from './services/send-message-histories.service';
 | 
				
			||||||
import { AdminSendMessageHistoriesController } from './controllers/admin/admin-send-message-histories.controller';
 | 
					import { AdminSendMessageHistoriesController } from './controllers/admin/admin-send-message-histories.controller';
 | 
				
			||||||
 | 
					import { AuthModule } from '../auth/auth.module';
 | 
				
			||||||
 | 
					import { AdminsModule } from '../admins/admins.module';
 | 
				
			||||||
 | 
					import { AdminBidGateway } from './getways/admin-bid-getway';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
| 
						 | 
					@ -35,6 +38,8 @@ import { AdminSendMessageHistoriesController } from './controllers/admin/admin-s
 | 
				
			||||||
    EventEmitterModule.forRoot({
 | 
					    EventEmitterModule.forRoot({
 | 
				
			||||||
      wildcard: true,
 | 
					      wildcard: true,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					    // AuthModule,
 | 
				
			||||||
 | 
					    AdminsModule,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  controllers: [
 | 
					  controllers: [
 | 
				
			||||||
    BidsController,
 | 
					    BidsController,
 | 
				
			||||||
| 
						 | 
					@ -50,6 +55,7 @@ import { AdminSendMessageHistoriesController } from './controllers/admin/admin-s
 | 
				
			||||||
    BidsService,
 | 
					    BidsService,
 | 
				
			||||||
    BidHistoriesService,
 | 
					    BidHistoriesService,
 | 
				
			||||||
    BidGateway,
 | 
					    BidGateway,
 | 
				
			||||||
 | 
					    AdminBidGateway,
 | 
				
			||||||
    OutBidLogsService,
 | 
					    OutBidLogsService,
 | 
				
			||||||
    WebBidsService,
 | 
					    WebBidsService,
 | 
				
			||||||
    BotTelegramApi,
 | 
					    BotTelegramApi,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  WebSocketGateway,
 | 
				
			||||||
 | 
					  WebSocketServer,
 | 
				
			||||||
 | 
					  SubscribeMessage,
 | 
				
			||||||
 | 
					  OnGatewayConnection,
 | 
				
			||||||
 | 
					} from '@nestjs/websockets';
 | 
				
			||||||
 | 
					import { Server, Socket } from 'socket.io';
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
 | 
					import { BidsService } from '../services/bids.service';
 | 
				
			||||||
 | 
					import { WebBidsService } from '../services/web-bids.service';
 | 
				
			||||||
 | 
					import { plainToClass } from 'class-transformer';
 | 
				
			||||||
 | 
					import { WebBid } from '../entities/wed-bid.entity';
 | 
				
			||||||
 | 
					import * as cookie from 'cookie';
 | 
				
			||||||
 | 
					import { Constant } from '@/modules/auth/ultils/constant';
 | 
				
			||||||
 | 
					import { getWayMiddleware } from '@/modules/auth/middlewares/get-way.middleware';
 | 
				
			||||||
 | 
					import { AdminsService } from '@/modules/admins/services/admins.service';
 | 
				
			||||||
 | 
					import { JwtService } from '@nestjs/jwt';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@WebSocketGateway({
 | 
				
			||||||
 | 
					  namespace: 'admin-bid-ws',
 | 
				
			||||||
 | 
					  cors: {
 | 
				
			||||||
 | 
					    origin: '*',
 | 
				
			||||||
 | 
					    methods: ['GET', 'POST'],
 | 
				
			||||||
 | 
					    credentials: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AdminBidGateway implements OnGatewayConnection {
 | 
				
			||||||
 | 
					  @WebSocketServer()
 | 
				
			||||||
 | 
					  server: Server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private eventEmitter: EventEmitter2,
 | 
				
			||||||
 | 
					    private readonly jwtService: JwtService,
 | 
				
			||||||
 | 
					    private webBidsService: WebBidsService,
 | 
				
			||||||
 | 
					    private readonly adminService: AdminsService,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async onModuleInit() {
 | 
				
			||||||
 | 
					    this.eventEmitter.on('bids.updated', (data) => {
 | 
				
			||||||
 | 
					      this.server.emit('adminBidsUpdated', plainToClass(WebBid, data));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.eventEmitter.on('working', (data) => {
 | 
				
			||||||
 | 
					      this.server.emit('working', data);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async handleConnection(client: Socket) {
 | 
				
			||||||
 | 
					    await getWayMiddleware(client, this.jwtService, this.adminService);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`📢 Client connected: ${client.id}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const data = await this.webBidsService.getDataClient();
 | 
				
			||||||
 | 
					    // Gửi dữ liệu bids ngay khi client kết nối
 | 
				
			||||||
 | 
					    client.emit('adminBidsUpdated', plainToClass(WebBid, data));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,17 @@
 | 
				
			||||||
 | 
					import { GenerateKeysService } from '@/modules/admins/services/generate-key.service';
 | 
				
			||||||
 | 
					import { clientGetWayMiddleware } from '@/modules/auth/middlewares/client-get-way.middleware copy';
 | 
				
			||||||
 | 
					import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  OnGatewayConnection,
 | 
				
			||||||
  WebSocketGateway,
 | 
					  WebSocketGateway,
 | 
				
			||||||
  WebSocketServer,
 | 
					  WebSocketServer,
 | 
				
			||||||
  SubscribeMessage,
 | 
					 | 
				
			||||||
  OnGatewayConnection,
 | 
					 | 
				
			||||||
} from '@nestjs/websockets';
 | 
					} from '@nestjs/websockets';
 | 
				
			||||||
import { Server, Socket } from 'socket.io';
 | 
					import { Server, Socket } from 'socket.io';
 | 
				
			||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
 | 
					 | 
				
			||||||
import { BidsService } from '../services/bids.service';
 | 
					import { BidsService } from '../services/bids.service';
 | 
				
			||||||
import { WebBidsService } from '../services/web-bids.service';
 | 
					import { WebBidsService } from '../services/web-bids.service';
 | 
				
			||||||
import { plainToClass } from 'class-transformer';
 | 
					 | 
				
			||||||
import { WebBid } from '../entities/wed-bid.entity';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@WebSocketGateway({
 | 
					@WebSocketGateway({
 | 
				
			||||||
 | 
					  namespace: 'bid-ws',
 | 
				
			||||||
  cors: {
 | 
					  cors: {
 | 
				
			||||||
    origin: '*',
 | 
					    origin: '*',
 | 
				
			||||||
    methods: ['GET', 'POST'],
 | 
					    methods: ['GET', 'POST'],
 | 
				
			||||||
| 
						 | 
					@ -25,30 +25,26 @@ export class BidGateway implements OnGatewayConnection {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private eventEmitter: EventEmitter2,
 | 
					    private eventEmitter: EventEmitter2,
 | 
				
			||||||
    private webBidsService: WebBidsService,
 | 
					    private webBidsService: WebBidsService,
 | 
				
			||||||
    private bidsService: BidsService,
 | 
					    private generateKeysService: GenerateKeysService,
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async onModuleInit() {
 | 
					  async onModuleInit() {
 | 
				
			||||||
    this.eventEmitter.on('bids.updated', (data) => {
 | 
					    this.eventEmitter.on('bids.updated', (data) => {
 | 
				
			||||||
      this.server.emit('bidsUpdated', data);
 | 
					      this.server.emit('bidsUpdated', data);
 | 
				
			||||||
      this.server.emit('adminBidsUpdated', plainToClass(WebBid, data));
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.eventEmitter.on('web.updated', (data) => {
 | 
					    this.eventEmitter.on('web.updated', (data) => {
 | 
				
			||||||
      this.server.emit('webUpdated', data);
 | 
					      this.server.emit('webUpdated', data);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.eventEmitter.on('working', (data) => {
 | 
					 | 
				
			||||||
      this.server.emit('working', data);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async handleConnection(client: Socket) {
 | 
					  async handleConnection(client: Socket) {
 | 
				
			||||||
 | 
					    await clientGetWayMiddleware(client, this.generateKeysService);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log(`📢 Client connected: ${client.id}`);
 | 
					    console.log(`📢 Client connected: ${client.id}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data = await this.webBidsService.getDataClient();
 | 
					    const data = await this.webBidsService.getDataClient();
 | 
				
			||||||
    // Gửi dữ liệu bids ngay khi client kết nối
 | 
					    // Gửi dữ liệu bids ngay khi client kết nối
 | 
				
			||||||
    client.emit('bidsUpdated', data);
 | 
					    client.emit('bidsUpdated', data);
 | 
				
			||||||
    client.emit('adminBidsUpdated', plainToClass(WebBid, data));
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,14 @@ export const excludeAuth = [
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
] as (string | RouteInfo)[];
 | 
					] as (string | RouteInfo)[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const clientExcludeAuth = [
 | 
				
			||||||
 | 
					  { path: '/admin/(.*)', method: RequestMethod.ALL },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    path: 'auth/generate-token',
 | 
				
			||||||
 | 
					    method: RequestMethod.POST,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					] as (string | RouteInfo)[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const excludeAuthor = [
 | 
					export const excludeAuthor = [
 | 
				
			||||||
  ...excludeAuth,
 | 
					  ...excludeAuth,
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
ENVIRONMENT = 'prod'
 | 
					ENVIRONMENT = 'prod'
 | 
				
			||||||
SOCKET_URL = 'http://localhost:4000'
 | 
					SOCKET_URL = 'http://localhost:4000'
 | 
				
			||||||
BASE_URL = 'http://localhost:4000/api/v1/'
 | 
					BASE_URL = 'http://localhost:4000/api/v1/'
 | 
				
			||||||
 | 
					CLIENT_KEY = ''
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,11 @@
 | 
				
			||||||
import 'dotenv/config';
 | 
					import 'dotenv/config';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					import pLimit from 'p-limit';
 | 
				
			||||||
import { io } from 'socket.io-client';
 | 
					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 { delay, isTimeReached, safeClosePage } from './system/utils.js';
 | 
					import { delay, isTimeReached, safeClosePage } from './system/utils.js';
 | 
				
			||||||
import pLimit from 'p-limit';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
let MANAGER_BIDS = [];
 | 
					let MANAGER_BIDS = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,6 @@ const tracking = async () => {
 | 
				
			||||||
            console.log('🔍 Scanning active bids...');
 | 
					            console.log('🔍 Scanning active bids...');
 | 
				
			||||||
            const productTabs = _.flatMap(MANAGER_BIDS, 'children');
 | 
					            const productTabs = _.flatMap(MANAGER_BIDS, 'children');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Lắng nghe sự kiện của API bids (chạy song song)
 | 
					 | 
				
			||||||
            await Promise.allSettled(
 | 
					            await Promise.allSettled(
 | 
				
			||||||
                MANAGER_BIDS.filter((bid) => !bid.page_context).map((apiBid) => {
 | 
					                MANAGER_BIDS.filter((bid) => !bid.page_context).map((apiBid) => {
 | 
				
			||||||
                    console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
 | 
					                    console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
 | 
				
			||||||
| 
						 | 
					@ -196,9 +195,12 @@ const workTracking = async () => {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(async () => {
 | 
					(async () => {
 | 
				
			||||||
    const socket = io(configs.SOCKET_URL, {
 | 
					    const socket = io(`${configs.SOCKET_URL}/bid-ws`, {
 | 
				
			||||||
        transports: ['websocket'],
 | 
					        transports: ['websocket'],
 | 
				
			||||||
        reconnection: true,
 | 
					        reconnection: true,
 | 
				
			||||||
 | 
					        extraHeaders: {
 | 
				
			||||||
 | 
					            Authorization: process.env.CLIENT_KEY,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // listen connect
 | 
					    // listen connect
 | 
				
			||||||
| 
						 | 
					@ -207,6 +209,11 @@ const workTracking = async () => {
 | 
				
			||||||
        console.log('🔗 Socket ID:', socket.id);
 | 
					        console.log('🔗 Socket ID:', socket.id);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // listen connect
 | 
				
			||||||
 | 
					    socket.on('disconnect', () => {
 | 
				
			||||||
 | 
					        console.log('❌Client key is valid. Disconnected');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // listen event
 | 
					    // listen event
 | 
				
			||||||
    socket.on('bidsUpdated', async (data) => {
 | 
					    socket.on('bidsUpdated', async (data) => {
 | 
				
			||||||
        console.log('📢 Bids Data:', data);
 | 
					        console.log('📢 Bids Data:', data);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					import BID_TYPE from '../system/bid-type.js';
 | 
				
			||||||
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';
 | 
					import _ from 'lodash';
 | 
				
			||||||
| 
						 | 
					@ -20,7 +21,7 @@ export class Bid {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            // await this.page_context.waitForSelector('#pageContainer', { timeout: 10000 });
 | 
					            // await this.page_context.waitForSelector('#pageContainer', { timeout: 10000 });
 | 
				
			||||||
            console.log(`✅ Page fully loaded. Taking snapshot for Product ID: ${this.id}`);
 | 
					            console.log(`✅ Page fully loaded. Taking snapshot for ${this.type === BID_TYPE.PRODUCT_TAB ? 'Product ID' : 'Tracking ID'}: ${this.id}`);
 | 
				
			||||||
            takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
 | 
					            takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            console.error(`❌ Error taking snapshot for Product ID: ${this.id}:`, error.message);
 | 
					            console.error(`❌ Error taking snapshot for Product ID: ${this.id}:`, error.message);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -218,6 +218,13 @@ export class GraysProductBid extends ProductBid {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const bidHistoriesItem = _.maxBy(this.histories, 'price');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (bidHistoriesItem && bidHistoriesItem.price === this.current_price) {
 | 
				
			||||||
 | 
					                console.log(`🔄 You have already bid on this item! (Bid Price: ${bidHistoriesItem.price})`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (price_value != bid_price) {
 | 
					            if (price_value != bid_price) {
 | 
				
			||||||
                console.log(`✍️ Updating bid price from ${price_value} → ${bid_price}`);
 | 
					                console.log(`✍️ Updating bid price from ${price_value} → ${bid_price}`);
 | 
				
			||||||
                await this.handleWritePrice(page, bid_price);
 | 
					                await this.handleWritePrice(page, bid_price);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,9 @@ import ax from 'axios';
 | 
				
			||||||
const axios = ax.create({
 | 
					const axios = ax.create({
 | 
				
			||||||
    // baseURL: 'http://172.18.2.125/api/v1/',
 | 
					    // baseURL: 'http://172.18.2.125/api/v1/',
 | 
				
			||||||
    baseURL: process.env.BASE_URL,
 | 
					    baseURL: process.env.BASE_URL,
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					        Authorization: process.env.CLIENT_KEY,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default axios;
 | 
					export default axios;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue