Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules/
dist/
dist-ssr/
*.local
.DS_Store
Binary file added backend/.DS_Store
Binary file not shown.
9 changes: 8 additions & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ Sjekk at tunnelen virker (i et annet vindu):
nc -z localhost 27017 && echo "Tunnel OK"
```

### 5. Start backenden

### 5. Hent plantebilder fra Wikimedia

```bash
npx tsx src/updatePlantImages.ts
```

### 6. Start backenden

```bash
npm run dev
Expand Down
79 changes: 39 additions & 40 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import type { WithId } from "mongodb";
import { db } from "./db.js";
// The shape of a plant document in MongoDB (only the fields we read)
interface PlantDoc {
commonName: string;
image?: { regular?: string };
scientificName?: string[];
description?: string;
watering?: string;
sunlight?: string[];
careLevel?: string;
commonName: string;
imageURL?: string;
scientificName?: string[];
description?: string;
watering?: string;
sunlight?: string[];
careLevel?: string;
}

// The shape of a Plant in the GraphQL API
Expand Down Expand Up @@ -53,42 +53,41 @@ const typeDefs = `
`;

const toPlant = (doc: WithId<PlantDoc>): Plant => ({
id: doc._id.toString(),
name: doc.commonName,
imageURL: doc.image?.regular ?? null,
scientificName: doc.scientificName,
description: doc.description,
watering: doc.watering,
sunlight: doc.sunlight,
careLevel: doc.careLevel,
id: doc._id.toString(),
name: doc.commonName,
imageURL: doc.imageURL ?? null,
scientificName: doc.scientificName,
description: doc.description,
watering: doc.watering,
sunlight: doc.sunlight,
careLevel: doc.careLevel,
});

const resolvers = {
Query: {
getPlants: async (
_parent: unknown,
args: GetPlantsArgs,
): Promise<Plant[]> => {
const docs = await plants
.find(
{},
{
projection: {
commonName: 1,
image: 1,
scientificName: 1,
description: 1,
watering: 1,
sunlight: 1,
careLevel: 1,
},
},
)
.sort({ perenualId: 1 })
.skip(args.offset)
.limit(args.limit)
.toArray();
return docs.map(toPlant);
Query: {
getPlants: async (_parent: unknown, args: GetPlantsArgs): Promise<Plant[]> => {
const docs = await plants
.aggregate<WithId<PlantDoc>>([
{
$sample: {
size: args.limit
}
},
{
$project: {
commonName: 1,
imageURL: 1,
scientificName: 1,
description: 1,
watering: 1,
sunlight: 1,
careLevel: 1
}
}
])
.toArray();
return docs.map(toPlant);
},
},
},
};
Expand Down
67 changes: 67 additions & 0 deletions backend/src/updatePlantImages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { db } from "./db.js";
import { getWikimediaImage } from "./wikimedia.js";

interface PlantDoc {
commonName: string;
scientificName?: string[];
imageURL?: string | null;
}

const plants = db.collection<PlantDoc>("plants");

async function updatePlantImages() {
const docs = await plants.find({}).toArray();

console.log(`Fant ${docs.length} planter`);

for (const doc of docs) {
const scientificName = doc.scientificName?.[0];

if (!scientificName) {
console.log(`Mangler scientificName: ${doc.commonName}`);
continue;
}

try {
console.log(`Søker etter: ${scientificName}`);

const imageURL = await getWikimediaImage(scientificName);

if (!imageURL) {
console.log(`Fant ikke bilde for ${scientificName}`);

await plants.updateOne(
{ _id: doc._id },
{
$set: {
imageURL: null,
},
}
);

continue;
}

await plants.updateOne(
{ _id: doc._id },
{
$set: {
imageURL,
},
}
);

console.log(`✓ Oppdaterte ${doc.commonName}`);
} catch (error) {
console.error(
`Feil ved henting av bilde for ${scientificName}:`,
error
);
}
}

console.log("Ferdig!");
process.exit(0);
}

updatePlantImages();
51 changes: 51 additions & 0 deletions backend/src/wikimedia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export async function getWikimediaImage(scientificName: string) {
const params = new URLSearchParams({
action: "query",
generator: "search",
gsrsearch: scientificName,
gsrnamespace: "6",
gsrlimit: "10",
prop: "imageinfo",
iiprop: "url|mime|extmetadata",
format: "json",
origin: "*",
});

const response = await fetch(
`https://commons.wikimedia.org/w/api.php?${params}`
);

if (!response.ok) {
throw new Error(
`Wikimedia svarte med status ${response.status}`
);
}

const data = await response.json();

const pages = data.query?.pages;

if (!pages) {
return null;
}

const results = Object.values(pages) as any[];

const allowedMimeTypes = [
"image/jpeg",
"image/png",
"image/webp",
];

const image = results.find((page) => {
const info = page.imageinfo?.[0];

if (!info?.url || !info?.mime) {
return false;
}

return allowedMimeTypes.includes(info.mime);
});

return image?.imageinfo?.[0]?.url ?? null;
}
Binary file added frontend/public/plant-placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 27 additions & 1 deletion frontend/src/components/plantCard/PlantCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,33 @@
}

.plant-card-image-missing {
background-color: var(--accent-green);
position: relative;
overflow: hidden;
}

.plant-card-image-missing::before {
content: "";
position: absolute;
inset: 0;
background: url("/plant-placeholder.png") center / cover;
opacity: 0.55;
}

.plant-card-placeholder-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--dark-green);
background: var(--light-green);
font-size: 1rem;
font-weight: 700;
text-align: center;
padding: 0.65rem 1rem;
border-radius: 0.75rem;
min-width: 70%;
max-width: 85%;
z-index: 1;
}

.plant-card-traits {
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/plantCard/PlantCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ export default function PlantCard({
{imageURL ? (
<img className="plant-card-image" src={imageURL} alt={name ?? ""} />
) : (
<div className="plant-card-image plant-card-image-missing" />
<div className="plant-card-image plant-card-image-missing">
<span className="plant-card-placeholder-text">
{t("plantCard.noImage")}
</span>
</div>
)}
</div>

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"watering": "Watering",
"sunlight": "Sunlight",
"careLevel": "Care level",
"unknown": "Unknown"
"unknown": "Unknown",
"noImage": "No image available"
}
}
3 changes: 2 additions & 1 deletion frontend/src/i18n/locales/no/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"watering": "Vanning",
"sunlight": "Sollys",
"careLevel": "Stell",
"unknown": "Ukjent"
"unknown": "Ukjent",
"noImage": "Bilde ikke tilgjengelig"
}
}
Loading