Merge branch 'master' of 192.168.11.4:thowitz-work/tremor-tracker
This commit is contained in:
commit
0a34470e65
6
importersFixed.md
Normal file
6
importersFixed.md
Normal file
@ -0,0 +1,6 @@
|
||||
- [x] Import users
|
||||
- [x] Import artefacts
|
||||
- [x] Import earthquakes
|
||||
- [x] Import observatories
|
||||
- [ ] Import requests
|
||||
- [x] Import scientists
|
||||
6
importersOrder.md
Normal file
6
importersOrder.md
Normal file
@ -0,0 +1,6 @@
|
||||
1. Import users
|
||||
2. Import scientists
|
||||
3. Import observatories
|
||||
4. Import earthquakes
|
||||
5. Import artefacts
|
||||
6. Import requests
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@ -35,7 +35,7 @@
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-node": "^1.0.2",
|
||||
"swr": "^2.3.3",
|
||||
"zod": "^3.24.4"
|
||||
"zod": "^3.25.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
@ -8095,9 +8095,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.0",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.0.tgz",
|
||||
"integrity": "sha512-ficnZKUW0mlNivqeJkosTEkGbJ6NKCtSaOHGx5aXbtfeWMdRyzXLbAIn19my4C/KB7WPY/p9vlGPt+qpOp6c4Q==",
|
||||
"version": "3.25.3",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.3.tgz",
|
||||
"integrity": "sha512-VGZqnyYNrl8JpEJRZaFPqeVNIuqgXNu4cXZ5cOb6zEUO1OxKbRnWB4UdDIXMmiERWncs0yDQukssHov8JUxykQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-node": "^1.0.2",
|
||||
"swr": "^2.3.3",
|
||||
"zod": "^3.24.4"
|
||||
"zod": "^3.25.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
|
||||
@ -70,9 +70,9 @@ model Observatory {
|
||||
updatedAt DateTime @updatedAt
|
||||
name String
|
||||
location String
|
||||
longitude String
|
||||
latitude String
|
||||
dateEstablished Int?
|
||||
longitude Float
|
||||
latitude Float
|
||||
dateEstablished DateTime
|
||||
isFunctional Boolean
|
||||
seismicSensorOnline Boolean @default(true)
|
||||
creatorId Int?
|
||||
@ -88,7 +88,7 @@ model Artefact {
|
||||
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
||||
warehouseArea String
|
||||
description String
|
||||
imagePath String
|
||||
imageName String
|
||||
earthquakeId Int
|
||||
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
||||
creatorId Int?
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
Name,Type,WarehouseArea,Description,earthquakeID,Price,Required,PickedUp,Picture
|
||||
Name,Type,WarehouseArea,Description,EarthquakeCode,Price,Required,PickedUp,Picture
|
||||
Echo Bomb,Lava,ShelvingAreaA,A dense glossy black volcanic bomb with minor vesicles.,EV-7.4-Mexico-00035,120,no,no,EchoBomb.PNG
|
||||
Silvershade Ash,Ash,ShelvingAreaD,Fine light-grey volcanic ash collected near a village.,EV-6.0-Iceland-00018,40,no,no,SilvershadeAsh.PNG
|
||||
Strata Core,Soil,LoggingArea,Soil core with visible stratification showing evidence of liquefaction.,ET-6.9-Brazil-00046,30,no,no,StrataCore.PNG
|
||||
|
||||
|
@ -1,25 +1,26 @@
|
||||
Pacific Apex Seismic Center,"Aleutian Trench, Alaska, USA",53.0000,-168.0000,1973-06-15,Yes
|
||||
Cascadia Quake Research Institute,"Oregon Coast, USA",44.5000,-124.0000,1985-03-22,Yes
|
||||
Andes Fault Survey Observatory,"Nazca-South American Plate, Santiago, Chile",-33.4500,-70.6667,1992-10-10,Yes
|
||||
Kermadec Deep Motion Lab,"Kermadec Trench, northeast of New Zealand",-29.5000,-177.0000,2001-05-14,Yes
|
||||
Japan Trench Monitoring Station,"Off the coast of Sendai, Japan",38.5000,143.0000,1968-09-01,Yes
|
||||
Himalayan Rift Observatory,"Nepal-Tibet border, near Mount Everest",28.5000,86.5000,1998-12-08,Yes
|
||||
East African Rift Monitoring Center,"Rift Valley, near Nairobi, Kenya",-1.2921,36.8219,2010-03-30,Yes
|
||||
Reykjavik Seismic Monitoring Hub,"Mid-Atlantic Ridge, Reykjavik, Iceland",64.1355,-21.8954,1957-11-17,Yes
|
||||
Azores Tectonic Research Base,"Azores Archipelago, Portugal",37.7412,-25.6756,1982-07-04,Yes
|
||||
Sumatra Subduction Observatory,"West Coast of Indonesia, Aceh Province",4.6951,95.5539,2005-02-16,Yes
|
||||
Tonga Trench Seismographic Unit,"Off the coast of Tonga",-20.0000,-175.0000,1994-08-21,Yes
|
||||
San Andreas Fault Research Center,"San Francisco Bay Area, California, USA",37.7749,-122.4194,1929-10-07,Yes
|
||||
Kamchatka Seismic Laboratory,"Kamchatka Peninsula, Russia",56.0000,160.0000,1978-01-12,Yes
|
||||
Hawaii Island Seismic Research Station,"Hawaii Big Island, USA",19.8968,-155.5828,1989-05-06,Yes
|
||||
Cascadia Ridge Offshore Observatory,"Offshore of Vancouver Island, British Columbia, Canada",48.4284,-123.3656,2003-11-18,Yes
|
||||
Zagros Fault Zone Research Center,"Western Iran, near Kermanshah",34.0000,46.0000,1990-06-28,Yes
|
||||
Manila Trench Seismic Observatory,"South China Sea, west of Luzon, Philippines",15.0000,120.0000,1996-04-09,Yes
|
||||
Caribbean Subduction Monitoring Station,"Lesser Antilles Subduction Zone, Martinique",14.6000,-61.0000,2008-09-23,Yes
|
||||
Gorda Plate Analysis Hub,"Mendocino Triple Junction, California, USA",40.0000,-124.0000,1952-12-01,Yes
|
||||
Red Sea Rift Research Base,"Southern Red Sea, Eritrea",15.0000,42.0000,2015-07-15,Yes
|
||||
Sumatra Coastal Reserve Observatory,"West Coast of Sumatra, Indonesia",-2.0000,100.5000,1980-03-11,No
|
||||
Antarctic Polar Seismology Station,"Rift vicinity near Ross Ice Shelf, Antarctica",-78.3000,-166.2500,1974-06-01,No
|
||||
Yucatan Seismic Monitoring Site,"Cocos Plate near Yucatan Peninsula, Mexico",20.7000,-90.8000,1965-09-23,No
|
||||
Makran Subduction Fault Observatory,"Coastal Pakistan and Iran junction",25.5000,62.0000,1990-08-29,No
|
||||
Baltic Continental Drift Center,"Southeastern Sweden, Baltic Shield zone",56.0000,15.0000,1987-12-12,No
|
||||
Name,Location,Latitude,Longitude,DateEstablished,Functional,SeismicSensorOnline
|
||||
Pacific Apex Seismic Center,"Aleutian Trench, Alaska, USA",53.0000,-168.0000,1973-06-15,Yes,Yes
|
||||
Cascadia Quake Research Institute,"Oregon Coast, USA",44.5000,-124.0000,1985-03-22,Yes,Yes
|
||||
Andes Fault Survey Observatory,"Nazca-South American Plate, Santiago, Chile",-33.4500,-70.6667,1992-10-10,Yes,Yes
|
||||
Kermadec Deep Motion Lab,"Kermadec Trench, northeast of New Zealand",-29.5000,-177.0000,2001-05-14,Yes,Yes
|
||||
Japan Trench Monitoring Station,"Off the coast of Sendai, Japan",38.5000,143.0000,1968-09-01,Yes,Yes
|
||||
Himalayan Rift Observatory,"Nepal-Tibet border, near Mount Everest",28.5000,86.5000,1998-12-08,Yes,Yes
|
||||
East African Rift Monitoring Center,"Rift Valley, near Nairobi, Kenya",-1.2921,36.8219,2010-03-30,Yes,Yes
|
||||
Reykjavik Seismic Monitoring Hub,"Mid-Atlantic Ridge, Reykjavik, Iceland",64.1355,-21.8954,1957-11-17,Yes,Yes
|
||||
Azores Tectonic Research Base,"Azores Archipelago, Portugal",37.7412,-25.6756,1982-07-04,Yes,Yes
|
||||
Sumatra Subduction Observatory,"West Coast of Indonesia, Aceh Province",4.6951,95.5539,2005-02-16,Yes,Yes
|
||||
Tonga Trench Seismographic Unit,"Off the coast of Tonga",-20.0000,-175.0000,1994-08-21,Yes,Yes
|
||||
San Andreas Fault Research Center,"San Francisco Bay Area, California, USA",37.7749,-122.4194,1929-10-07,Yes,Yes
|
||||
Kamchatka Seismic Laboratory,"Kamchatka Peninsula, Russia",56.0000,160.0000,1978-01-12,Yes,Yes
|
||||
Hawaii Island Seismic Research Station,"Hawaii Big Island, USA",19.8968,-155.5828,1989-05-06,Yes,Yes
|
||||
Cascadia Ridge Offshore Observatory,"Offshore of Vancouver Island, British Columbia, Canada",48.4284,-123.3656,2003-11-18,Yes,Yes
|
||||
Zagros Fault Zone Research Center,"Western Iran, near Kermanshah",34.0000,46.0000,1990-06-28,Yes,Yes
|
||||
Manila Trench Seismic Observatory,"South China Sea, west of Luzon, Philippines",15.0000,120.0000,1996-04-09,Yes,Yes
|
||||
Caribbean Subduction Monitoring Station,"Lesser Antilles Subduction Zone, Martinique",14.6000,-61.0000,2008-09-23,Yes,Yes
|
||||
Gorda Plate Analysis Hub,"Mendocino Triple Junction, California, USA",40.0000,-124.0000,1952-12-01,Yes,Yes
|
||||
Red Sea Rift Research Base,"Southern Red Sea, Eritrea",15.0000,42.0000,2015-07-15,Yes,Yes
|
||||
Sumatra Coastal Reserve Observatory,"West Coast of Sumatra, Indonesia",-2.0000,100.5000,1980-03-11,Yes,Yes
|
||||
Antarctic Polar Seismology Station,"Rift vicinity near Ross Ice Shelf, Antarctica",-78.3000,-166.2500,1974-06-01,Yes,Yes
|
||||
Yucatan Seismic Monitoring Site,"Cocos Plate near Yucatan Peninsula, Mexico",20.7000,-90.8000,1965-09-23,Yes,Yes
|
||||
Makran Subduction Fault Observatory,"Coastal Pakistan and Iran junction",25.5000,62.0000,1990-08-29,Yes,Yes
|
||||
Baltic Continental Drift Center,"Southeastern Sweden, Baltic Shield zone",56.0000,15.0000,1987-12-12,Yes,Yes
|
||||
|
@ -1,10 +1,10 @@
|
||||
Name,Level,SuperiorName
|
||||
Dr. Emily Neighbour Carter,Junior,Dr. Rajiv Menon
|
||||
Dr. Rajiv Menon,Senior,None
|
||||
Dr. Izzy Patterson,Senior,None
|
||||
Dr. Hiroshi Takeda,Senior,None
|
||||
Dr. Miriam Hassan,Senior,None
|
||||
Dr. Alice Johnson,Senior,None
|
||||
Tim Howitz,Admin,None
|
||||
Dr. Natalia Petrova,Junior,Dr. Izzy Patteron
|
||||
Dr. Li Cheng,Junior,Dr. Rajiv Menon
|
||||
Dr. Javier Ortega,Junior,Dr. Izzy Patterson
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { PrismaClient } from '@prismaclient';
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
if (usingPrisma) prisma = new PrismaClient();
|
||||
import { prisma } from '@utils/prisma';
|
||||
|
||||
// todo add specification of date range in request
|
||||
|
||||
@ -35,7 +31,5 @@ export async function POST(req: Request) {
|
||||
} catch (error) {
|
||||
console.error("Error in earthquakes endpoint:", error);
|
||||
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
|
||||
} finally {
|
||||
if (usingPrisma) await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { env } from "@utils/env";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { verifyJwt } from "@utils/verifyJwt";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { prisma } from "@utils/prisma";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
let cookieStore;
|
||||
try {
|
||||
cookieStore = await cookies();
|
||||
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 });
|
||||
|
||||
@ -44,7 +39,5 @@ export async function POST(req: Request) {
|
||||
console.error("Error in user endpoint:", error);
|
||||
cookieStore?.delete("jwt"); // Delete JWT cookie on error
|
||||
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
87
src/app/api/import-artefacts/route.ts
Normal file
87
src/app/api/import-artefacts/route.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
import { stringToBool } from "@utils/parsingUtils";
|
||||
import { prisma } from "@utils/prisma";
|
||||
import { getRandomNumber } from "@utils/maths";
|
||||
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/Artefacts.csv");
|
||||
|
||||
type CsvRow = {
|
||||
Type: string;
|
||||
Name: string;
|
||||
Description: string;
|
||||
WarehouseArea: string;
|
||||
EarthquakeCode: string;
|
||||
Required?: string;
|
||||
Price: string;
|
||||
PickedUp?: string;
|
||||
Picture: string;
|
||||
};
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
const failedImports: { row: CsvRow; reason: string }[] = [];
|
||||
|
||||
const artefacts = await Promise.all(
|
||||
records.map(async (row) => {
|
||||
const earthquake = await prisma.earthquake.findUnique({
|
||||
where: { code: row.EarthquakeCode },
|
||||
});
|
||||
|
||||
const creators = await prisma.user.findMany({
|
||||
where: {
|
||||
role: { in: ["SCIENTIST", "ADMIN"] },
|
||||
},
|
||||
});
|
||||
const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null;
|
||||
|
||||
if (!earthquake || !randomCreator) {
|
||||
failedImports.push({ row, reason: `Earthquake: ${earthquake}, RandomCreator: ${randomCreator}` });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name: row.Name,
|
||||
description: row.Description,
|
||||
type: row.Type,
|
||||
warehouseArea: row.WarehouseArea,
|
||||
earthquakeId: earthquake.id,
|
||||
required: stringToBool(row.Required, true),
|
||||
shopPrice: row.Price && row.Price !== "" ? parseFloat(row.Price) : getRandomNumber(20, 500),
|
||||
pickedUp: stringToBool(row.PickedUp, false),
|
||||
creatorId: randomCreator.id,
|
||||
purchasedById: null,
|
||||
imageName: row.Picture,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const validArtefacts = artefacts.filter((artefact): artefact is NonNullable<typeof artefact> => artefact !== null);
|
||||
|
||||
await prisma.artefact.createMany({
|
||||
data: validArtefacts,
|
||||
});
|
||||
|
||||
if (failedImports.length > 0) {
|
||||
console.warn("Failed imports:", failedImports);
|
||||
await fs.writeFile(path.resolve(process.cwd(), "failed_imports_artefacts.json"), JSON.stringify(failedImports, null, 2));
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: validArtefacts.length,
|
||||
failedCount: failedImports.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// CSV location
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/artefacts.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Type: string;
|
||||
Name: string;
|
||||
Description: string;
|
||||
WarehouseArea: string;
|
||||
EarthquakeId: string;
|
||||
Required?: string;
|
||||
ShopPrice?: string;
|
||||
PickedUp?: string;
|
||||
};
|
||||
|
||||
function stringToBool(val: string | undefined, defaultValue: boolean = false): boolean {
|
||||
if (!val) return defaultValue;
|
||||
return /^true$/i.test(val.trim());
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Map records to artefact input
|
||||
const artefacts = records.map((row) => ({
|
||||
name: row.Name,
|
||||
description: row.Description,
|
||||
type: row.Type,
|
||||
warehouseArea: row.WarehouseArea,
|
||||
// todo get earthquakeId where code === row.EarthquakeCode
|
||||
earthquakeId: parseInt(row.EarthquakeId, 10),
|
||||
required: stringToBool(row.Required, true), // default TRUE
|
||||
shopPrice: row.ShopPrice && row.ShopPrice !== "" ? parseFloat(row.ShopPrice) : null,
|
||||
pickedUp: stringToBool(row.PickedUp, false), // default FALSE
|
||||
// todo add random selection for creatorId
|
||||
creatorId: null,
|
||||
purchasedById: null,
|
||||
}));
|
||||
|
||||
// 4. Bulk insert
|
||||
await prisma.artefact.createMany({
|
||||
data: artefacts,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: artefacts.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
@ -3,11 +3,11 @@ import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { getRandomNumber } from "@utils/maths";
|
||||
import { prisma } from "@utils/prisma";
|
||||
|
||||
// Path to your earthquakes.csv
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/earthquakes.csv");
|
||||
const prisma = new PrismaClient();
|
||||
const DISTANCE_THRESHOLD_KM = 500; // Max distance for observatory matching
|
||||
|
||||
type CsvRow = {
|
||||
Date: string;
|
||||
@ -20,49 +20,107 @@ type CsvRow = {
|
||||
Depth: string;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Haversine formula to calculate distance between two points (in km)
|
||||
function getDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
||||
const R = 6371; // Earth's radius in km
|
||||
const dLat = (lat2 - lat1) * (Math.PI / 180);
|
||||
const dLon = (lon2 - lon1) * (Math.PI / 180);
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return R * c;
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// Get a random user ID
|
||||
const randomUserArr: Array<{ id: number }> = await prisma.$queryRaw`
|
||||
SELECT TOP 1 id FROM [user] ORDER BY NEWID()
|
||||
`;
|
||||
if (!randomUserArr.length) {
|
||||
return NextResponse.json({ error: 'No users found.' }, { status: 404 });
|
||||
}
|
||||
const userId = randomUserArr[0].id;
|
||||
|
||||
// 1. Read the CSV file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
// 2. Parse the CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
// 3. Transform to fit Earthquake model
|
||||
const earthquakes = records.map((row) => ({
|
||||
|
||||
const failedImports: { row: CsvRow; reason: string }[] = [];
|
||||
|
||||
// Fetch all observatories once to avoid repeated queries
|
||||
const observatories = await prisma.observatory.findMany({
|
||||
select: { id: true, latitude: true, longitude: true },
|
||||
});
|
||||
|
||||
const earthquakes = await Promise.all(
|
||||
records.map(async (row) => {
|
||||
const creators = await prisma.user.findMany({
|
||||
where: { role: { in: ["SCIENTIST", "ADMIN"] } },
|
||||
});
|
||||
const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null;
|
||||
|
||||
if (!randomCreator) {
|
||||
failedImports.push({ row, reason: `RandomCreator: ${randomCreator}` });
|
||||
return null;
|
||||
}
|
||||
|
||||
const eqLat = parseFloat(row.Latitude);
|
||||
const eqLon = parseFloat(row.Longitude);
|
||||
|
||||
// Find observatories within distance threshold
|
||||
let nearbyObservatories = observatories
|
||||
.map((obs) => ({
|
||||
id: obs.id,
|
||||
distance: getDistance(eqLat, eqLon, obs.latitude, obs.longitude),
|
||||
}))
|
||||
.filter((obs) => obs.distance <= DISTANCE_THRESHOLD_KM)
|
||||
.map((obs) => ({ id: obs.id }));
|
||||
|
||||
// If no observatories within range, find the nearest one
|
||||
if (nearbyObservatories.length === 0) {
|
||||
const nearest = observatories.reduce(
|
||||
(min, obs) => {
|
||||
const distance = getDistance(eqLat, eqLon, obs.latitude, obs.longitude);
|
||||
return distance < min.distance ? { id: obs.id, distance } : min;
|
||||
},
|
||||
{ id: -1, distance: Infinity }
|
||||
);
|
||||
if (nearest.id !== -1) {
|
||||
nearbyObservatories = [{ id: nearest.id }];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
date: new Date(row.Date),
|
||||
code: row.Code,
|
||||
magnitude: parseFloat(row.Magnitude),
|
||||
type: row.Type,
|
||||
latitude: parseFloat(row.Latitude),
|
||||
longitude: parseFloat(row.Longitude),
|
||||
latitude: eqLat,
|
||||
longitude: eqLon,
|
||||
location: row.Location,
|
||||
depth: row.Depth, // store as received
|
||||
// todo add random selection for creatorId
|
||||
creatorId: userId,
|
||||
}));
|
||||
// 4. Bulk create earthquakes in database:
|
||||
depth: row.Depth,
|
||||
creatorId: randomCreator.id,
|
||||
observatories: { connect: nearbyObservatories },
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const validEarthquakes = earthquakes.filter(
|
||||
(earthquake): earthquake is NonNullable<typeof earthquake> => earthquake !== null
|
||||
);
|
||||
|
||||
// Bulk insert earthquakes with observatory connections
|
||||
await prisma.earthquake.createMany({
|
||||
data: earthquakes,
|
||||
data: validEarthquakes,
|
||||
});
|
||||
|
||||
if (failedImports.length > 0) {
|
||||
console.warn("Failed imports:", failedImports);
|
||||
await fs.writeFile(path.resolve(process.cwd(), "failed_imports_earthquakes.json"), JSON.stringify(failedImports, null, 2));
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: validEarthquakes.length,
|
||||
failedCount: failedImports.length,
|
||||
});
|
||||
return NextResponse.json({ success: true, count: earthquakes.length });
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +1,83 @@
|
||||
import { parse } from "csv-parse/sync";
|
||||
import { stringToBool } from "@utils/parsingUtils";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
import { prisma } from "@utils/prisma";
|
||||
import { getRandomNumber } from "@utils/maths";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// CSV location (update filename as needed)
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/observatories.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Name: string;
|
||||
Location: string;
|
||||
Latitude: string;
|
||||
Longitude: string;
|
||||
DateEstablished?: string;
|
||||
DateEstablished: string;
|
||||
Functional: string;
|
||||
SeismicSensorOnline?: string;
|
||||
SeismicSensorOnline: string;
|
||||
};
|
||||
|
||||
function stringToBool(val: string | undefined): boolean {
|
||||
// Accepts "TRUE", "true", "True", etc.
|
||||
if (!val) return false;
|
||||
return /^true$/i.test(val.trim());
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Map records to Prisma inputs
|
||||
const observatories = records.map((row) => ({
|
||||
const failedImports: { row: CsvRow; reason: string }[] = [];
|
||||
|
||||
const observatories = await Promise.all(
|
||||
records.map(async (row) => {
|
||||
const creators = await prisma.user.findMany({
|
||||
where: {
|
||||
role: { in: ["SCIENTIST", "ADMIN"] },
|
||||
},
|
||||
});
|
||||
const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null;
|
||||
|
||||
if (!randomCreator) {
|
||||
failedImports.push({ row, reason: `RandomCreator: ${randomCreator}` });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name: row.Name,
|
||||
location: row.Location,
|
||||
latitude: row.Latitude,
|
||||
longitude: row.Longitude,
|
||||
dateEstablished: row.DateEstablished ? parseInt(row.DateEstablished, 10) : null,
|
||||
functional: stringToBool(row.Functional),
|
||||
seismicSensorOnline: row.SeismicSensorOnline ? stringToBool(row.SeismicSensorOnline) : true, // default true per schema
|
||||
// todo add random selection of creatorId
|
||||
creatorId: null,
|
||||
}));
|
||||
latitude: parseFloat(row.Latitude),
|
||||
longitude: parseFloat(row.Longitude),
|
||||
dateEstablished: new Date(row.DateEstablished),
|
||||
isFunctional: stringToBool(row.Functional),
|
||||
seismicSensorOnline: stringToBool(row.SeismicSensorOnline),
|
||||
creatorId: randomCreator.id,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const validObservatories = observatories.filter(
|
||||
(observatory): observatory is NonNullable<typeof observatory> => observatory !== null
|
||||
);
|
||||
|
||||
// 4. Bulk insert
|
||||
await prisma.observatory.createMany({
|
||||
data: observatories,
|
||||
data: validObservatories,
|
||||
});
|
||||
|
||||
if (failedImports.length > 0) {
|
||||
console.warn("Failed imports:", failedImports);
|
||||
await fs.writeFile(
|
||||
path.resolve(process.cwd(), "failed_imports_observatories.json"),
|
||||
JSON.stringify(failedImports, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: observatories.length,
|
||||
count: validObservatories.length,
|
||||
failedCount: failedImports.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,8 @@ import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
import { prisma } from "@utils/prisma";
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/requests.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type RequestType = "NEW_USER" | "CHANGE_LEVEL" | "DELETE";
|
||||
type RequestOutcome = "FULFILLED" | "REJECTED" | "IN_PROGRESS" | "CANCELLED" | "OTHER";
|
||||
@ -56,7 +54,5 @@ export async function POST() {
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,22 +2,18 @@ import { parse } from "csv-parse/sync";
|
||||
import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
import { prisma } from "@utils/prisma";
|
||||
import { getRandomNumber } from "@utils/maths";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
// Path to CSV file
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/scientists.csv");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CsvRow = {
|
||||
Name: string;
|
||||
Level?: string;
|
||||
UserId: string;
|
||||
SuperiorId?: string;
|
||||
Level: string;
|
||||
SuperiorName: string;
|
||||
};
|
||||
|
||||
function normalizeLevel(level: string | undefined): string {
|
||||
// Only allow JUNIOR, SENIOR; default JUNIOR
|
||||
if (!level || !level.trim()) return "JUNIOR";
|
||||
const lv = level.trim().toUpperCase();
|
||||
return ["JUNIOR", "SENIOR"].includes(lv) ? lv : "JUNIOR";
|
||||
@ -25,35 +21,102 @@ function normalizeLevel(level: string | undefined): string {
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read the CSV file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse the CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Transform each record for Prisma
|
||||
// todo add senior scientists first
|
||||
const scientists = records.map((row) => ({
|
||||
name: row.Name,
|
||||
level: normalizeLevel(row.Level),
|
||||
userId: parseInt(row.UserId, 10),
|
||||
// todo get superior id by name from db
|
||||
superiorId: row.SuperiorId && row.SuperiorId.trim() !== "" ? parseInt(row.SuperiorId, 10) : null,
|
||||
}));
|
||||
const failedImports: { row: CsvRow; reason: string }[] = [];
|
||||
|
||||
// 4. Bulk create scientists in database
|
||||
await prisma.scientist.createMany({
|
||||
data: scientists,
|
||||
// Fetch all users to match names
|
||||
const users = await prisma.user.findMany({
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, count: scientists.length });
|
||||
// Process seniors first to ensure superiors exist for juniors
|
||||
const seniorScientists = records.filter((row) => normalizeLevel(row.Level) === "SENIOR");
|
||||
const juniorScientists = records.filter((row) => normalizeLevel(row.Level) === "JUNIOR");
|
||||
const seniorScientistNames = new Map<string, number>(); // Map name to ID
|
||||
|
||||
const seniors = await Promise.all(
|
||||
seniorScientists.map(async (row) => {
|
||||
const user = users.find((u) => u.name === row.Name);
|
||||
if (!user) {
|
||||
failedImports.push({ row, reason: `User not found: ${row.Name}` });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name: row.Name,
|
||||
level: normalizeLevel(row.Level),
|
||||
userId: user.id,
|
||||
superiorId: null, // Seniors have no superior
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const validSeniors = seniors.filter((senior): senior is NonNullable<typeof senior> => senior !== null);
|
||||
|
||||
// Create senior scientists and store their IDs
|
||||
for (const senior of validSeniors) {
|
||||
try {
|
||||
const scientist = await prisma.scientist.create({ data: senior });
|
||||
seniorScientistNames.set(senior.name, scientist.id);
|
||||
} catch (error: any) {
|
||||
failedImports.push({
|
||||
row: { Name: senior.name, Level: senior.level, SuperiorName: "None" },
|
||||
reason: `Failed to create senior scientist: ${error.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Process junior scientists
|
||||
const juniors = await Promise.all(
|
||||
juniorScientists.map(async (row) => {
|
||||
const user = users.find((u) => u.name === row.Name);
|
||||
if (!user) {
|
||||
failedImports.push({ row, reason: `User not found: ${row.Name}` });
|
||||
return null;
|
||||
}
|
||||
|
||||
let superiorId: number | null = null;
|
||||
if (row.SuperiorName && row.SuperiorName.trim() !== "None") {
|
||||
superiorId = seniorScientistNames.get(row.SuperiorName.trim()) || null;
|
||||
if (!superiorId) {
|
||||
failedImports.push({ row, reason: `Superior not found: ${row.SuperiorName}` });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: row.Name,
|
||||
level: normalizeLevel(row.Level),
|
||||
userId: user.id,
|
||||
superiorId,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const validJuniors = juniors.filter((junior): junior is NonNullable<typeof junior> => junior !== null);
|
||||
|
||||
// Bulk create junior scientists
|
||||
await prisma.scientist.createMany({
|
||||
data: validJuniors,
|
||||
});
|
||||
|
||||
if (failedImports.length > 0) {
|
||||
console.warn("Failed imports:", failedImports);
|
||||
await fs.writeFile(path.resolve(process.cwd(), "failed_imports_scientists.json"), JSON.stringify(failedImports, null, 2));
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: validSeniors.length + validJuniors.length,
|
||||
failedCount: failedImports.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,21 +3,23 @@ import fs from "fs/promises";
|
||||
import { NextResponse } from "next/server";
|
||||
import path from "path";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { prisma } from "@utils/prisma";
|
||||
|
||||
// Path to users.csv - adjust as needed
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/users.csv");
|
||||
const prisma = new PrismaClient();
|
||||
const csvFilePath = path.resolve(process.cwd(), "public/Users.csv");
|
||||
|
||||
type CsvRow = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
Name: string;
|
||||
Email: string;
|
||||
PasswordHash: string;
|
||||
Role?: string;
|
||||
Role: string;
|
||||
Scientist: string;
|
||||
PurchasedArtefacts: string;
|
||||
Requests: string;
|
||||
};
|
||||
|
||||
function normalizeRole(role: string | undefined): string {
|
||||
// Only allow ADMIN, SCIENTIST, GUEST; default GUEST
|
||||
if (!role || !role.trim()) return "GUEST";
|
||||
const r = role.trim().toUpperCase();
|
||||
return ["ADMIN", "SCIENTIST", "GUEST"].includes(r) ? r : "GUEST";
|
||||
@ -25,33 +27,48 @@ function normalizeRole(role: string | undefined): string {
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
// 1. Read the CSV file
|
||||
const fileContent = await fs.readFile(csvFilePath, "utf8");
|
||||
|
||||
// 2. Parse the CSV
|
||||
const records: CsvRow[] = parse(fileContent, {
|
||||
columns: true,
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
|
||||
// 3. Transform each CSV row to User model format
|
||||
const users = records.map((row) => ({
|
||||
const failedImports: { row: CsvRow; reason: string }[] = [];
|
||||
|
||||
const users = await Promise.all(
|
||||
records.map(async (row) => {
|
||||
try {
|
||||
return {
|
||||
name: row.Name,
|
||||
email: row.Email,
|
||||
passwordHash: row.PasswordHash,
|
||||
role: normalizeRole(row.Role),
|
||||
}));
|
||||
};
|
||||
} catch (error: any) {
|
||||
failedImports.push({ row, reason: error.message });
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validUsers = users.filter((user): user is NonNullable<typeof user> => user !== null);
|
||||
|
||||
// 4. Bulk create users in database
|
||||
await prisma.user.createMany({
|
||||
data: users,
|
||||
data: validUsers,
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, count: users.length });
|
||||
if (failedImports.length > 0) {
|
||||
console.warn("Failed imports:", failedImports);
|
||||
await fs.writeFile(path.resolve(process.cwd(), "failed_imports_users.json"), JSON.stringify(failedImports, null, 2));
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
count: validUsers.length,
|
||||
failedCount: failedImports.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,15 +2,11 @@ import bcryptjs from "bcryptjs";
|
||||
import { SignJWT } from "jose";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { env } from "@utils/env";
|
||||
|
||||
import { prisma } from "@utils/prisma";
|
||||
import { findUserByEmail, readUserCsv, User } from "../functions/csvReadWrite";
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
if (usingPrisma) prisma = new PrismaClient();
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password } = await req.json(); // Parse incoming JSON data
|
||||
@ -67,7 +63,5 @@ export async function POST(req: Request) {
|
||||
} catch (error) {
|
||||
console.error("Error in signup endpoint:", error);
|
||||
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
|
||||
} finally {
|
||||
if (usingPrisma) await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from '@prismaclient';
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
if (usingPrisma) prisma = new PrismaClient();
|
||||
import { prisma } from "@utils/prisma";
|
||||
|
||||
// todo remove if (usingPrisma) code
|
||||
// todo add specification of date range in request
|
||||
@ -39,8 +35,7 @@ export async function GET(request: Request) {
|
||||
];
|
||||
|
||||
// todo get earthquakes associated with observatories
|
||||
let observatories;
|
||||
if (usingPrisma) observatories = await prisma.observatory.findMany();
|
||||
const observatories = await prisma.observatory.findMany();
|
||||
|
||||
if (observatories) {
|
||||
return NextResponse.json({ message: "Got observatories successfully", observatories }, { status: 200 });
|
||||
@ -51,7 +46,5 @@ export async function GET(request: Request) {
|
||||
} catch (error) {
|
||||
console.error("Error in observatories endpoint:", error);
|
||||
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
|
||||
} finally {
|
||||
if (usingPrisma) await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,17 +2,11 @@ import bcryptjs from "bcryptjs";
|
||||
import { SignJWT } from "jose";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
import { env } from "@utils/env";
|
||||
import { prisma } from "@utils/prisma";
|
||||
|
||||
import { findUserByEmail, passwordStrengthCheck, readUserCsv, User, writeUserCsv } from "../functions/csvReadWrite";
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
if (usingPrisma) prisma = new PrismaClient();
|
||||
|
||||
// todo remove if (usingPrisma) code
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password, name } = await req.json(); // Parse incoming JSON data
|
||||
@ -26,17 +20,11 @@ export async function POST(req: Request) {
|
||||
console.log("Email:", email); // ! remove
|
||||
console.log("Password:", password); // ! remove
|
||||
|
||||
let foundUser;
|
||||
|
||||
if (usingPrisma) {
|
||||
foundUser = await prisma.user.findUnique({
|
||||
const foundUser = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: email, // use the email to uniquely identify the user
|
||||
},
|
||||
});
|
||||
} else {
|
||||
foundUser = findUserByEmail(userData, email);
|
||||
}
|
||||
|
||||
if (foundUser) {
|
||||
return NextResponse.json({ message: "Sorry, this email is already in use" }, { status: 409 });
|
||||
@ -61,20 +49,15 @@ export async function POST(req: Request) {
|
||||
} else {
|
||||
try {
|
||||
const passwordHash = await bcryptjs.hash(password, 10);
|
||||
let user;
|
||||
if (usingPrisma) {
|
||||
// todo add sending back user
|
||||
user = await prisma.user.create({
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
name,
|
||||
email,
|
||||
passwordHash,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
user = { name, email, password: passwordHash, accessLevel };
|
||||
userData.push(user);
|
||||
}
|
||||
|
||||
await writeUserCsv(userData);
|
||||
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
|
||||
@ -1,108 +1,42 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from "next/headers";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { PrismaClient } from '@prismaclient';
|
||||
import { env } from '@utils/env';
|
||||
import { verifyJwt } from '@utils/verifyJwt';
|
||||
|
||||
const usingPrisma = false;
|
||||
let prisma: PrismaClient;
|
||||
if (usingPrisma) prisma = new PrismaClient();
|
||||
|
||||
// todo remove if (usingPrisma) code
|
||||
// todo add specification of date range in request
|
||||
|
||||
// Artefact type
|
||||
interface Artefact {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
location: string;
|
||||
earthquakeId: string;
|
||||
isRequired: boolean;
|
||||
isSold: boolean;
|
||||
isCollected: boolean;
|
||||
dateAdded: string;
|
||||
}
|
||||
import { ExtendedArtefact } from "@appTypes/ApiTypes";
|
||||
import { JWTPayload } from "@appTypes/JWT";
|
||||
import { apiAuthMiddleware } from "@utils/apiAuthMiddleware";
|
||||
import { env } from "@utils/env";
|
||||
import { prisma } from "@utils/prisma";
|
||||
import { verifyJwt } from "@utils/verifyJwt";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
// todo fix, moron the token will be in the cookie header
|
||||
const json = await req.json(); // Parse incoming JSON data
|
||||
const { token } = json.body;
|
||||
if (!token) return NextResponse.json({ message: "Unauthorised" }, { status: 401 });
|
||||
await verifyJwt({ token, secret: env.JWT_SECRET_KEY });
|
||||
const authResult = await apiAuthMiddleware();
|
||||
if ("user" in authResult === false) return authResult; // Handle error response
|
||||
|
||||
const warehouseArtefacts: Artefact[] = [
|
||||
{
|
||||
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",
|
||||
},
|
||||
];
|
||||
const { user } = authResult;
|
||||
|
||||
let artefacts;
|
||||
if (usingPrisma) artefacts = await prisma.artefact.findMany();
|
||||
if (user.role !== "SCIENTIST" && user.role !== "ADMIN") {
|
||||
return NextResponse.json({ message: "Not authorised" }, { status: 401 });
|
||||
}
|
||||
|
||||
const artefacts = await prisma.artefact.findMany({
|
||||
include: {
|
||||
earthquake: true,
|
||||
},
|
||||
});
|
||||
|
||||
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 {
|
||||
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) {
|
||||
console.error("Error in artefacts endpoint:", error);
|
||||
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
|
||||
} finally {
|
||||
if (usingPrisma) await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
|
||||
const OurMission = () => {
|
||||
function OurMission() {
|
||||
return (
|
||||
<div
|
||||
className="relative bg-fixed bg-cover bg-center text-white "
|
||||
style={{ backgroundImage: "url('destruction.jpg')", height: 845, overflow: "hidden" }}
|
||||
className="relative h-full bg-fixed bg-cover bg-center text-white "
|
||||
style={{ backgroundImage: "url('destruction.jpg')", overflow: "hidden" }}
|
||||
>
|
||||
{/* Overlay for Readability */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-50"></div>
|
||||
@ -55,5 +55,5 @@ const OurMission = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
export default OurMission;
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
import Image from 'next/image';
|
||||
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||
import Image from "next/image";
|
||||
import { Dispatch, SetStateAction, useCallback, useState } from "react";
|
||||
|
||||
import Artefact from '@appTypes/Artefact';
|
||||
import { Currency } from '@appTypes/StoreModel';
|
||||
import { useStoreState } from '@hooks/store';
|
||||
import { ExtendedArtefact } from "@appTypes/ApiTypes";
|
||||
import { Currency } from "@appTypes/StoreModel";
|
||||
import { useStoreState } from "@hooks/store";
|
||||
|
||||
// Artefacts Data
|
||||
const artefacts: Artefact[] = [
|
||||
const artefacts: ExtendedArtefact[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Golden Scarab",
|
||||
@ -215,7 +214,7 @@ export default function Shop() {
|
||||
if (currentPage > 1) setCurrentPage((prev) => prev - 1);
|
||||
};
|
||||
|
||||
function ArtefactCard({ artefact }: { artefact: Artefact }) {
|
||||
function ArtefactCard({ artefact }: { artefact: ExtendedArtefact }) {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
||||
@ -234,7 +233,7 @@ export default function Shop() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Modal({ artefact }: { artefact: Artefact }) {
|
||||
function Modal({ artefact }: { artefact: ExtendedArtefact }) {
|
||||
if (!artefact) return null;
|
||||
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) setSelectedArtefact(null);
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
"use client";
|
||||
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from 'react-icons/fa6';
|
||||
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from 'react-icons/io5';
|
||||
import { Dispatch, SetStateAction, useMemo, useState } from "react";
|
||||
import { FaTimes } from "react-icons/fa";
|
||||
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
|
||||
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5";
|
||||
|
||||
import { ExtendedArtefact } from "@appTypes/ApiTypes";
|
||||
|
||||
// import { Artefact } from "@appTypes/Prisma";
|
||||
|
||||
import type { Artefact } from "@prismaclient";
|
||||
|
||||
interface WarehouseArtefact extends Artefact {
|
||||
location: string;
|
||||
}
|
||||
|
||||
// Warehouse Artefacts Data
|
||||
const warehouseArtefacts: WarehouseArtefact[] = [
|
||||
const extendedArtefacts: ExtendedArtefact[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Solidified Lava Chunk",
|
||||
@ -705,7 +703,7 @@ export default function Warehouse() {
|
||||
// Apply filters with loading state
|
||||
const filteredArtefacts = useMemo(() => {
|
||||
setIsFiltering(true);
|
||||
const result = applyFilters(warehouseArtefacts, filters);
|
||||
const result = applyFilters(extendedArtefacts, filters);
|
||||
setIsFiltering(false);
|
||||
return result;
|
||||
}, [filters]);
|
||||
@ -713,10 +711,10 @@ export default function Warehouse() {
|
||||
const currentArtefacts = filteredArtefacts.slice(indexOfFirstArtefact, indexOfLastArtefact);
|
||||
|
||||
// Overview stats
|
||||
const totalArtefacts = warehouseArtefacts.length;
|
||||
const totalArtefacts = extendedArtefacts.length;
|
||||
const today = new Date();
|
||||
const artefactsAddedToday = warehouseArtefacts.filter((a) => a.createdAt.toDateString() === today.toDateString()).length;
|
||||
const artefactsSoldToday = warehouseArtefacts.filter(
|
||||
const artefactsAddedToday = extendedArtefacts.filter((a) => a.createdAt.toDateString() === today.toDateString()).length;
|
||||
const artefactsSoldToday = extendedArtefacts.filter(
|
||||
(a) => a.isSold && a.createdAt.toDateString() === today.toDateString()
|
||||
).length;
|
||||
|
||||
@ -809,8 +807,11 @@ export default function Warehouse() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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)} />}
|
||||
{showBulkLogModal && <BulkLogModal onClose={() => setShowBulkLogModal(false)} />}
|
||||
{editArtefact && <EditModal artefact={editArtefact} onClose={() => setEditArtefact(null)} />}
|
||||
|
||||
@ -10,6 +10,7 @@ interface AuthModalProps {
|
||||
}
|
||||
|
||||
export default function AuthModal({ isOpen, onClose }: AuthModalProps) {
|
||||
// todo add login successful message
|
||||
const [isLogin, setIsLogin] = useState<boolean>(true);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
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 };
|
||||
@ -1,5 +1,5 @@
|
||||
import { Action } from "easy-peasy";
|
||||
// import type { User } from "@prisma/client";
|
||||
// import type { User } from "@prismaclient";
|
||||
import { User } from "@appTypes/Prisma";
|
||||
|
||||
type Currency = "GBP" | "USD" | "EUR";
|
||||
|
||||
32
src/utils/apiAuthMiddleware.ts
Normal file
32
src/utils/apiAuthMiddleware.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { verifyJwt } from "@utils/verifyJwt";
|
||||
import type { JWTPayload } from "@/types/JWT";
|
||||
import { env } from "@utils/env";
|
||||
|
||||
import { prisma } from "@utils/prisma";
|
||||
|
||||
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 };
|
||||
}
|
||||
5
src/utils/maths.ts
Normal file
5
src/utils/maths.ts
Normal file
@ -0,0 +1,5 @@
|
||||
function getRandomNumber(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export { getRandomNumber };
|
||||
7
src/utils/parsingUtils.ts
Normal file
7
src/utils/parsingUtils.ts
Normal file
@ -0,0 +1,7 @@
|
||||
function stringToBool(val: string | undefined, defaultValue: boolean = false): boolean {
|
||||
if (!val) return defaultValue;
|
||||
const normalized = val.trim().toLowerCase();
|
||||
return normalized === "true" || normalized === "yes";
|
||||
}
|
||||
|
||||
export { stringToBool };
|
||||
9
src/utils/prisma.ts
Normal file
9
src/utils/prisma.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { PrismaClient } from "@prismaclient";
|
||||
|
||||
declare global {
|
||||
var prisma: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export { prisma };
|
||||
Loading…
x
Reference in New Issue
Block a user