Skip to content

Commit

Permalink
Updated API and Dashboard UI.
Browse files Browse the repository at this point in the history
  • Loading branch information
Spytex committed Jul 20, 2023
1 parent 1482e56 commit b1eccce
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 66 deletions.
Binary file removed public/photos/95753337.jpg
Binary file not shown.
Binary file removed public/photos/95753393.jpg
Binary file not shown.
32 changes: 26 additions & 6 deletions src/components/screens/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => {
Expand Down Expand Up @@ -38,11 +38,11 @@ const Dashboard: FC = () => {
<Center>
<Button onClick={handleCreateApiKey}>Create API Key</Button>
</Center>
<Divider orientation="horizontal" py={2} />
<VStack py={4}>
<Divider orientation="horizontal" py={2} ml={-4} px={4} />
<VStack mt={4}>
{apiKeys.map((apiKey, index) => (
<Box key={index} display="flex" alignItems="center">
<Text>{apiKey}</Text>
<Box key={index} display="flex" alignItems="center" w="100%">
<InputWithClipboard apiKey={apiKey} />
<IconButton
ml={2}
icon={<DeleteIcon />}
Expand All @@ -59,4 +59,24 @@ const Dashboard: FC = () => {
);
};


const InputWithClipboard: FC<{ apiKey: string }> = ({ apiKey }) => {
const { onCopy, hasCopied } = useClipboard(apiKey);

return (
<InputGroup>
<Input value={apiKey} isReadOnly />
<InputRightAddon >
<IconButton
variant="ghost"
onClick={onCopy}
icon={hasCopied ? <CheckIcon /> : <CopyIcon />}
aria-label={hasCopied ? "Copied" : "Copy"}
/>
</InputRightAddon>
</InputGroup>
);
};


export default Dashboard;
10 changes: 7 additions & 3 deletions src/components/ui/wagon/WagonItem.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -10,6 +10,10 @@ export const WagonItem: FC<IWagonDataSingle> = ({ 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) {
Expand Down Expand Up @@ -92,12 +96,12 @@ export const WagonItem: FC<IWagonDataSingle> = ({ Wagon }) => {
</Flex>
{showQRCode && (
<Center mt={4}>
<QRCode value={Wagon.VagonNumber} />
<QRCode value={Wagon.VagonNumber} bgColor={bgColor} fgColor={lineColor}/>
</Center>
)}
{showBarcode && (
<Center mt={4} textAlign="center">
<Barcode value={Wagon.VagonNumber.toString().padStart(12, "0")} format="EAN13" />
<Barcode value={Wagon.VagonNumber.toString().padStart(12, "0")} format="EAN13" background={bgColor} lineColor={lineColor} />
</Center>
)}
<input type="file" accept="image/*" ref={fileInputRef} hidden onChange={handleFileUpload} />
Expand Down
11 changes: 11 additions & 0 deletions src/enums/httpMethod.enum.ts
Original file line number Diff line number Diff line change
@@ -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",
}
6 changes: 6 additions & 0 deletions src/interfaces/middleware.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next';

export interface IMiddlewareOptions {
req: NextApiRequest;
res: NextApiResponse;
}
65 changes: 49 additions & 16 deletions src/pages/api/keys.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
89 changes: 54 additions & 35 deletions src/pages/api/photo.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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";


Expand All @@ -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) {
Expand Down
31 changes: 31 additions & 0 deletions src/pages/api/wagons/index.ts
Original file line number Diff line number Diff line change
@@ -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' });
}
}
6 changes: 6 additions & 0 deletions src/types/middleware.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { HttpMethod } from "@/enums/httpMethod.enum";
import { IMiddlewareOptions } from "@/interfaces/middleware.interface";

export type Middlewares = Partial<
Record<HttpMethod, (options: IMiddlewareOptions) => any>
>

0 comments on commit b1eccce

Please sign in to comment.