Fixed warehouse api auth and extended artefact type

This commit is contained in:
Tim Howitz 2025-05-19 18:05:40 +01:00
parent 3b2927e896
commit 8341d9d8ce
11 changed files with 91 additions and 121 deletions

8
package-lock.json generated
View File

@ -35,7 +35,7 @@
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-node": "^1.0.2", "react-node": "^1.0.2",
"swr": "^2.3.3", "swr": "^2.3.3",
"zod": "^3.24.4" "zod": "^3.25.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@ -8095,9 +8095,9 @@
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "3.25.0", "version": "3.25.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.0.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.3.tgz",
"integrity": "sha512-ficnZKUW0mlNivqeJkosTEkGbJ6NKCtSaOHGx5aXbtfeWMdRyzXLbAIn19my4C/KB7WPY/p9vlGPt+qpOp6c4Q==", "integrity": "sha512-VGZqnyYNrl8JpEJRZaFPqeVNIuqgXNu4cXZ5cOb6zEUO1OxKbRnWB4UdDIXMmiERWncs0yDQukssHov8JUxykQ==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"

View File

@ -38,7 +38,7 @@
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-node": "^1.0.2", "react-node": "^1.0.2",
"swr": "^2.3.3", "swr": "^2.3.3",
"zod": "^3.24.4" "zod": "^3.25.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",

View File

