Fixed warehouse api auth and extended artefact type
This commit is contained in:
parent
3b2927e896
commit
8341d9d8ce
8
package-lock.json
generated
8
package-lock.json
generated
@ -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"
|
||||||
|
|||||||
@ -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",
|
||||||
@ -52,4 +52,4 @@
|
|||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 });
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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)} />}
|
||||||
|
|||||||
@ -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
8
src/types/ApiTypes.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Artefact } from "@prismaclient";
|
||||||
|
|
||||||
|
interface ExtendedArtefact extends Artefact {
|
||||||
|
location: string;
|
||||||
|
date: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ExtendedArtefact };
|
||||||
@ -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
5
src/types/JWT.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
interface JWTPayload {
|
||||||
|
userId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { JWTPayload };
|
||||||
33
src/utils/apiAuthMiddleware.ts
Normal file
33
src/utils/apiAuthMiddleware.ts
Normal 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 };
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user