diff --git a/public/photos/95753337.jpg b/public/photos/95753337.jpg deleted file mode 100644 index 67b6222..0000000 Binary files a/public/photos/95753337.jpg and /dev/null differ diff --git a/public/photos/95753393.jpg b/public/photos/95753393.jpg deleted file mode 100644 index 67b6222..0000000 Binary files a/public/photos/95753393.jpg and /dev/null differ diff --git a/src/components/screens/dashboard/Dashboard.tsx b/src/components/screens/dashboard/Dashboard.tsx index 9ada980..6baf5f1 100644 --- a/src/components/screens/dashboard/Dashboard.tsx +++ b/src/components/screens/dashboard/Dashboard.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useState } from 'react'; import Layout from '@/components/layout/Layout'; -import { Box, Button, Center, Container, Divider, Flex, IconButton, Text, VStack } from '@chakra-ui/react'; -import { DeleteIcon } from '@chakra-ui/icons'; +import { Box, Button, Center, Container, Divider, Flex, IconButton, Input, InputGroup, InputRightAddon, Text, VStack, useClipboard } from '@chakra-ui/react'; +import { DeleteIcon, CopyIcon, CheckIcon } from '@chakra-ui/icons'; import { KeyService } from "@/service/key.service"; const Dashboard: FC = () => { @@ -38,11 +38,11 @@ const Dashboard: FC = () => {
- - + + {apiKeys.map((apiKey, index) => ( - - {apiKey} + + } @@ -59,4 +59,24 @@ const Dashboard: FC = () => { ); }; + +const InputWithClipboard: FC<{ apiKey: string }> = ({ apiKey }) => { + const { onCopy, hasCopied } = useClipboard(apiKey); + + return ( + + + + : } + aria-label={hasCopied ? "Copied" : "Copy"} + /> + + + ); +}; + + export default Dashboard; diff --git a/src/components/ui/wagon/WagonItem.tsx b/src/components/ui/wagon/WagonItem.tsx index cd5e22b..5cb8830 100644 --- a/src/components/ui/wagon/WagonItem.tsx +++ b/src/components/ui/wagon/WagonItem.tsx @@ -1,6 +1,6 @@ import { FC, useRef, useState } from "react"; import { IWagonDataSingle } from "@/interfaces/wagon.interface"; -import { Box, Button, Center, Flex, Link, Text, useToast, VStack } from "@chakra-ui/react"; +import { Box, Button, Center, Flex, Link, Text, useToast, VStack, useColorMode, useColorModeValue } from "@chakra-ui/react"; import { PhotoService } from "@/service/photo.service"; import QRCode from "qrcode.react"; import Barcode from "react-barcode"; @@ -10,6 +10,10 @@ export const WagonItem: FC = ({ Wagon }) => { const toast = useToast(); const [showQRCode, setShowQRCode] = useState(false); const [showBarcode, setShowBarcode] = useState(false); + const { colorMode } = useColorMode(); + const bgColor = useColorModeValue('#FFFFFF', '#1a202c'); + const lineColor = useColorModeValue('#000000', '#FFFFFF'); + const handleFileInputChange = () => { if (fileInputRef.current) { @@ -92,12 +96,12 @@ export const WagonItem: FC = ({ Wagon }) => { {showQRCode && (
- +
)} {showBarcode && (
- +
)} diff --git a/src/enums/httpMethod.enum.ts b/src/enums/httpMethod.enum.ts new file mode 100644 index 0000000..9d14372 --- /dev/null +++ b/src/enums/httpMethod.enum.ts @@ -0,0 +1,11 @@ +export enum HttpMethod { + GET = "GET", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE", + HEAD = "HEAD", + CONNECT = "CONNECT", + OPTIONS = "OPTIONS", + TRACE = "TRACE", + PATCH = "PATCH", +} diff --git a/src/interfaces/middleware.interface.ts b/src/interfaces/middleware.interface.ts new file mode 100644 index 0000000..218894b --- /dev/null +++ b/src/interfaces/middleware.interface.ts @@ -0,0 +1,6 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +export interface IMiddlewareOptions { + req: NextApiRequest; + res: NextApiResponse; +} diff --git a/src/pages/api/keys.ts b/src/pages/api/keys.ts index 30d7f1f..f6abb61 100644 --- a/src/pages/api/keys.ts +++ b/src/pages/api/keys.ts @@ -1,34 +1,67 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { v4 as uuidv4 } from 'uuid'; import { createClient } from '@node-redis/client'; +import { HttpMethod } from '@/enums/httpMethod.enum'; +import { IMiddlewareOptions } from '@/interfaces/middleware.interface'; +import { Middlewares } from '@/types/middleware.type'; const client = createClient(); const redisKey = 'apiKeys'; +const middlewares: Middlewares = { + [HttpMethod.GET]: getKeys, + [HttpMethod.POST]: postKey, + [HttpMethod.DELETE]: deleteKey, +} + + + +async function getKeys(options: IMiddlewareOptions) { + const { res } = options; + const elements = await client.sMembers(redisKey); + res.status(200).json(elements); +} + +async function postKey(options: IMiddlewareOptions) { + const { res } = options; + await client.sAdd(redisKey, uuidv4()); + res.status(200).end(); +} + +async function deleteKey(options: IMiddlewareOptions) { + const { res, req } = options; + const { apiKey } = req.query; + if (typeof apiKey === 'string') { + await client.sRem(redisKey, apiKey); + res.status(200).end(); + } else { + res.status(400).json({ message: 'Invalid request' }); + } +} + export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (!client.isOpen) { await client.connect(); } - if (req.method === 'POST') { - await client.sAdd(redisKey, uuidv4()); - res.status(200).end(); + const method = req.method as HttpMethod; - } else if (req.method === 'GET') { - const elements = await client.sMembers(redisKey); - res.status(200).json(elements); - } else if (req.method === 'DELETE') { - const { apiKey } = req.query; - if (typeof apiKey === 'string') { - await client.sRem(redisKey, apiKey); - res.status(200).end(); - - } else { - res.status(400).json({ message: 'Invalid request' }); - } - } else { + if (!Object.keys(middlewares).includes(method)) { res.setHeader('Allow', ['POST', 'GET', 'DELETE']); res.status(405).end(`Method ${req.method} Not Allowed`); } + + const middleware = middlewares[method]; + + if (!middleware) { + return res.status(500).end() + } + + try { + await middleware({ req, res }); + } catch (error) { + console.error(JSON.stringify(error)); + return res.status(500).end(); + } } diff --git a/src/pages/api/photo.ts b/src/pages/api/photo.ts index af6142b..5478aa0 100644 --- a/src/pages/api/photo.ts +++ b/src/pages/api/photo.ts @@ -1,34 +1,38 @@ -import { NextApiHandler, NextApiRequest } from "next"; +import { NextApiRequest, NextApiResponse } from "next"; import formidable from "formidable"; import path from "path"; import fs from "fs/promises"; +import { HttpMethod } from '@/enums/httpMethod.enum'; +import { IMiddlewareOptions } from '@/interfaces/middleware.interface'; +import { Middlewares } from '@/types/middleware.type'; -export const config = { - api: { - bodyParser: false, - }, -}; -const readFile = ( +const middlewares: Middlewares = { + [HttpMethod.POST]: postPhoto, + [HttpMethod.DELETE]: deletePhoto, +} + + +const readFile = async ( req: NextApiRequest, saveLocally?: boolean, ): Promise<{ fields: formidable.Fields; files: formidable.Files }> => { - const options: formidable.Options = {}; + const formidableOptions: formidable.Options = {}; if (saveLocally) { - options.uploadDir = path.join(process.cwd(), "/public/photos"); - options.filename = () => { + formidableOptions.uploadDir = path.join(process.cwd(), "/public/photos"); + formidableOptions.filename = () => { return `${req.query.VagonNumber as string}.jpg`; }; } - options.maxFileSize = 4000 * 1024 * 1024; - const form = formidable(options); + formidableOptions.maxFileSize = 4000 * 1024 * 1024; + const form = formidable(formidableOptions); return new Promise((resolve, reject) => { form.parse(req, (err, fields, files) => { if (err) reject(err); resolve({ fields, files }); }); }); -}; +} const deleteFile = async (req: NextApiRequest) => { const { VagonNumber } = req.query; @@ -38,29 +42,44 @@ const deleteFile = async (req: NextApiRequest) => { } catch (error) { throw new Error(`Error deleting file: ${error}`); } -}; +} + + +async function postPhoto(options: IMiddlewareOptions) { + const { res, req } = options; + try { + await fs.readdir(path.join(process.cwd() + "/public", "/photos")); + } catch (error) { + await fs.mkdir(path.join(process.cwd() + "/public", "/photos")); + } + await readFile(req, true); + res.json({ done: "ok" }); +} -const handler: NextApiHandler = async (req, res) => { - const { method } = req; +async function deletePhoto(options: IMiddlewareOptions) { + const { res, req } = options; + await deleteFile(req); + res.status(200).json({ message: "File deleted successfully" }); +} - if (method === "DELETE") { - try { - await deleteFile(req); - res.status(200).json({ message: "File deleted successfully" }); - } catch (error) { - res.status(500).json({ message: error }); - } - } else if (method === "POST") { - try { - await fs.readdir(path.join(process.cwd() + "/public", "/photos")); - } catch (error) { - await fs.mkdir(path.join(process.cwd() + "/public", "/photos")); - } - await readFile(req, true); - res.json({ done: "ok" }); - } else { - res.status(405).json({ message: "Method not supported" }); + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const method = req.method as HttpMethod; + if (!Object.keys(middlewares).includes(method)) { + res.setHeader('Allow', ['POST', 'GET', 'DELETE']); + res.status(405).end(`Method ${req.method} Not Allowed`); + } + + const middleware = middlewares[method]; + + if (!middleware) { + return res.status(500).end() } -}; -export default handler; + try { + await middleware({ req, res }); + } catch (error) { + console.error(JSON.stringify(error)); + return res.status(500).end(); + } +} diff --git a/src/pages/api/wagons.ts b/src/pages/api/wagons/[VagonNumber].ts similarity index 85% rename from src/pages/api/wagons.ts rename to src/pages/api/wagons/[VagonNumber].ts index 46e2868..157a471 100644 --- a/src/pages/api/wagons.ts +++ b/src/pages/api/wagons/[VagonNumber].ts @@ -1,6 +1,5 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { WagonService } from '@/service/wagon.service'; -import { KeyService } from "@/service/key.service"; import { createClient } from "@node-redis/client"; @@ -24,11 +23,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const wagonNumber = query.VagonNumber as string; - if (!wagonNumber) { - const wagons = await WagonService.getAll(); - return res.status(200).json(wagons); - } - const wagon = await WagonService.getOne(wagonNumber); if (!wagon) { diff --git a/src/pages/api/wagons/index.ts b/src/pages/api/wagons/index.ts new file mode 100644 index 0000000..020f67f --- /dev/null +++ b/src/pages/api/wagons/index.ts @@ -0,0 +1,31 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { WagonService } from '@/service/wagon.service'; +import { createClient } from "@node-redis/client"; + + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const { query } = req; + const apiKey = query.apiKey as string; + const redisKey = 'apiKeys'; + + const client = createClient(); + + if (!client.isOpen) { + await client.connect(); + } + + const validation = await client.sIsMember(redisKey, apiKey); + + if (!apiKey || !validation) { + return res.status(401).json({ error: 'Invalid API key' }); + } + + const wagons = await WagonService.getAll(); + return res.status(200).json(wagons); + + } catch (error) { + console.error('Error retrieving wagon:', error); + return res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/src/types/middleware.type.ts b/src/types/middleware.type.ts new file mode 100644 index 0000000..cb98f7a --- /dev/null +++ b/src/types/middleware.type.ts @@ -0,0 +1,6 @@ +import { HttpMethod } from "@/enums/httpMethod.enum"; +import { IMiddlewareOptions } from "@/interfaces/middleware.interface"; + +export type Middlewares = Partial< + Record any> +>