@ -11,10 +11,7 @@ export async function POST(req: Request) {
try { try {
cookieStore = await cookies(); cookieStore = await cookies();
const token = cookieStore.get("jwt")?.value; const token = cookieStore.get("jwt")?.value;
if (!token) return NextResponse.json({ error: "No JWT found" }, { status: 401 });
if (!token) {
return NextResponse.json({ error: "No JWT found" }, { status: 401 });
}
const payload = await verifyJwt({ token, secret: env.JWT_SECRET_KEY }); const payload = await verifyJwt({ token, secret: env.JWT_SECRET_KEY });

View File

@ -1,105 +1,46 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { JWTPayload } from "@appTypes/JWT";
import { cookies } from "next/headers";
import { PrismaClient } from "@prismaclient"; import { PrismaClient } from "@prismaclient";
import { env } from "@utils/env"; import { env } from "@utils/env";
import { verifyJwt } from "@utils/verifyJwt"; import { verifyJwt } from "@utils/verifyJwt";
import { ExtendedArtefact } from "@appTypes/ApiTypes";
import { apiAuthMiddleware } from "@utils/apiAuthMiddleware";
const usingPrisma = false; const prisma = new PrismaClient();
let prisma: PrismaClient;
if (usingPrisma) prisma = new PrismaClient();
// Artefact type
interface Artefact {
id: number;
name: string;
description: string;
location: string;
earthquakeId: string;
isRequired: boolean;
isSold: boolean;
isCollected: boolean;
dateAdded: string;
}
export async function POST(req: Request) { export async function POST(req: Request) {
try { try {
// todo fix, moron the token will be in the cookie header const authResult = await apiAuthMiddleware();
const json = await req.json(); // Parse incoming JSON data if ("user" in authResult === false) return authResult; // Handle error response
const { token } = json.body;
if (!token) return NextResponse.json({ message: "Unauthorised" }, { status: 401 });
await verifyJwt({ token, secret: env.JWT_SECRET_KEY });
const warehouseArtefacts: Artefact[] = [ const { user } = authResult;
{
id: 1,
name: "Solidified Lava Chunk",
description: "A chunk of solidified lava from the 2023 Iceland eruption.",
location: "Reykjanes, Iceland",
earthquakeId: "EQ2023ICL",
isRequired: true,
isSold: false,
isCollected: false,
dateAdded: "2025-05-04",
},
{
id: 2,
name: "Tephra Sample",
description: "Foreign debris from the 2022 Tonga volcanic eruption.",
location: "Tonga",
earthquakeId: "EQ2022TGA",
isRequired: false,
isSold: true,
isCollected: true,
dateAdded: "2025-05-03",
},
{
id: 3,
name: "Ash Sample",
description: "Volcanic ash from the 2021 La Palma eruption.",
location: "La Palma, Spain",
earthquakeId: "EQ2021LPA",
isRequired: false,
isSold: false,
isCollected: false,
dateAdded: "2025-05-04",
},
{
id: 4,
name: "Ground Soil",
description: "Soil sample from the 2020 Croatia earthquake site.",
location: "Zagreb, Croatia",
earthquakeId: "EQ2020CRO",
isRequired: true,
isSold: false,
isCollected: false,
dateAdded: "2025-05-02",
},
{
id: 5,
name: "Basalt Fragment",
description: "Basalt rock from the 2019 New Zealand eruption.",
location: "White Island, New Zealand",
earthquakeId: "EQ2019NZL",
isRequired: false,
isSold: true,
isCollected: false,
dateAdded: "2025-05-04",
},
];
let artefacts; if (user.role !== "SCIENTIST" && user.role !== "ADMIN") {
if (usingPrisma) artefacts = await prisma.artefact.findMany(); return NextResponse.json({ message: "Not authorised" }, { status: 401 });
}
const artefacts = await prisma.artefact.findMany({
include: {
earthquake: true,
},
});
if (artefacts) { if (artefacts) {
return NextResponse.json({ message: "Got artefacts successfully", artefacts }, { status: 200 }); const extendedArtefacts: ExtendedArtefact[] = artefacts.map((x) => ({
...x,
location: x.earthquake.location,
date: x.earthquake.date,
}));
return NextResponse.json({ message: "Got artefacts successfully", artefacts: extendedArtefacts }, { status: 200 });
} else { } else {
return NextResponse.json({ message: "Got earthquakes successfully", earthquakes: warehouseArtefacts }, { status: 200 }); return NextResponse.json({ message: "Failed to get earthquakes" }, { status: 401 });
// return NextResponse.json({ message: "Failed to get earthquakes" }, { status: 401 });
} }
} catch (error) { } catch (error) {
console.error("Error in artefacts endpoint:", error); console.error("Error in artefacts endpoint:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
} finally { } finally {
if (usingPrisma) await prisma.$disconnect(); await prisma.$disconnect();
} }
} }

View File

@ -2,12 +2,11 @@
import Image from "next/image"; import Image from "next/image";
import { Dispatch, SetStateAction, useCallback, useState } from "react"; import { Dispatch, SetStateAction, useCallback, useState } from "react";
import Artefact from "@appTypes/Artefact"; import { ExtendedArtefact } from "@appTypes/ApiTypes";
import { Currency } from "@appTypes/StoreModel"; import { Currency } from "@appTypes/StoreModel";
import { useStoreState } from "@hooks/store"; import { useStoreState } from "@hooks/store";
// Artefacts Data const artefacts: ExtendedArtefact[] = [
const artefacts: Artefact[] = [
{ {
id: 1, id: 1,
name: "Golden Scarab", name: "Golden Scarab",

View File

@ -3,17 +3,14 @@ import { Dispatch, SetStateAction, useMemo, useState } from "react";
import { FaTimes } from "react-icons/fa"; import { FaTimes } from "react-icons/fa";
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6"; import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5"; import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5";
import { ExtendedArtefact } from "@appTypes/ApiTypes";
// import { Artefact } from "@appTypes/Prisma"; // import { Artefact } from "@appTypes/Prisma";
import type { Artefact } from "@prismaclient"; import type { Artefact } from "@prismaclient";
interface WarehouseArtefact extends Artefact {
location: string;
}
// Warehouse Artefacts Data // Warehouse Artefacts Data
const warehouseArtefacts: WarehouseArtefact[] = [ const extendedArtefacts: ExtendedArtefact[] = [
{ {
id: 1, id: 1,
name: "Solidified Lava Chunk", name: "Solidified Lava Chunk",
@ -699,7 +696,7 @@ export default function Warehouse() {
// Apply filters with loading state // Apply filters with loading state
const filteredArtefacts = useMemo(() => { const filteredArtefacts = useMemo(() => {
setIsFiltering(true); setIsFiltering(true);
const result = applyFilters(warehouseArtefacts, filters); const result = applyFilters(extendedArtefacts, filters);
setIsFiltering(false); setIsFiltering(false);
return result; return result;
}, [filters]); }, [filters]);
@ -707,10 +704,10 @@ export default function Warehouse() {
const currentArtefacts = filteredArtefacts.slice(indexOfFirstArtefact, indexOfLastArtefact); const currentArtefacts = filteredArtefacts.slice(indexOfFirstArtefact, indexOfLastArtefact);
// Overview stats // Overview stats
const totalArtefacts = warehouseArtefacts.length; const totalArtefacts = extendedArtefacts.length;
const today = new Date(); const today = new Date();
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.createdAt.toDateString() === today.toDateString()).length; const artefactsAddedToday = extendedArtefacts.filter((a) => a.createdAt.toDateString() === today.toDateString()).length;
const artefactsSoldToday = warehouseArtefacts.filter( const artefactsSoldToday = extendedArtefacts.filter(
(a) => a.isSold && a.createdAt.toDateString() === today.toDateString() (a) => a.isSold && a.createdAt.toDateString() === today.toDateString()
).length; ).length;
@ -803,8 +800,11 @@ export default function Warehouse() {
</div> </div>
</div> </div>
</div> </div>
{/* Modals */} {/* Modals */}
{/* // todo only admins and senior scientists can log, add not allowed modal for juniors */}
{/* // todo add new artefact/pallet saving */}
{/* // todo add existing artefact modifying */}
{/* // todo add pallet display */}
{showLogModal && <LogModal onClose={() => setShowLogModal(false)} />} {showLogModal && <LogModal onClose={() => setShowLogModal(false)} />}
{showBulkLogModal && <BulkLogModal onClose={() => setShowBulkLogModal(false)} />} {showBulkLogModal && <BulkLogModal onClose={() => setShowBulkLogModal(false)} />}
{editArtefact && <EditModal artefact={editArtefact} onClose={() => setEditArtefact(null)} />} {editArtefact && <EditModal artefact={editArtefact} onClose={() => setEditArtefact(null)} />}

View File

@ -10,6 +10,7 @@ interface AuthModalProps {
} }
export default function AuthModal({ isOpen, onClose }: AuthModalProps) { export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
// todo add login successful message
const [isLogin, setIsLogin] = useState<boolean>(true); const [isLogin, setIsLogin] = useState<boolean>(true);
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
const [isFailed, setIsFailed] = useState<boolean>(false); const [isFailed, setIsFailed] = useState<boolean>(false);

8
src/types/ApiTypes.ts Normal file
View File

@ -0,0 +1,8 @@
import { Artefact } from "@prismaclient";
interface ExtendedArtefact extends Artefact {
location: string;
date: Date;
}
export type { ExtendedArtefact };

View File

@ -1,14 +0,0 @@
interface Artefact {
// todo change to string
id: number;
name: string;
description: string;
location: string;
earthquakeID: string;
observatory: string;
dateReleased: string;
image: string;
price: number;
}
export default Artefact;

5
src/types/JWT.ts Normal file
View File

@ -0,0 +1,5 @@
interface JWTPayload {
userId: number;
}
export type { JWTPayload };

View File

@ -0,0 +1,33 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { verifyJwt } from "@utils/verifyJwt";
import { PrismaClient } from "@prismaclient";
import type { JWTPayload } from "@/types/JWT";
import { env } from "@utils/env";
const prisma = new PrismaClient();
export async function apiAuthMiddleware() {
const cookieStore = await cookies();
const token = cookieStore.get("jwt")?.value;
if (!token) {
return NextResponse.json({ error: "No JWT found" }, { status: 401 });
}
const payload = (await verifyJwt({
token,
secret: env.JWT_SECRET_KEY,
})) as unknown as JWTPayload;
const user = await prisma.user.findUnique({
where: { id: payload.userId },
});
if (!user) {
cookieStore.delete("jwt");
return NextResponse.json({ error: "Failed to get user" }, { status: 401 });
}
return { user, payload };
}