Compare commits

..

No commits in common. "7311e379e8fdc62df372d8bf9e1d4f93b6ac7d76" and "87d2cfbc25ef0c8214ed2394cc67dd5f05f79d77" have entirely different histories.

8 changed files with 87 additions and 223 deletions

View File

@ -1,6 +1,6 @@
- [x] Import users - [x] Import users
- [x] Import artefacts - [x] Import artefacts
- [x] Import earthquakes - [x] Import earthquakes
- [x] Import observatories - [ ] Import observatoies
- [ ] Import requests - [ ] Import requests
- [x] Import scientists - [ ] Import scientists

View File

@ -1,6 +0,0 @@
1. Import users
2. Import scientists
3. Import observatories
4. Import earthquakes
5. Import artefacts
6. Import requests

View File

@ -73,9 +73,9 @@ model Observatory {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
name String name String
location String location String
longitude Float longitude String
latitude Float latitude String
dateEstablished DateTime dateEstablished Int?
isFunctional Boolean isFunctional Boolean
seismicSensorOnline Boolean @default(true) seismicSensorOnline Boolean @default(true)
creatorId Int? creatorId Int?

View File

@ -1,26 +1,25 @@
Name,Location,Latitude,Longitude,DateEstablished,Functional,SeismicSensorOnline Pacific Apex Seismic Center,"Aleutian Trench, Alaska, USA",53.0000,-168.0000,1973-06-15,Yes
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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,No
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,No
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,No
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,No
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,No
Baltic Continental Drift Center,"Southeastern Sweden, Baltic Shield zone",56.0000,15.0000,1987-12-12,Yes,Yes
1 Name Pacific Apex Seismic Center Location Aleutian Trench, Alaska, USA Latitude 53.0000 Longitude -168.0000 DateEstablished 1973-06-15 Functional Yes SeismicSensorOnline
2 Pacific Apex Seismic Center Cascadia Quake Research Institute Aleutian Trench, Alaska, USA Oregon Coast, USA 53.0000 44.5000 -168.0000 -124.0000 1973-06-15 1985-03-22 Yes Yes
3 Cascadia Quake Research Institute Andes Fault Survey Observatory Oregon Coast, USA Nazca-South American Plate, Santiago, Chile 44.5000 -33.4500 -124.0000 -70.6667 1985-03-22 1992-10-10 Yes Yes
4 Andes Fault Survey Observatory Kermadec Deep Motion Lab Nazca-South American Plate, Santiago, Chile Kermadec Trench, northeast of New Zealand -33.4500 -29.5000 -70.6667 -177.0000 1992-10-10 2001-05-14 Yes Yes
5 Kermadec Deep Motion Lab Japan Trench Monitoring Station Kermadec Trench, northeast of New Zealand Off the coast of Sendai, Japan -29.5000 38.5000 -177.0000 143.0000 2001-05-14 1968-09-01 Yes Yes
6 Japan Trench Monitoring Station Himalayan Rift Observatory Off the coast of Sendai, Japan Nepal-Tibet border, near Mount Everest 38.5000 28.5000 143.0000 86.5000 1968-09-01 1998-12-08 Yes Yes
7 Himalayan Rift Observatory East African Rift Monitoring Center Nepal-Tibet border, near Mount Everest Rift Valley, near Nairobi, Kenya 28.5000 -1.2921 86.5000 36.8219 1998-12-08 2010-03-30 Yes Yes
8 East African Rift Monitoring Center Reykjavik Seismic Monitoring Hub Rift Valley, near Nairobi, Kenya Mid-Atlantic Ridge, Reykjavik, Iceland -1.2921 64.1355 36.8219 -21.8954 2010-03-30 1957-11-17 Yes Yes
9 Reykjavik Seismic Monitoring Hub Azores Tectonic Research Base Mid-Atlantic Ridge, Reykjavik, Iceland Azores Archipelago, Portugal 64.1355 37.7412 -21.8954 -25.6756 1957-11-17 1982-07-04 Yes Yes
10 Azores Tectonic Research Base Sumatra Subduction Observatory Azores Archipelago, Portugal West Coast of Indonesia, Aceh Province 37.7412 4.6951 -25.6756 95.5539 1982-07-04 2005-02-16 Yes Yes
11 Sumatra Subduction Observatory Tonga Trench Seismographic Unit West Coast of Indonesia, Aceh Province Off the coast of Tonga 4.6951 -20.0000 95.5539 -175.0000 2005-02-16 1994-08-21 Yes Yes
12 Tonga Trench Seismographic Unit San Andreas Fault Research Center Off the coast of Tonga San Francisco Bay Area, California, USA -20.0000 37.7749 -175.0000 -122.4194 1994-08-21 1929-10-07 Yes Yes
13 San Andreas Fault Research Center Kamchatka Seismic Laboratory San Francisco Bay Area, California, USA Kamchatka Peninsula, Russia 37.7749 56.0000 -122.4194 160.0000 1929-10-07 1978-01-12 Yes Yes
14 Kamchatka Seismic Laboratory Hawaii Island Seismic Research Station Kamchatka Peninsula, Russia Hawaii Big Island, USA 56.0000 19.8968 160.0000 -155.5828 1978-01-12 1989-05-06 Yes Yes
15 Hawaii Island Seismic Research Station Cascadia Ridge Offshore Observatory Hawaii Big Island, USA Offshore of Vancouver Island, British Columbia, Canada 19.8968 48.4284 -155.5828 -123.3656 1989-05-06 2003-11-18 Yes Yes
16 Cascadia Ridge Offshore Observatory Zagros Fault Zone Research Center Offshore of Vancouver Island, British Columbia, Canada Western Iran, near Kermanshah 48.4284 34.0000 -123.3656 46.0000 2003-11-18 1990-06-28 Yes Yes
17 Zagros Fault Zone Research Center Manila Trench Seismic Observatory Western Iran, near Kermanshah South China Sea, west of Luzon, Philippines 34.0000 15.0000 46.0000 120.0000 1990-06-28 1996-04-09 Yes Yes
18 Manila Trench Seismic Observatory Caribbean Subduction Monitoring Station South China Sea, west of Luzon, Philippines Lesser Antilles Subduction Zone, Martinique 15.0000 14.6000 120.0000 -61.0000 1996-04-09 2008-09-23 Yes Yes
19 Caribbean Subduction Monitoring Station Gorda Plate Analysis Hub Lesser Antilles Subduction Zone, Martinique Mendocino Triple Junction, California, USA 14.6000 40.0000 -61.0000 -124.0000 2008-09-23 1952-12-01 Yes Yes
20 Gorda Plate Analysis Hub Red Sea Rift Research Base Mendocino Triple Junction, California, USA Southern Red Sea, Eritrea 40.0000 15.0000 -124.0000 42.0000 1952-12-01 2015-07-15 Yes Yes
21 Red Sea Rift Research Base Sumatra Coastal Reserve Observatory Southern Red Sea, Eritrea West Coast of Sumatra, Indonesia 15.0000 -2.0000 42.0000 100.5000 2015-07-15 1980-03-11 Yes No Yes
22 Sumatra Coastal Reserve Observatory Antarctic Polar Seismology Station West Coast of Sumatra, Indonesia Rift vicinity near Ross Ice Shelf, Antarctica -2.0000 -78.3000 100.5000 -166.2500 1980-03-11 1974-06-01 Yes No Yes
23 Antarctic Polar Seismology Station Yucatan Seismic Monitoring Site Rift vicinity near Ross Ice Shelf, Antarctica Cocos Plate near Yucatan Peninsula, Mexico -78.3000 20.7000 -166.2500 -90.8000 1974-06-01 1965-09-23 Yes No Yes
24 Yucatan Seismic Monitoring Site Makran Subduction Fault Observatory Cocos Plate near Yucatan Peninsula, Mexico Coastal Pakistan and Iran junction 20.7000 25.5000 -90.8000 62.0000 1965-09-23 1990-08-29 Yes No Yes
25 Makran Subduction Fault Observatory Baltic Continental Drift Center Coastal Pakistan and Iran junction Southeastern Sweden, Baltic Shield zone 25.5000 56.0000 62.0000 15.0000 1990-08-29 1987-12-12 Yes No Yes
Baltic Continental Drift Center Southeastern Sweden, Baltic Shield zone 56.0000 15.0000 1987-12-12 Yes Yes

View File

@ -1,10 +1,10 @@
Name,Level,SuperiorName
Dr. Emily Neighbour Carter,Junior,Dr. Rajiv Menon Dr. Emily Neighbour Carter,Junior,Dr. Rajiv Menon
Dr. Rajiv Menon,Senior,None Dr. Rajiv Menon,Senior,None
Dr. Izzy Patterson,Senior,None Dr. Izzy Patterson,Senior,None
Dr. Hiroshi Takeda,Senior,None Dr. Hiroshi Takeda,Senior,None
Dr. Miriam Hassan,Senior,None Dr. Miriam Hassan,Senior,None
Dr. Alice Johnson,Senior,None Dr. Alice Johnson,Senior,None
Tim Howitz,Admin,None
Dr. Natalia Petrova,Junior,Dr. Izzy Patteron Dr. Natalia Petrova,Junior,Dr. Izzy Patteron
Dr. Li Cheng,Junior,Dr. Rajiv Menon Dr. Li Cheng,Junior,Dr. Rajiv Menon
Dr. Javier Ortega,Junior,Dr. Izzy Patterson Dr. Javier Ortega,Junior,Dr. Izzy Patterson

1 Name Dr. Emily Neighbour Carter Level Junior SuperiorName Dr. Rajiv Menon
Name Level SuperiorName
1 Dr. Emily Neighbour Carter Dr. Emily Neighbour Carter Junior Junior Dr. Rajiv Menon
2 Dr. Rajiv Menon Dr. Rajiv Menon Senior Senior None
3 Dr. Izzy Patterson Dr. Izzy Patterson Senior Senior None
4 Dr. Hiroshi Takeda Dr. Hiroshi Takeda Senior Senior None
5 Dr. Miriam Hassan Dr. Miriam Hassan Senior Senior None
6 Dr. Alice Johnson Dr. Alice Johnson Senior Senior None
7 Tim Howitz Admin None
8 Dr. Natalia Petrova Dr. Natalia Petrova Junior Junior Dr. Izzy Patteron
9 Dr. Li Cheng Dr. Li Cheng Junior Junior Dr. Rajiv Menon
10 Dr. Javier Ortega Dr. Javier Ortega Junior Junior Dr. Izzy Patterson

View File

@ -6,7 +6,6 @@ import { prisma } from "@utils/prisma";
import { getRandomNumber } from "@utils/maths"; import { getRandomNumber } from "@utils/maths";
const csvFilePath = path.resolve(process.cwd(), "public/earthquakes.csv"); const csvFilePath = path.resolve(process.cwd(), "public/earthquakes.csv");
const DISTANCE_THRESHOLD_KM = 500; // Max distance for observatory matching
type CsvRow = { type CsvRow = {
Date: string; Date: string;
@ -19,18 +18,6 @@ type CsvRow = {
Depth: string; 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() { export async function POST() {
try { try {
const fileContent = await fs.readFile(csvFilePath, "utf8"); const fileContent = await fs.readFile(csvFilePath, "utf8");
@ -41,15 +28,12 @@ export async function POST() {
const failedImports: { row: CsvRow; reason: string }[] = []; 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( const earthquakes = await Promise.all(
records.map(async (row) => { records.map(async (row) => {
const creators = await prisma.user.findMany({ const creators = await prisma.user.findMany({
where: { role: { in: ["SCIENTIST", "ADMIN"] } }, where: {
role: { in: ["SCIENTIST", "ADMIN"] },
},
}); });
const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null; const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null;
@ -58,43 +42,16 @@ export async function POST() {
return null; 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 { return {
date: new Date(row.Date), date: new Date(row.Date),
code: row.Code, code: row.Code,
magnitude: parseFloat(row.Magnitude), magnitude: parseFloat(row.Magnitude),
type: row.Type, type: row.Type,
latitude: eqLat, latitude: parseFloat(row.Latitude),
longitude: eqLon, longitude: parseFloat(row.Longitude),
location: row.Location, location: row.Location,
depth: row.Depth, depth: row.Depth,
creatorId: randomCreator.id, creatorId: randomCreator.id,
observatories: { connect: nearbyObservatories },
}; };
}) })
); );
@ -103,7 +60,6 @@ export async function POST() {
(earthquake): earthquake is NonNullable<typeof earthquake> => earthquake !== null (earthquake): earthquake is NonNullable<typeof earthquake> => earthquake !== null
); );
// Bulk insert earthquakes with observatory connections
await prisma.earthquake.createMany({ await prisma.earthquake.createMany({
data: validEarthquakes, data: validEarthquakes,
}); });

View File

@ -1,11 +1,11 @@
import { parse } from "csv-parse/sync"; import { parse } from "csv-parse/sync";
import { stringToBool } from "@utils/parsingUtils";
import fs from "fs/promises"; import fs from "fs/promises";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import path from "path"; import path from "path";
import { prisma } from "@utils/prisma";
import { getRandomNumber } from "@utils/maths";
import { prisma } from "@utils/prisma";
// CSV location (update filename as needed)
const csvFilePath = path.resolve(process.cwd(), "public/observatories.csv"); const csvFilePath = path.resolve(process.cwd(), "public/observatories.csv");
type CsvRow = { type CsvRow = {
@ -13,68 +13,49 @@ type CsvRow = {
Location: string; Location: string;
Latitude: string; Latitude: string;
Longitude: string; Longitude: string;
DateEstablished: string; DateEstablished?: string;
Functional: 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() { export async function POST() {
try { try {
// 1. Read file
const fileContent = await fs.readFile(csvFilePath, "utf8"); const fileContent = await fs.readFile(csvFilePath, "utf8");
// 2. Parse CSV
const records: CsvRow[] = parse(fileContent, { const records: CsvRow[] = parse(fileContent, {
columns: true, columns: true,
skip_empty_lines: true, skip_empty_lines: true,
}); });
const failedImports: { row: CsvRow; reason: string }[] = []; // 3. Map records to Prisma inputs
const observatories = records.map((row) => ({
const observatories = await Promise.all( name: row.Name,
records.map(async (row) => { location: row.Location,
const creators = await prisma.user.findMany({ latitude: row.Latitude,
where: { longitude: row.Longitude,
role: { in: ["SCIENTIST", "ADMIN"] }, dateEstablished: row.DateEstablished ? parseInt(row.DateEstablished, 10) : null,
}, functional: stringToBool(row.Functional),
}); seismicSensorOnline: row.SeismicSensorOnline ? stringToBool(row.SeismicSensorOnline) : true, // default true per schema
const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null; // todo add random selection of creatorId
creatorId: null,
if (!randomCreator) { }));
failedImports.push({ row, reason: `RandomCreator: ${randomCreator}` });
return null;
}
return {
name: row.Name,
location: row.Location,
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({ await prisma.observatory.createMany({
data: validObservatories, data: observatories,
}); });
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({ return NextResponse.json({
success: true, success: true,
count: validObservatories.length, count: observatories.length,
failedCount: failedImports.length,
}); });
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);

View File

@ -2,18 +2,21 @@ import { parse } from "csv-parse/sync";
import fs from "fs/promises"; import fs from "fs/promises";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import path from "path"; import path from "path";
import { prisma } from "@utils/prisma";
import { getRandomNumber } from "@utils/maths";
import { prisma } from "@utils/prisma";
// Path to CSV file
const csvFilePath = path.resolve(process.cwd(), "public/scientists.csv"); const csvFilePath = path.resolve(process.cwd(), "public/scientists.csv");
type CsvRow = { type CsvRow = {
Name: string; Name: string;
Level: string; Level?: string;
SuperiorName: string; UserId: string;
SuperiorId?: string;
}; };
function normalizeLevel(level: string | undefined): string { function normalizeLevel(level: string | undefined): string {
// Only allow JUNIOR, SENIOR; default JUNIOR
if (!level || !level.trim()) return "JUNIOR"; if (!level || !level.trim()) return "JUNIOR";
const lv = level.trim().toUpperCase(); const lv = level.trim().toUpperCase();
return ["JUNIOR", "SENIOR"].includes(lv) ? lv : "JUNIOR"; return ["JUNIOR", "SENIOR"].includes(lv) ? lv : "JUNIOR";
@ -21,100 +24,31 @@ function normalizeLevel(level: string | undefined): string {
export async function POST() { export async function POST() {
try { try {
// 1. Read the CSV file
const fileContent = await fs.readFile(csvFilePath, "utf8"); const fileContent = await fs.readFile(csvFilePath, "utf8");
// 2. Parse the CSV
const records: CsvRow[] = parse(fileContent, { const records: CsvRow[] = parse(fileContent, {
columns: true, columns: true,
skip_empty_lines: true, skip_empty_lines: true,
}); });
const failedImports: { row: CsvRow; reason: string }[] = []; // 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,
}));
// Fetch all users to match names // 4. Bulk create scientists in database
const users = await prisma.user.findMany({
select: { id: true, name: true },
});
// 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({ await prisma.scientist.createMany({
data: validJuniors, data: scientists,
}); });
if (failedImports.length > 0) { return NextResponse.json({ success: true, count: scientists.length });
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) { } catch (error: any) {
console.error(error); console.error(error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 }); return NextResponse.json({ success: false, error: error.message }, { status: 500 });