Compare commits
2 Commits
885e694ad2
...
b40d0aedb4
| Author | SHA1 | Date | |
|---|---|---|---|
| b40d0aedb4 | |||
| cb6dd05071 |
6
importersFixed.md
Normal file
6
importersFixed.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
- [ ] Import users
|
||||||
|
- [x] Import artefacts
|
||||||
|
- [ ] Import earthquakes
|
||||||
|
- [ ] Import observatoies
|
||||||
|
- [ ] Import requests
|
||||||
|
- [ ] Import scientists
|
||||||
@ -91,6 +91,7 @@ model Artefact {
|
|||||||
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
||||||
warehouseArea String
|
warehouseArea String
|
||||||
description String
|
description String
|
||||||
|
imageName String
|
||||||
earthquakeId Int
|
earthquakeId Int
|
||||||
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
||||||
creatorId Int?
|
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
|
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
|
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
|
Strata Core,Soil,LoggingArea,Soil core with visible stratification showing evidence of liquefaction.,ET-6.9-Brazil-00046,30,no,no,StrataCore.PNG
|
||||||
|
|||||||
|
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 undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 !== undefined);
|
||||||
|
|
||||||
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const OurMission = () => {
|
function OurMission() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative bg-fixed bg-cover bg-center text-white "
|
className="relative h-full bg-fixed bg-cover bg-center text-white "
|
||||||
style={{ backgroundImage: "url('destruction.jpg')", height: 845, overflow: "hidden" }}
|
style={{ backgroundImage: "url('destruction.jpg')", overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
{/* Overlay for Readability */}
|
{/* Overlay for Readability */}
|
||||||
<div className="absolute inset-0 bg-black bg-opacity-50"></div>
|
<div className="absolute inset-0 bg-black bg-opacity-50"></div>
|
||||||
@ -55,5 +55,5 @@ const OurMission = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
export default OurMission;
|
export default OurMission;
|
||||||
|
|||||||
@ -214,7 +214,7 @@ export default function Shop() {
|
|||||||
if (currentPage > 1) setCurrentPage((prev) => prev - 1);
|
if (currentPage > 1) setCurrentPage((prev) => prev - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
function ArtefactCard({ artefact }: { artefact: Artefact }) {
|
function ArtefactCard({ artefact }: { artefact: ExtendedArtefact }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
className="flex flex-col bg-white shadow-md rounded-md overflow-hidden cursor-pointer hover:scale-105 transition-transform"
|
||||||
@ -233,7 +233,7 @@ export default function Shop() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function Modal({ artefact }: { artefact: Artefact }) {
|
function Modal({ artefact }: { artefact: ExtendedArtefact }) {
|
||||||
if (!artefact) return null;
|
if (!artefact) return null;
|
||||||
const handleOverlayClick = (e: React.MouseEvent) => {
|
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||||
if (e.target === e.currentTarget) setSelectedArtefact(null);
|
if (e.target === e.currentTarget) setSelectedArtefact(null);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Action } from "easy-peasy";
|
import { Action } from "easy-peasy";
|
||||||
// import type { User } from "@prisma/client";
|
// import type { User } from "@prismaclient";
|
||||||
import { User } from "@appTypes/Prisma";
|
import { User } from "@appTypes/Prisma";
|
||||||
|
|
||||||
type Currency = "GBP" | "USD" | "EUR";
|
type Currency = "GBP" | "USD" | "EUR";
|
||||||
|
|||||||
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 };
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prismaclient";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var prisma: PrismaClient | undefined;
|
var prisma: PrismaClient | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prisma = process.env.NODE_ENV === "production" ? new PrismaClient() : global.prisma ?? (global.prisma = new PrismaClient());
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
export { prisma };
|
export { prisma };
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user