From 2cd433d7c42e1fd343b142470973ed3565349aad Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 28 May 2025 10:25:39 +0200 Subject: [PATCH 01/41] setting up with the codealong code --- data/flowers.json | 364 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- server.js | 46 ++++-- 3 files changed, 401 insertions(+), 11 deletions(-) create mode 100644 data/flowers.json diff --git a/data/flowers.json b/data/flowers.json new file mode 100644 index 0000000..18e9839 --- /dev/null +++ b/data/flowers.json @@ -0,0 +1,364 @@ +{ + "flowers": [ + { + "id": 1, + "name": "Rose", + "scientificName": "Rosa rubiginosa", + "botanicalFamily": "Rosaceae", + "color": "Red", + "isSpotted": true, + "scent": "Sweet", + "size": "Medium", + "symbolism": ["Love", "Passion", "Beauty"], + "lastSpottedTimestamp": "2025-05-26T10:15:00Z" + }, + { + "id": 2, + "name": "Tulip", + "scientificName": "Tulipa gesneriana", + "botanicalFamily": "Liliaceae", + "color": "Yellow", + "isSpotted": false, + "scent": "Mild", + "size": "Medium", + "symbolism": ["Perfect Love", "Fame", "Declaration of Love"], + "lastSpottedTimestamp": null + }, + { + "id": 3, + "name": "Daisy", + "scientificName": "Bellis perennis", + "botanicalFamily": "Asteraceae", + "color": "White", + "isSpotted": true, + "scent": "Slightly Sweet", + "size": "Small", + "symbolism": ["Innocence", "Purity", "New Beginnings"], + "lastSpottedTimestamp": "2025-05-25T14:30:00Z" + }, + { + "id": 4, + "name": "Sunflower", + "scientificName": "Helianthus annuus", + "botanicalFamily": "Asteraceae", + "color": "Yellow", + "isSpotted": true, + "scent": "Earthy", + "size": "Large", + "symbolism": ["Adoration", "Loyalty", "Longevity"], + "lastSpottedTimestamp": "2025-05-26T18:00:00Z" + }, + { + "id": 5, + "name": "Lily", + "scientificName": "Lilium candidum", + "botanicalFamily": "Liliaceae", + "color": "White", + "isSpotted": false, + "scent": "Strongly Sweet", + "size": "Large", + "symbolism": ["Purity", "Majesty", "Wealth"], + "lastSpottedTimestamp": null + }, + { + "id": 6, + "name": "Orchid", + "scientificName": "Phalaenopsis amabilis", + "botanicalFamily": "Orchidaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Delicate", + "size": "Medium", + "symbolism": ["Luxury", "Beauty", "Strength", "Love"], + "lastSpottedTimestamp": "2025-05-24T11:00:00Z" + }, + { + "id": 7, + "name": "Carnation", + "scientificName": "Dianthus caryophyllus", + "botanicalFamily": "Caryophyllaceae", + "color": "Pink", + "isSpotted": false, + "scent": "Spicy", + "size": "Medium", + "symbolism": ["Love (general)", "Fascination", "Distinction"], + "lastSpottedTimestamp": null + }, + { + "id": 8, + "name": "Daffodil", + "scientificName": "Narcissus pseudonarcissus", + "botanicalFamily": "Amaryllidaceae", + "color": "Yellow", + "isSpotted": true, + "scent": "Sweet", + "size": "Medium", + "symbolism": ["Rebirth", "New Beginnings", "Unrequited Love"], + "lastSpottedTimestamp": "2025-05-23T09:45:00Z" + }, + { + "id": 9, + "name": "Peony", + "scientificName": "Paeonia lactiflora", + "botanicalFamily": "Paeoniaceae", + "color": "Pink", + "isSpotted": true, + "scent": "Rosy", + "size": "Large", + "symbolism": ["Romance", "Prosperity", "Good Fortune", "Honor"], + "lastSpottedTimestamp": "2025-05-26T12:00:00Z" + }, + { + "id": 10, + "name": "Marigold", + "scientificName": "Tagetes erecta", + "botanicalFamily": "Asteraceae", + "color": "Orange", + "isSpotted": false, + "scent": "Pungent", + "size": "Medium", + "symbolism": ["Despair (historically)", "Grief", "Creativity", "Passion"], + "lastSpottedTimestamp": null + }, + { + "id": 11, + "name": "Lavender", + "scientificName": "Lavandula angustifolia", + "botanicalFamily": "Lamiaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Sweet", + "size": "Medium", + "symbolism": ["Purity", "Silence", "Devotion", "Calmness"], + "lastSpottedTimestamp": "2025-05-25T08:30:00Z" + }, + { + "id": 12, + "name": "Poppy", + "scientificName": "Papaver rhoeas", + "botanicalFamily": "Papaveraceae", + "color": "Red", + "isSpotted": true, + "scent": "Slight", + "size": "Medium", + "symbolism": ["Remembrance", "Sleep", "Peace"], + "lastSpottedTimestamp": "2025-05-22T17:00:00Z" + }, + { + "id": 13, + "name": "Iris", + "scientificName": "Iris germanica", + "botanicalFamily": "Iridaceae", + "color": "Blue", + "isSpotted": false, + "scent": "Grape-like", + "size": "Large", + "symbolism": ["Faith", "Hope", "Wisdom", "Valor"], + "lastSpottedTimestamp": null + }, + { + "id": 14, + "name": "Snapdragon", + "scientificName": "Antirrhinum majus", + "botanicalFamily": "Plantaginaceae", + "color": "Pink", + "isSpotted": true, + "scent": "Mildly Sweet", + "size": "Medium", + "symbolism": ["Graciousness", "Strength", "Deception"], + "lastSpottedTimestamp": "2025-05-26T09:00:00Z" + }, + { + "id": 15, + "name": "Zinnia", + "scientificName": "Zinnia elegans", + "botanicalFamily": "Asteraceae", + "color": "Orange", + "isSpotted": true, + "scent": "None", + "size": "Medium", + "symbolism": ["Thoughts of friends", "Endurance", "Lasting affection"], + "lastSpottedTimestamp": "2025-05-24T16:20:00Z" + }, + { + "id": 16, + "name": "Chrysanthemum", + "scientificName": "Chrysanthemum morifolium", + "botanicalFamily": "Asteraceae", + "color": "Yellow", + "isSpotted": false, + "scent": "Earthy", + "size": "Medium", + "symbolism": ["Joy", "Optimism", "Longevity"], + "lastSpottedTimestamp": null + }, + { + "id": 17, + "name": "Geranium", + "scientificName": "Pelargonium hortorum", + "botanicalFamily": "Geraniaceae", + "color": "Red", + "isSpotted": true, + "scent": "Varied", + "size": "Medium", + "symbolism": ["Comfort", "Gentility"], + "lastSpottedTimestamp": "2025-05-26T11:00:00Z" + }, + { + "id": 18, + "name": "Begonia", + "scientificName": "Begonia semperflorens", + "botanicalFamily": "Begoniaceae", + "color": "Pink", + "isSpotted": false, + "scent": "None", + "size": "Small", + "symbolism": ["Beware", "Dark thoughts", "Gratitude"], + "lastSpottedTimestamp": null + }, + { + "id": 19, + "name": "Petunia", + "scientificName": "Petunia x hybrida", + "botanicalFamily": "Solanaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Sweet", + "size": "Small", + "symbolism": ["Resentment", "Anger", "Your presence soothes me"], + "lastSpottedTimestamp": "2025-05-25T21:00:00Z" + }, + { + "id": 20, + "name": "Pansy", + "scientificName": "Viola tricolor var. hortensis", + "botanicalFamily": "Violaceae", + "color": "Yellow", + "isSpotted": true, + "scent": "Delicate", + "size": "Small", + "symbolism": ["Loving thoughts", "Remembrance", "Free-thinking"], + "lastSpottedTimestamp": "2025-05-26T15:10:00Z" + }, + { + "id": 21, + "name": "Forget-Me-Not", + "scientificName": "Myosotis sylvatica", + "botanicalFamily": "Boraginaceae", + "color": "Blue", + "isSpotted": true, + "scent": "None", + "size": "Small", + "symbolism": ["True love", "Remembrance", "Faithfulness"], + "lastSpottedTimestamp": "2025-05-23T10:00:00Z" + }, + { + "id": 22, + "name": "Bluebell", + "scientificName": "Hyacinthoides non-scripta", + "botanicalFamily": "Asparagaceae", + "color": "Blue-Violet", + "isSpotted": false, + "scent": "Sweet", + "size": "Small", + "symbolism": ["Humility", "Gratitude", "Everlasting love"], + "lastSpottedTimestamp": null + }, + { + "id": 23, + "name": "Crocus", + "scientificName": "Crocus vernus", + "botanicalFamily": "Iridaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Slightly Sweet", + "size": "Small", + "symbolism": ["Youthful gladness", "Cheerfulness"], + "lastSpottedTimestamp": "2025-03-10T12:00:00Z" + }, + { + "id": 24, + "name": "Snowdrop", + "scientificName": "Galanthus nivalis", + "botanicalFamily": "Amaryllidaceae", + "color": "White", + "isSpotted": false, + "scent": "Mildly Sweet", + "size": "Small", + "symbolism": ["Hope", "Purity", "Consolation", "Rebirth"], + "lastSpottedTimestamp": null + }, + { + "id": 25, + "name": "Hibiscus", + "scientificName": "Hibiscus rosa-sinensis", + "botanicalFamily": "Malvaceae", + "color": "Red", + "isSpotted": true, + "scent": "None", + "size": "Large", + "symbolism": ["Delicate beauty", "Consumed by love"], + "lastSpottedTimestamp": "2025-05-20T14:00:00Z" + }, + { + "id": 26, + "name": "Jasmine", + "scientificName": "Jasminum officinale", + "botanicalFamily": "Oleaceae", + "color": "White", + "isSpotted": true, + "scent": "Strongly Sweet", + "size": "Medium", + "symbolism": ["Love", "Beauty", "Sensuality", "Grace"], + "lastSpottedTimestamp": "2025-05-26T20:30:00Z" + }, + { + "id": 27, + "name": "Lilac", + "scientificName": "Syringa vulgaris", + "botanicalFamily": "Oleaceae", + "color": "Purple", + "isSpotted": false, + "scent": "Strongly Sweet", + "size": "Large", + "symbolism": ["First emotions of love", "Innocence", "Confidence"], + "lastSpottedTimestamp": null + }, + { + "id": 28, + "name": "Magnolia", + "scientificName": "Magnolia grandiflora", + "botanicalFamily": "Magnoliaceae", + "color": "White", + "isSpotted": true, + "scent": "Sweet", + "size": "Large", + "symbolism": ["Dignity", "Nobility", "Perseverance"], + "lastSpottedTimestamp": "2025-04-30T11:30:00Z" + }, + { + "id": 29, + "name": "Camellia", + "scientificName": "Camellia japonica", + "botanicalFamily": "Theaceae", + "color": "Pink", + "isSpotted": false, + "scent": "Slight", + "size": "Medium", + "symbolism": ["Admiration", "Perfection", "Faithfulness"], + "lastSpottedTimestamp": null + }, + { + "id": 30, + "name": "Gardenia", + "scientificName": "Gardenia jasminoides", + "botanicalFamily": "Rubiaceae", + "color": "White", + "isSpotted": true, + "scent": "Strongly Sweet", + "size": "Medium", + "symbolism": ["Purity", "Sweet love", "Joy", "You're lovely"], + "lastSpottedTimestamp": "2025-05-22T19:45:00Z" + } + ] +} diff --git a/package.json b/package.json index bf25bb6..c03d1f9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,6 @@ "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", "express": "^4.17.3", - "nodemon": "^3.0.1" + "nodemon": "^3.1.10" } } diff --git a/server.js b/server.js index f47771b..2ed662d 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,48 @@ -import cors from "cors" -import express from "express" +import cors from "cors"; +import express from "express"; + +import flowerData from "./data/flowers.json"; // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: // PORT=9000 npm start -const port = process.env.PORT || 8080 -const app = express() +const port = process.env.PORT || 8080; +const app = express(); // Add middlewares to enable cors and json body parsing -app.use(cors()) -app.use(express.json()) +app.use(cors()); +app.use(express.json()); // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") -}) + res.send("Hello Technigo!"); +}); + +// endpoint for getting all flowers +app.get("/flowers", (req, res) => { + res.send(flowerData); +}); + +//endpoint for one flower +app.get("/flowers/:id", (req, res) => { + const flower = flowerData.find((flower) => flower.id === req.params.id); + res.json(flower); +}); + +app.get("flowers/:id", (req, res) => { + console.log("req.params.id", req.params.id); + console.log("flower.id", flower.id); + + const flower = flowerData.find((flower) => flower.id === +req.params.id); + + if (!flower) { + return res.status(res.status(404).json({ error: "flower not found" })); + } + + res.json(flower); +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); From dccb546a4fe64db15b7afbf94bc9b9adf7a38bbd Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 28 May 2025 11:19:05 +0200 Subject: [PATCH 02/41] setting up fliters and fixing data structure for the flowers example --- data/flowers.json | 726 +++++++++++++++++++++++----------------------- package.json | 2 + server.js | 41 +-- 3 files changed, 388 insertions(+), 381 deletions(-) diff --git a/data/flowers.json b/data/flowers.json index 18e9839..926e91c 100644 --- a/data/flowers.json +++ b/data/flowers.json @@ -1,364 +1,362 @@ -{ - "flowers": [ - { - "id": 1, - "name": "Rose", - "scientificName": "Rosa rubiginosa", - "botanicalFamily": "Rosaceae", - "color": "Red", - "isSpotted": true, - "scent": "Sweet", - "size": "Medium", - "symbolism": ["Love", "Passion", "Beauty"], - "lastSpottedTimestamp": "2025-05-26T10:15:00Z" - }, - { - "id": 2, - "name": "Tulip", - "scientificName": "Tulipa gesneriana", - "botanicalFamily": "Liliaceae", - "color": "Yellow", - "isSpotted": false, - "scent": "Mild", - "size": "Medium", - "symbolism": ["Perfect Love", "Fame", "Declaration of Love"], - "lastSpottedTimestamp": null - }, - { - "id": 3, - "name": "Daisy", - "scientificName": "Bellis perennis", - "botanicalFamily": "Asteraceae", - "color": "White", - "isSpotted": true, - "scent": "Slightly Sweet", - "size": "Small", - "symbolism": ["Innocence", "Purity", "New Beginnings"], - "lastSpottedTimestamp": "2025-05-25T14:30:00Z" - }, - { - "id": 4, - "name": "Sunflower", - "scientificName": "Helianthus annuus", - "botanicalFamily": "Asteraceae", - "color": "Yellow", - "isSpotted": true, - "scent": "Earthy", - "size": "Large", - "symbolism": ["Adoration", "Loyalty", "Longevity"], - "lastSpottedTimestamp": "2025-05-26T18:00:00Z" - }, - { - "id": 5, - "name": "Lily", - "scientificName": "Lilium candidum", - "botanicalFamily": "Liliaceae", - "color": "White", - "isSpotted": false, - "scent": "Strongly Sweet", - "size": "Large", - "symbolism": ["Purity", "Majesty", "Wealth"], - "lastSpottedTimestamp": null - }, - { - "id": 6, - "name": "Orchid", - "scientificName": "Phalaenopsis amabilis", - "botanicalFamily": "Orchidaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Delicate", - "size": "Medium", - "symbolism": ["Luxury", "Beauty", "Strength", "Love"], - "lastSpottedTimestamp": "2025-05-24T11:00:00Z" - }, - { - "id": 7, - "name": "Carnation", - "scientificName": "Dianthus caryophyllus", - "botanicalFamily": "Caryophyllaceae", - "color": "Pink", - "isSpotted": false, - "scent": "Spicy", - "size": "Medium", - "symbolism": ["Love (general)", "Fascination", "Distinction"], - "lastSpottedTimestamp": null - }, - { - "id": 8, - "name": "Daffodil", - "scientificName": "Narcissus pseudonarcissus", - "botanicalFamily": "Amaryllidaceae", - "color": "Yellow", - "isSpotted": true, - "scent": "Sweet", - "size": "Medium", - "symbolism": ["Rebirth", "New Beginnings", "Unrequited Love"], - "lastSpottedTimestamp": "2025-05-23T09:45:00Z" - }, - { - "id": 9, - "name": "Peony", - "scientificName": "Paeonia lactiflora", - "botanicalFamily": "Paeoniaceae", - "color": "Pink", - "isSpotted": true, - "scent": "Rosy", - "size": "Large", - "symbolism": ["Romance", "Prosperity", "Good Fortune", "Honor"], - "lastSpottedTimestamp": "2025-05-26T12:00:00Z" - }, - { - "id": 10, - "name": "Marigold", - "scientificName": "Tagetes erecta", - "botanicalFamily": "Asteraceae", - "color": "Orange", - "isSpotted": false, - "scent": "Pungent", - "size": "Medium", - "symbolism": ["Despair (historically)", "Grief", "Creativity", "Passion"], - "lastSpottedTimestamp": null - }, - { - "id": 11, - "name": "Lavender", - "scientificName": "Lavandula angustifolia", - "botanicalFamily": "Lamiaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Sweet", - "size": "Medium", - "symbolism": ["Purity", "Silence", "Devotion", "Calmness"], - "lastSpottedTimestamp": "2025-05-25T08:30:00Z" - }, - { - "id": 12, - "name": "Poppy", - "scientificName": "Papaver rhoeas", - "botanicalFamily": "Papaveraceae", - "color": "Red", - "isSpotted": true, - "scent": "Slight", - "size": "Medium", - "symbolism": ["Remembrance", "Sleep", "Peace"], - "lastSpottedTimestamp": "2025-05-22T17:00:00Z" - }, - { - "id": 13, - "name": "Iris", - "scientificName": "Iris germanica", - "botanicalFamily": "Iridaceae", - "color": "Blue", - "isSpotted": false, - "scent": "Grape-like", - "size": "Large", - "symbolism": ["Faith", "Hope", "Wisdom", "Valor"], - "lastSpottedTimestamp": null - }, - { - "id": 14, - "name": "Snapdragon", - "scientificName": "Antirrhinum majus", - "botanicalFamily": "Plantaginaceae", - "color": "Pink", - "isSpotted": true, - "scent": "Mildly Sweet", - "size": "Medium", - "symbolism": ["Graciousness", "Strength", "Deception"], - "lastSpottedTimestamp": "2025-05-26T09:00:00Z" - }, - { - "id": 15, - "name": "Zinnia", - "scientificName": "Zinnia elegans", - "botanicalFamily": "Asteraceae", - "color": "Orange", - "isSpotted": true, - "scent": "None", - "size": "Medium", - "symbolism": ["Thoughts of friends", "Endurance", "Lasting affection"], - "lastSpottedTimestamp": "2025-05-24T16:20:00Z" - }, - { - "id": 16, - "name": "Chrysanthemum", - "scientificName": "Chrysanthemum morifolium", - "botanicalFamily": "Asteraceae", - "color": "Yellow", - "isSpotted": false, - "scent": "Earthy", - "size": "Medium", - "symbolism": ["Joy", "Optimism", "Longevity"], - "lastSpottedTimestamp": null - }, - { - "id": 17, - "name": "Geranium", - "scientificName": "Pelargonium hortorum", - "botanicalFamily": "Geraniaceae", - "color": "Red", - "isSpotted": true, - "scent": "Varied", - "size": "Medium", - "symbolism": ["Comfort", "Gentility"], - "lastSpottedTimestamp": "2025-05-26T11:00:00Z" - }, - { - "id": 18, - "name": "Begonia", - "scientificName": "Begonia semperflorens", - "botanicalFamily": "Begoniaceae", - "color": "Pink", - "isSpotted": false, - "scent": "None", - "size": "Small", - "symbolism": ["Beware", "Dark thoughts", "Gratitude"], - "lastSpottedTimestamp": null - }, - { - "id": 19, - "name": "Petunia", - "scientificName": "Petunia x hybrida", - "botanicalFamily": "Solanaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Sweet", - "size": "Small", - "symbolism": ["Resentment", "Anger", "Your presence soothes me"], - "lastSpottedTimestamp": "2025-05-25T21:00:00Z" - }, - { - "id": 20, - "name": "Pansy", - "scientificName": "Viola tricolor var. hortensis", - "botanicalFamily": "Violaceae", - "color": "Yellow", - "isSpotted": true, - "scent": "Delicate", - "size": "Small", - "symbolism": ["Loving thoughts", "Remembrance", "Free-thinking"], - "lastSpottedTimestamp": "2025-05-26T15:10:00Z" - }, - { - "id": 21, - "name": "Forget-Me-Not", - "scientificName": "Myosotis sylvatica", - "botanicalFamily": "Boraginaceae", - "color": "Blue", - "isSpotted": true, - "scent": "None", - "size": "Small", - "symbolism": ["True love", "Remembrance", "Faithfulness"], - "lastSpottedTimestamp": "2025-05-23T10:00:00Z" - }, - { - "id": 22, - "name": "Bluebell", - "scientificName": "Hyacinthoides non-scripta", - "botanicalFamily": "Asparagaceae", - "color": "Blue-Violet", - "isSpotted": false, - "scent": "Sweet", - "size": "Small", - "symbolism": ["Humility", "Gratitude", "Everlasting love"], - "lastSpottedTimestamp": null - }, - { - "id": 23, - "name": "Crocus", - "scientificName": "Crocus vernus", - "botanicalFamily": "Iridaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Slightly Sweet", - "size": "Small", - "symbolism": ["Youthful gladness", "Cheerfulness"], - "lastSpottedTimestamp": "2025-03-10T12:00:00Z" - }, - { - "id": 24, - "name": "Snowdrop", - "scientificName": "Galanthus nivalis", - "botanicalFamily": "Amaryllidaceae", - "color": "White", - "isSpotted": false, - "scent": "Mildly Sweet", - "size": "Small", - "symbolism": ["Hope", "Purity", "Consolation", "Rebirth"], - "lastSpottedTimestamp": null - }, - { - "id": 25, - "name": "Hibiscus", - "scientificName": "Hibiscus rosa-sinensis", - "botanicalFamily": "Malvaceae", - "color": "Red", - "isSpotted": true, - "scent": "None", - "size": "Large", - "symbolism": ["Delicate beauty", "Consumed by love"], - "lastSpottedTimestamp": "2025-05-20T14:00:00Z" - }, - { - "id": 26, - "name": "Jasmine", - "scientificName": "Jasminum officinale", - "botanicalFamily": "Oleaceae", - "color": "White", - "isSpotted": true, - "scent": "Strongly Sweet", - "size": "Medium", - "symbolism": ["Love", "Beauty", "Sensuality", "Grace"], - "lastSpottedTimestamp": "2025-05-26T20:30:00Z" - }, - { - "id": 27, - "name": "Lilac", - "scientificName": "Syringa vulgaris", - "botanicalFamily": "Oleaceae", - "color": "Purple", - "isSpotted": false, - "scent": "Strongly Sweet", - "size": "Large", - "symbolism": ["First emotions of love", "Innocence", "Confidence"], - "lastSpottedTimestamp": null - }, - { - "id": 28, - "name": "Magnolia", - "scientificName": "Magnolia grandiflora", - "botanicalFamily": "Magnoliaceae", - "color": "White", - "isSpotted": true, - "scent": "Sweet", - "size": "Large", - "symbolism": ["Dignity", "Nobility", "Perseverance"], - "lastSpottedTimestamp": "2025-04-30T11:30:00Z" - }, - { - "id": 29, - "name": "Camellia", - "scientificName": "Camellia japonica", - "botanicalFamily": "Theaceae", - "color": "Pink", - "isSpotted": false, - "scent": "Slight", - "size": "Medium", - "symbolism": ["Admiration", "Perfection", "Faithfulness"], - "lastSpottedTimestamp": null - }, - { - "id": 30, - "name": "Gardenia", - "scientificName": "Gardenia jasminoides", - "botanicalFamily": "Rubiaceae", - "color": "White", - "isSpotted": true, - "scent": "Strongly Sweet", - "size": "Medium", - "symbolism": ["Purity", "Sweet love", "Joy", "You're lovely"], - "lastSpottedTimestamp": "2025-05-22T19:45:00Z" - } - ] -} +[ + { + "id": 1, + "name": "Rose", + "scientificName": "Rosa rubiginosa", + "botanicalFamily": "Rosaceae", + "color": "Red", + "isSpotted": true, + "scent": "Sweet", + "size": "Medium", + "symbolism": ["Love", "Passion", "Beauty"], + "lastSpottedTimestamp": "2025-05-26T10:15:00Z" + }, + { + "id": 2, + "name": "Tulip", + "scientificName": "Tulipa gesneriana", + "botanicalFamily": "Liliaceae", + "color": "Yellow", + "isSpotted": false, + "scent": "Mild", + "size": "Medium", + "symbolism": ["Perfect Love", "Fame", "Declaration of Love"], + "lastSpottedTimestamp": null + }, + { + "id": 3, + "name": "Daisy", + "scientificName": "Bellis perennis", + "botanicalFamily": "Asteraceae", + "color": "White", + "isSpotted": true, + "scent": "Slightly Sweet", + "size": "Small", + "symbolism": ["Innocence", "Purity", "New Beginnings"], + "lastSpottedTimestamp": "2025-05-25T14:30:00Z" + }, + { + "id": 4, + "name": "Sunflower", + "scientificName": "Helianthus annuus", + "botanicalFamily": "Asteraceae", + "color": "Yellow", + "isSpotted": true, + "scent": "Earthy", + "size": "Large", + "symbolism": ["Adoration", "Loyalty", "Longevity"], + "lastSpottedTimestamp": "2025-05-26T18:00:00Z" + }, + { + "id": 5, + "name": "Lily", + "scientificName": "Lilium candidum", + "botanicalFamily": "Liliaceae", + "color": "White", + "isSpotted": false, + "scent": "Strongly Sweet", + "size": "Large", + "symbolism": ["Purity", "Majesty", "Wealth"], + "lastSpottedTimestamp": null + }, + { + "id": 6, + "name": "Orchid", + "scientificName": "Phalaenopsis amabilis", + "botanicalFamily": "Orchidaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Delicate", + "size": "Medium", + "symbolism": ["Luxury", "Beauty", "Strength", "Love"], + "lastSpottedTimestamp": "2025-05-24T11:00:00Z" + }, + { + "id": 7, + "name": "Carnation", + "scientificName": "Dianthus caryophyllus", + "botanicalFamily": "Caryophyllaceae", + "color": "Pink", + "isSpotted": false, + "scent": "Spicy", + "size": "Medium", + "symbolism": ["Love (general)", "Fascination", "Distinction"], + "lastSpottedTimestamp": null + }, + { + "id": 8, + "name": "Daffodil", + "scientificName": "Narcissus pseudonarcissus", + "botanicalFamily": "Amaryllidaceae", + "color": "Yellow", + "isSpotted": true, + "scent": "Sweet", + "size": "Medium", + "symbolism": ["Rebirth", "New Beginnings", "Unrequited Love"], + "lastSpottedTimestamp": "2025-05-23T09:45:00Z" + }, + { + "id": 9, + "name": "Peony", + "scientificName": "Paeonia lactiflora", + "botanicalFamily": "Paeoniaceae", + "color": "Pink", + "isSpotted": true, + "scent": "Rosy", + "size": "Large", + "symbolism": ["Romance", "Prosperity", "Good Fortune", "Honor"], + "lastSpottedTimestamp": "2025-05-26T12:00:00Z" + }, + { + "id": 10, + "name": "Marigold", + "scientificName": "Tagetes erecta", + "botanicalFamily": "Asteraceae", + "color": "Orange", + "isSpotted": false, + "scent": "Pungent", + "size": "Medium", + "symbolism": ["Despair (historically)", "Grief", "Creativity", "Passion"], + "lastSpottedTimestamp": null + }, + { + "id": 11, + "name": "Lavender", + "scientificName": "Lavandula angustifolia", + "botanicalFamily": "Lamiaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Sweet", + "size": "Medium", + "symbolism": ["Purity", "Silence", "Devotion", "Calmness"], + "lastSpottedTimestamp": "2025-05-25T08:30:00Z" + }, + { + "id": 12, + "name": "Poppy", + "scientificName": "Papaver rhoeas", + "botanicalFamily": "Papaveraceae", + "color": "Red", + "isSpotted": true, + "scent": "Slight", + "size": "Medium", + "symbolism": ["Remembrance", "Sleep", "Peace"], + "lastSpottedTimestamp": "2025-05-22T17:00:00Z" + }, + { + "id": 13, + "name": "Iris", + "scientificName": "Iris germanica", + "botanicalFamily": "Iridaceae", + "color": "Blue", + "isSpotted": false, + "scent": "Grape-like", + "size": "Large", + "symbolism": ["Faith", "Hope", "Wisdom", "Valor"], + "lastSpottedTimestamp": null + }, + { + "id": 14, + "name": "Snapdragon", + "scientificName": "Antirrhinum majus", + "botanicalFamily": "Plantaginaceae", + "color": "Pink", + "isSpotted": true, + "scent": "Mildly Sweet", + "size": "Medium", + "symbolism": ["Graciousness", "Strength", "Deception"], + "lastSpottedTimestamp": "2025-05-26T09:00:00Z" + }, + { + "id": 15, + "name": "Zinnia", + "scientificName": "Zinnia elegans", + "botanicalFamily": "Asteraceae", + "color": "Orange", + "isSpotted": true, + "scent": "None", + "size": "Medium", + "symbolism": ["Thoughts of friends", "Endurance", "Lasting affection"], + "lastSpottedTimestamp": "2025-05-24T16:20:00Z" + }, + { + "id": 16, + "name": "Chrysanthemum", + "scientificName": "Chrysanthemum morifolium", + "botanicalFamily": "Asteraceae", + "color": "Yellow", + "isSpotted": false, + "scent": "Earthy", + "size": "Medium", + "symbolism": ["Joy", "Optimism", "Longevity"], + "lastSpottedTimestamp": null + }, + { + "id": 17, + "name": "Geranium", + "scientificName": "Pelargonium hortorum", + "botanicalFamily": "Geraniaceae", + "color": "Red", + "isSpotted": true, + "scent": "Varied", + "size": "Medium", + "symbolism": ["Comfort", "Gentility"], + "lastSpottedTimestamp": "2025-05-26T11:00:00Z" + }, + { + "id": 18, + "name": "Begonia", + "scientificName": "Begonia semperflorens", + "botanicalFamily": "Begoniaceae", + "color": "Pink", + "isSpotted": false, + "scent": "None", + "size": "Small", + "symbolism": ["Beware", "Dark thoughts", "Gratitude"], + "lastSpottedTimestamp": null + }, + { + "id": 19, + "name": "Petunia", + "scientificName": "Petunia x hybrida", + "botanicalFamily": "Solanaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Sweet", + "size": "Small", + "symbolism": ["Resentment", "Anger", "Your presence soothes me"], + "lastSpottedTimestamp": "2025-05-25T21:00:00Z" + }, + { + "id": 20, + "name": "Pansy", + "scientificName": "Viola tricolor var. hortensis", + "botanicalFamily": "Violaceae", + "color": "Yellow", + "isSpotted": true, + "scent": "Delicate", + "size": "Small", + "symbolism": ["Loving thoughts", "Remembrance", "Free-thinking"], + "lastSpottedTimestamp": "2025-05-26T15:10:00Z" + }, + { + "id": 21, + "name": "Forget-Me-Not", + "scientificName": "Myosotis sylvatica", + "botanicalFamily": "Boraginaceae", + "color": "Blue", + "isSpotted": true, + "scent": "None", + "size": "Small", + "symbolism": ["True love", "Remembrance", "Faithfulness"], + "lastSpottedTimestamp": "2025-05-23T10:00:00Z" + }, + { + "id": 22, + "name": "Bluebell", + "scientificName": "Hyacinthoides non-scripta", + "botanicalFamily": "Asparagaceae", + "color": "Blue-Violet", + "isSpotted": false, + "scent": "Sweet", + "size": "Small", + "symbolism": ["Humility", "Gratitude", "Everlasting love"], + "lastSpottedTimestamp": null + }, + { + "id": 23, + "name": "Crocus", + "scientificName": "Crocus vernus", + "botanicalFamily": "Iridaceae", + "color": "Purple", + "isSpotted": true, + "scent": "Slightly Sweet", + "size": "Small", + "symbolism": ["Youthful gladness", "Cheerfulness"], + "lastSpottedTimestamp": "2025-03-10T12:00:00Z" + }, + { + "id": 24, + "name": "Snowdrop", + "scientificName": "Galanthus nivalis", + "botanicalFamily": "Amaryllidaceae", + "color": "White", + "isSpotted": false, + "scent": "Mildly Sweet", + "size": "Small", + "symbolism": ["Hope", "Purity", "Consolation", "Rebirth"], + "lastSpottedTimestamp": null + }, + { + "id": 25, + "name": "Hibiscus", + "scientificName": "Hibiscus rosa-sinensis", + "botanicalFamily": "Malvaceae", + "color": "Red", + "isSpotted": true, + "scent": "None", + "size": "Large", + "symbolism": ["Delicate beauty", "Consumed by love"], + "lastSpottedTimestamp": "2025-05-20T14:00:00Z" + }, + { + "id": 26, + "name": "Jasmine", + "scientificName": "Jasminum officinale", + "botanicalFamily": "Oleaceae", + "color": "White", + "isSpotted": true, + "scent": "Strongly Sweet", + "size": "Medium", + "symbolism": ["Love", "Beauty", "Sensuality", "Grace"], + "lastSpottedTimestamp": "2025-05-26T20:30:00Z" + }, + { + "id": 27, + "name": "Lilac", + "scientificName": "Syringa vulgaris", + "botanicalFamily": "Oleaceae", + "color": "Purple", + "isSpotted": false, + "scent": "Strongly Sweet", + "size": "Large", + "symbolism": ["First emotions of love", "Innocence", "Confidence"], + "lastSpottedTimestamp": null + }, + { + "id": 28, + "name": "Magnolia", + "scientificName": "Magnolia grandiflora", + "botanicalFamily": "Magnoliaceae", + "color": "White", + "isSpotted": true, + "scent": "Sweet", + "size": "Large", + "symbolism": ["Dignity", "Nobility", "Perseverance"], + "lastSpottedTimestamp": "2025-04-30T11:30:00Z" + }, + { + "id": 29, + "name": "Camellia", + "scientificName": "Camellia japonica", + "botanicalFamily": "Theaceae", + "color": "Pink", + "isSpotted": false, + "scent": "Slight", + "size": "Medium", + "symbolism": ["Admiration", "Perfection", "Faithfulness"], + "lastSpottedTimestamp": null + }, + { + "id": 30, + "name": "Gardenia", + "scientificName": "Gardenia jasminoides", + "botanicalFamily": "Rubiaceae", + "color": "White", + "isSpotted": true, + "scent": "Strongly Sweet", + "size": "Medium", + "symbolism": ["Purity", "Sweet love", "Joy", "You're lovely"], + "lastSpottedTimestamp": "2025-05-22T19:45:00Z" + } +] diff --git a/package.json b/package.json index c03d1f9..44cce74 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.17.3", + "express-list-endpoints": "^7.1.1", "nodemon": "^3.1.10" } } diff --git a/server.js b/server.js index 2ed662d..900ee3a 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,8 @@ -import cors from "cors"; import express from "express"; +import cors from "cors"; import flowerData from "./data/flowers.json"; -// Defines the port the app will run on. Defaults to 8080, but can be overridden -// when starting the server. Example command to overwrite PORT env variable value: -// PORT=9000 npm start const port = process.env.PORT || 8080; const app = express(); @@ -13,30 +10,40 @@ const app = express(); app.use(cors()); app.use(express.json()); -// Start defining your routes here +// TODO: add documentation of the API here with express-list-endpoints app.get("/", (req, res) => { - res.send("Hello Technigo!"); + res.send("Hello Technigo! ❤️"); }); // endpoint for getting all flowers +// TODO: add query params to be able to filter on color or sort by name app.get("/flowers", (req, res) => { - res.send(flowerData); -}); + const { color, size } = req.query; -//endpoint for one flower -app.get("/flowers/:id", (req, res) => { - const flower = flowerData.find((flower) => flower.id === req.params.id); - res.json(flower); -}); + let filteredFlowers = flowerData; -app.get("flowers/:id", (req, res) => { - console.log("req.params.id", req.params.id); - console.log("flower.id", flower.id); + if (color) { + filteredFlowers = filteredFlowers.filter( + (flower) => flower.color.toLowerCase() === color.toLowerCase() + ); + } + if (size) { + filteredFlowers = filteredFlowers.filter( + (flower) => flower.size.toLowerCase() === size.toLowerCase() + ); + } + res.json(filteredFlowers); +}); + +// endpoint for gettin one flower +app.get("/flowers/:id", (req, res) => { + // be aware! The id that comes from the param is of type string. and in our json it is of type number. You have to turn them into the same type before you can compare them. trun a string to a number by adding + 👇 const flower = flowerData.find((flower) => flower.id === +req.params.id); + // tiny error handling if we get an id that doesnt exist in our data if (!flower) { - return res.status(res.status(404).json({ error: "flower not found" })); + return res.status(404).json({ error: "flower not found" }); } res.json(flower); From f980af9ed5e19a91381165f533baaef81942dbb2 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 28 May 2025 11:24:30 +0200 Subject: [PATCH 03/41] dsipalying endpoints --- server.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 900ee3a..bbdca09 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,6 @@ import express from "express"; import cors from "cors"; +import listEndpoints from "express-list-endpoints"; import flowerData from "./data/flowers.json"; @@ -12,7 +13,11 @@ app.use(express.json()); // TODO: add documentation of the API here with express-list-endpoints app.get("/", (req, res) => { - res.send("Hello Technigo! ❤️"); + const endpoints = listEndpoints(app); + res.json({ + message: "Welcome to the flower API", + endpoints: endpoints, + }); }); // endpoint for getting all flowers From 6e9c2e52f83ee59cd9338db2acf32e55a526559a Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Thu, 29 May 2025 13:30:04 +0200 Subject: [PATCH 04/41] adding routes and modifying data --- data/data.json | 9 ++ data/flowers.json | 362 ---------------------------------------------- server.js | 31 +++- 3 files changed, 35 insertions(+), 367 deletions(-) create mode 100644 data/data.json delete mode 100644 data/flowers.json diff --git a/data/data.json b/data/data.json new file mode 100644 index 0000000..2b37045 --- /dev/null +++ b/data/data.json @@ -0,0 +1,9 @@ +[ + { + "_id": "abc123", + "message": "This is a thought", + "hearts": 3, + "createdAt": "2025-05-29T12:00:00.000Z", + "likedBy": ["client1", "client2", "client3"] + } +] diff --git a/data/flowers.json b/data/flowers.json deleted file mode 100644 index 926e91c..0000000 --- a/data/flowers.json +++ /dev/null @@ -1,362 +0,0 @@ -[ - { - "id": 1, - "name": "Rose", - "scientificName": "Rosa rubiginosa", - "botanicalFamily": "Rosaceae", - "color": "Red", - "isSpotted": true, - "scent": "Sweet", - "size": "Medium", - "symbolism": ["Love", "Passion", "Beauty"], - "lastSpottedTimestamp": "2025-05-26T10:15:00Z" - }, - { - "id": 2, - "name": "Tulip", - "scientificName": "Tulipa gesneriana", - "botanicalFamily": "Liliaceae", - "color": "Yellow", - "isSpotted": false, - "scent": "Mild", - "size": "Medium", - "symbolism": ["Perfect Love", "Fame", "Declaration of Love"], - "lastSpottedTimestamp": null - }, - { - "id": 3, - "name": "Daisy", - "scientificName": "Bellis perennis", - "botanicalFamily": "Asteraceae", - "color": "White", - "isSpotted": true, - "scent": "Slightly Sweet", - "size": "Small", - "symbolism": ["Innocence", "Purity", "New Beginnings"], - "lastSpottedTimestamp": "2025-05-25T14:30:00Z" - }, - { - "id": 4, - "name": "Sunflower", - "scientificName": "Helianthus annuus", - "botanicalFamily": "Asteraceae", - "color": "Yellow", - "isSpotted": true, - "scent": "Earthy", - "size": "Large", - "symbolism": ["Adoration", "Loyalty", "Longevity"], - "lastSpottedTimestamp": "2025-05-26T18:00:00Z" - }, - { - "id": 5, - "name": "Lily", - "scientificName": "Lilium candidum", - "botanicalFamily": "Liliaceae", - "color": "White", - "isSpotted": false, - "scent": "Strongly Sweet", - "size": "Large", - "symbolism": ["Purity", "Majesty", "Wealth"], - "lastSpottedTimestamp": null - }, - { - "id": 6, - "name": "Orchid", - "scientificName": "Phalaenopsis amabilis", - "botanicalFamily": "Orchidaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Delicate", - "size": "Medium", - "symbolism": ["Luxury", "Beauty", "Strength", "Love"], - "lastSpottedTimestamp": "2025-05-24T11:00:00Z" - }, - { - "id": 7, - "name": "Carnation", - "scientificName": "Dianthus caryophyllus", - "botanicalFamily": "Caryophyllaceae", - "color": "Pink", - "isSpotted": false, - "scent": "Spicy", - "size": "Medium", - "symbolism": ["Love (general)", "Fascination", "Distinction"], - "lastSpottedTimestamp": null - }, - { - "id": 8, - "name": "Daffodil", - "scientificName": "Narcissus pseudonarcissus", - "botanicalFamily": "Amaryllidaceae", - "color": "Yellow", - "isSpotted": true, - "scent": "Sweet", - "size": "Medium", - "symbolism": ["Rebirth", "New Beginnings", "Unrequited Love"], - "lastSpottedTimestamp": "2025-05-23T09:45:00Z" - }, - { - "id": 9, - "name": "Peony", - "scientificName": "Paeonia lactiflora", - "botanicalFamily": "Paeoniaceae", - "color": "Pink", - "isSpotted": true, - "scent": "Rosy", - "size": "Large", - "symbolism": ["Romance", "Prosperity", "Good Fortune", "Honor"], - "lastSpottedTimestamp": "2025-05-26T12:00:00Z" - }, - { - "id": 10, - "name": "Marigold", - "scientificName": "Tagetes erecta", - "botanicalFamily": "Asteraceae", - "color": "Orange", - "isSpotted": false, - "scent": "Pungent", - "size": "Medium", - "symbolism": ["Despair (historically)", "Grief", "Creativity", "Passion"], - "lastSpottedTimestamp": null - }, - { - "id": 11, - "name": "Lavender", - "scientificName": "Lavandula angustifolia", - "botanicalFamily": "Lamiaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Sweet", - "size": "Medium", - "symbolism": ["Purity", "Silence", "Devotion", "Calmness"], - "lastSpottedTimestamp": "2025-05-25T08:30:00Z" - }, - { - "id": 12, - "name": "Poppy", - "scientificName": "Papaver rhoeas", - "botanicalFamily": "Papaveraceae", - "color": "Red", - "isSpotted": true, - "scent": "Slight", - "size": "Medium", - "symbolism": ["Remembrance", "Sleep", "Peace"], - "lastSpottedTimestamp": "2025-05-22T17:00:00Z" - }, - { - "id": 13, - "name": "Iris", - "scientificName": "Iris germanica", - "botanicalFamily": "Iridaceae", - "color": "Blue", - "isSpotted": false, - "scent": "Grape-like", - "size": "Large", - "symbolism": ["Faith", "Hope", "Wisdom", "Valor"], - "lastSpottedTimestamp": null - }, - { - "id": 14, - "name": "Snapdragon", - "scientificName": "Antirrhinum majus", - "botanicalFamily": "Plantaginaceae", - "color": "Pink", - "isSpotted": true, - "scent": "Mildly Sweet", - "size": "Medium", - "symbolism": ["Graciousness", "Strength", "Deception"], - "lastSpottedTimestamp": "2025-05-26T09:00:00Z" - }, - { - "id": 15, - "name": "Zinnia", - "scientificName": "Zinnia elegans", - "botanicalFamily": "Asteraceae", - "color": "Orange", - "isSpotted": true, - "scent": "None", - "size": "Medium", - "symbolism": ["Thoughts of friends", "Endurance", "Lasting affection"], - "lastSpottedTimestamp": "2025-05-24T16:20:00Z" - }, - { - "id": 16, - "name": "Chrysanthemum", - "scientificName": "Chrysanthemum morifolium", - "botanicalFamily": "Asteraceae", - "color": "Yellow", - "isSpotted": false, - "scent": "Earthy", - "size": "Medium", - "symbolism": ["Joy", "Optimism", "Longevity"], - "lastSpottedTimestamp": null - }, - { - "id": 17, - "name": "Geranium", - "scientificName": "Pelargonium hortorum", - "botanicalFamily": "Geraniaceae", - "color": "Red", - "isSpotted": true, - "scent": "Varied", - "size": "Medium", - "symbolism": ["Comfort", "Gentility"], - "lastSpottedTimestamp": "2025-05-26T11:00:00Z" - }, - { - "id": 18, - "name": "Begonia", - "scientificName": "Begonia semperflorens", - "botanicalFamily": "Begoniaceae", - "color": "Pink", - "isSpotted": false, - "scent": "None", - "size": "Small", - "symbolism": ["Beware", "Dark thoughts", "Gratitude"], - "lastSpottedTimestamp": null - }, - { - "id": 19, - "name": "Petunia", - "scientificName": "Petunia x hybrida", - "botanicalFamily": "Solanaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Sweet", - "size": "Small", - "symbolism": ["Resentment", "Anger", "Your presence soothes me"], - "lastSpottedTimestamp": "2025-05-25T21:00:00Z" - }, - { - "id": 20, - "name": "Pansy", - "scientificName": "Viola tricolor var. hortensis", - "botanicalFamily": "Violaceae", - "color": "Yellow", - "isSpotted": true, - "scent": "Delicate", - "size": "Small", - "symbolism": ["Loving thoughts", "Remembrance", "Free-thinking"], - "lastSpottedTimestamp": "2025-05-26T15:10:00Z" - }, - { - "id": 21, - "name": "Forget-Me-Not", - "scientificName": "Myosotis sylvatica", - "botanicalFamily": "Boraginaceae", - "color": "Blue", - "isSpotted": true, - "scent": "None", - "size": "Small", - "symbolism": ["True love", "Remembrance", "Faithfulness"], - "lastSpottedTimestamp": "2025-05-23T10:00:00Z" - }, - { - "id": 22, - "name": "Bluebell", - "scientificName": "Hyacinthoides non-scripta", - "botanicalFamily": "Asparagaceae", - "color": "Blue-Violet", - "isSpotted": false, - "scent": "Sweet", - "size": "Small", - "symbolism": ["Humility", "Gratitude", "Everlasting love"], - "lastSpottedTimestamp": null - }, - { - "id": 23, - "name": "Crocus", - "scientificName": "Crocus vernus", - "botanicalFamily": "Iridaceae", - "color": "Purple", - "isSpotted": true, - "scent": "Slightly Sweet", - "size": "Small", - "symbolism": ["Youthful gladness", "Cheerfulness"], - "lastSpottedTimestamp": "2025-03-10T12:00:00Z" - }, - { - "id": 24, - "name": "Snowdrop", - "scientificName": "Galanthus nivalis", - "botanicalFamily": "Amaryllidaceae", - "color": "White", - "isSpotted": false, - "scent": "Mildly Sweet", - "size": "Small", - "symbolism": ["Hope", "Purity", "Consolation", "Rebirth"], - "lastSpottedTimestamp": null - }, - { - "id": 25, - "name": "Hibiscus", - "scientificName": "Hibiscus rosa-sinensis", - "botanicalFamily": "Malvaceae", - "color": "Red", - "isSpotted": true, - "scent": "None", - "size": "Large", - "symbolism": ["Delicate beauty", "Consumed by love"], - "lastSpottedTimestamp": "2025-05-20T14:00:00Z" - }, - { - "id": 26, - "name": "Jasmine", - "scientificName": "Jasminum officinale", - "botanicalFamily": "Oleaceae", - "color": "White", - "isSpotted": true, - "scent": "Strongly Sweet", - "size": "Medium", - "symbolism": ["Love", "Beauty", "Sensuality", "Grace"], - "lastSpottedTimestamp": "2025-05-26T20:30:00Z" - }, - { - "id": 27, - "name": "Lilac", - "scientificName": "Syringa vulgaris", - "botanicalFamily": "Oleaceae", - "color": "Purple", - "isSpotted": false, - "scent": "Strongly Sweet", - "size": "Large", - "symbolism": ["First emotions of love", "Innocence", "Confidence"], - "lastSpottedTimestamp": null - }, - { - "id": 28, - "name": "Magnolia", - "scientificName": "Magnolia grandiflora", - "botanicalFamily": "Magnoliaceae", - "color": "White", - "isSpotted": true, - "scent": "Sweet", - "size": "Large", - "symbolism": ["Dignity", "Nobility", "Perseverance"], - "lastSpottedTimestamp": "2025-04-30T11:30:00Z" - }, - { - "id": 29, - "name": "Camellia", - "scientificName": "Camellia japonica", - "botanicalFamily": "Theaceae", - "color": "Pink", - "isSpotted": false, - "scent": "Slight", - "size": "Medium", - "symbolism": ["Admiration", "Perfection", "Faithfulness"], - "lastSpottedTimestamp": null - }, - { - "id": 30, - "name": "Gardenia", - "scientificName": "Gardenia jasminoides", - "botanicalFamily": "Rubiaceae", - "color": "White", - "isSpotted": true, - "scent": "Strongly Sweet", - "size": "Medium", - "symbolism": ["Purity", "Sweet love", "Joy", "You're lovely"], - "lastSpottedTimestamp": "2025-05-22T19:45:00Z" - } -] diff --git a/server.js b/server.js index bbdca09..e665680 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,7 @@ import express from "express"; import cors from "cors"; import listEndpoints from "express-list-endpoints"; -import flowerData from "./data/flowers.json"; +import data from "./data/data.json"; const port = process.env.PORT || 8080; const app = express(); @@ -11,21 +11,42 @@ const app = express(); app.use(cors()); app.use(express.json()); -// TODO: add documentation of the API here with express-list-endpoints +// documentation of the API app.get("/", (req, res) => { const endpoints = listEndpoints(app); res.json({ - message: "Welcome to the flower API", + message: "Welcome to the Happy Thougts API", endpoints: endpoints, }); }); +// routes and endpoints +app.get("/thoughts", (req, res) => res.send("getThoughts placeholder")); + +app.delete("/thoughts:id", (req, res) => + res.send("deleteThoughts placeholder") +); + +app.get("/thoughts/liked/:clientId", (req, res) => + res.send("likedThoughts placeholder") +); + +app.post("/thoughts", (req, res) => res.send("postThought placeholder")); + +app.post("/thoughts/:id/like", (req, res) => + res.send("likeThought placeholder") +); + +app.delete("/thoughts/:id/like", (req, res) => + res.send("unlikeThought placeholder") +); + // endpoint for getting all flowers // TODO: add query params to be able to filter on color or sort by name app.get("/flowers", (req, res) => { const { color, size } = req.query; - let filteredFlowers = flowerData; + let filteredFlowers = data; if (color) { filteredFlowers = filteredFlowers.filter( @@ -44,7 +65,7 @@ app.get("/flowers", (req, res) => { // endpoint for gettin one flower app.get("/flowers/:id", (req, res) => { // be aware! The id that comes from the param is of type string. and in our json it is of type number. You have to turn them into the same type before you can compare them. trun a string to a number by adding + 👇 - const flower = flowerData.find((flower) => flower.id === +req.params.id); + const flower = data.find((flower) => flower.id === +req.params.id); // tiny error handling if we get an id that doesnt exist in our data if (!flower) { From 9a388b860fa44940f243a812032bac372bca789c Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Thu, 29 May 2025 13:42:09 +0200 Subject: [PATCH 05/41] adding endpoints for get requests --- server.js | 70 +++++++++++++++++++++---------------------------------- 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/server.js b/server.js index e665680..c5af4d4 100644 --- a/server.js +++ b/server.js @@ -21,59 +21,41 @@ app.get("/", (req, res) => { }); // routes and endpoints -app.get("/thoughts", (req, res) => res.send("getThoughts placeholder")); - -app.delete("/thoughts:id", (req, res) => - res.send("deleteThoughts placeholder") -); - -app.get("/thoughts/liked/:clientId", (req, res) => - res.send("likedThoughts placeholder") -); - -app.post("/thoughts", (req, res) => res.send("postThought placeholder")); - -app.post("/thoughts/:id/like", (req, res) => - res.send("likeThought placeholder") -); +// get all thoughts +app.get("/thoughts", (req, res) => { + res.json(data); +}); -app.delete("/thoughts/:id/like", (req, res) => - res.send("unlikeThought placeholder") -); +// get one thought +app.get("/thoughts/:id", (req, res) => { + const { id } = req.params; + const thought = data.find((item) => item.id === +id); -// endpoint for getting all flowers -// TODO: add query params to be able to filter on color or sort by name -app.get("/flowers", (req, res) => { - const { color, size } = req.query; + if (!thought) { + return res.status(404).json({ error: "Thought not found" }); + } - let filteredFlowers = data; + res.json(thought); +}); - if (color) { - filteredFlowers = filteredFlowers.filter( - (flower) => flower.color.toLowerCase() === color.toLowerCase() - ); - } - if (size) { - filteredFlowers = filteredFlowers.filter( - (flower) => flower.size.toLowerCase() === size.toLowerCase() - ); - } +// get liked thoughts +app.get("/thoughts/liked/:clientId", (req, res) => { + const { clientId } = req.params; + const likedThoughts = data.filter((thought) => + thought.likedBy.includes(clientId) + ); - res.json(filteredFlowers); + res.json(likedThoughts); }); -// endpoint for gettin one flower -app.get("/flowers/:id", (req, res) => { - // be aware! The id that comes from the param is of type string. and in our json it is of type number. You have to turn them into the same type before you can compare them. trun a string to a number by adding + 👇 - const flower = data.find((flower) => flower.id === +req.params.id); +// cant do these yet i think (maybe next week?) +app.delete("/thoughts:id", (req, res) => res.send("placeholder")); - // tiny error handling if we get an id that doesnt exist in our data - if (!flower) { - return res.status(404).json({ error: "flower not found" }); - } +app.post("/thoughts", (req, res) => res.send("placeholder")); - res.json(flower); -}); +app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); + +app.delete("/thoughts/:id/like", (req, res) => res.send("placeholder")); // Start the server app.listen(port, () => { From 01dcdd6ede3a1abd4f61cf64c86d04cd8978c0cb Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Thu, 29 May 2025 13:50:44 +0200 Subject: [PATCH 06/41] adding stretch goals: filtering, pagination, error handling, categories --- data/data.json | 5 +++-- server.js | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/data/data.json b/data/data.json index 2b37045..63be4a3 100644 --- a/data/data.json +++ b/data/data.json @@ -1,9 +1,10 @@ [ { - "_id": "abc123", + "_id": "1", "message": "This is a thought", "hearts": 3, "createdAt": "2025-05-29T12:00:00.000Z", - "likedBy": ["client1", "client2", "client3"] + "likedBy": ["client1", "client2", "client3"], + "category": "Project" } ] diff --git a/server.js b/server.js index c5af4d4..b1e8ca7 100644 --- a/server.js +++ b/server.js @@ -7,11 +7,11 @@ import data from "./data/data.json"; const port = process.env.PORT || 8080; const app = express(); -// Add middlewares to enable cors and json body parsing +// MIDDLEWARES // app.use(cors()); app.use(express.json()); -// documentation of the API +// API DOCUMENTATION // app.get("/", (req, res) => { const endpoints = listEndpoints(app); res.json({ @@ -20,10 +20,36 @@ app.get("/", (req, res) => { }); }); -// routes and endpoints +// ROUTES AND ENDPOINTS // // get all thoughts app.get("/thoughts", (req, res) => { - res.json(data); + const { category, minHearts, sortBy, page = 1, limit = 10 } = req.query; + + let filtered = data; + + if (category) { + filtered = filtered.filter( + (item) => item.category.toLowerCase() === category.toLowerCase() + ); + } + + if (sortBy === "date") { + filtered = filtered.sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt) + ); + } + + const start = (page - 1) * limit; + const end = start + +limit; + + const paginated = filtered.slice(start, end); + + res.json({ + page: +page, + limit: +limit, + total: filtered.length, + thoughts: paginated, + }); }); // get one thought @@ -57,7 +83,7 @@ app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); app.delete("/thoughts/:id/like", (req, res) => res.send("placeholder")); -// Start the server +// SERVER START // app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From 2fdd7aa3e37d9a35b61ab2e33886e7569713dcb2 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Thu, 29 May 2025 14:39:13 +0200 Subject: [PATCH 07/41] fixing names --- server.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index b1e8ca7..3f19e8c 100644 --- a/server.js +++ b/server.js @@ -23,18 +23,18 @@ app.get("/", (req, res) => { // ROUTES AND ENDPOINTS // // get all thoughts app.get("/thoughts", (req, res) => { - const { category, minHearts, sortBy, page = 1, limit = 10 } = req.query; + const { category, sortBy, page = 1, limit = 10 } = req.query; - let filtered = data; + let filteredThoughts = data; if (category) { - filtered = filtered.filter( + filteredThoughts = filteredThoughts.filter( (item) => item.category.toLowerCase() === category.toLowerCase() ); } if (sortBy === "date") { - filtered = filtered.sort( + filteredThoughts = filteredThoughts.sort( (a, b) => new Date(b.createdAt) - new Date(a.createdAt) ); } @@ -42,13 +42,13 @@ app.get("/thoughts", (req, res) => { const start = (page - 1) * limit; const end = start + +limit; - const paginated = filtered.slice(start, end); + const paginatedThoughts = filteredThoughts.slice(start, end); res.json({ page: +page, limit: +limit, - total: filtered.length, - thoughts: paginated, + total: filteredThoughts.length, + thoughts: paginatedThoughts, }); }); From f7d2b0594e41ea99343dcfda83000ba3a8dc61ec Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Thu, 29 May 2025 14:41:17 +0200 Subject: [PATCH 08/41] adding some dummy data --- data/data.json | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/data/data.json b/data/data.json index 63be4a3..3836db6 100644 --- a/data/data.json +++ b/data/data.json @@ -1,10 +1,42 @@ [ { - "_id": "1", - "message": "This is a thought", + "id": 1, + "message": "I finally fixed the login bug! 🎉", + "category": "project", "hearts": 3, - "createdAt": "2025-05-29T12:00:00.000Z", - "likedBy": ["client1", "client2", "client3"], - "category": "Project" + "createdAt": "2024-06-01T10:30:00Z", + "likedBy": ["user123"] + }, + { + "id": 2, + "message": "Meal prepped for the week. Feeling organized!", + "category": "home", + "hearts": 5, + "createdAt": "2024-06-03T08:15:00Z", + "likedBy": [] + }, + { + "id": 3, + "message": "Pasta night with friends 🍝❤️", + "category": "food", + "hearts": 8, + "createdAt": "2024-06-05T19:00:00Z", + "likedBy": ["user123", "user456"] + }, + { + "id": 4, + "message": "Started a new React project today!", + "category": "project", + "hearts": 4, + "createdAt": "2024-06-06T14:00:00Z", + "likedBy": [] + }, + { + "id": 5, + "message": "Cleaned the whole apartment and lit some candles 🕯️", + "category": "home", + "hearts": 2, + "createdAt": "2024-06-07T09:00:00Z", + "likedBy": ["user789"] } ] From aed5d75a908871a1fdde46d6c75f5f3793e4ae35 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Mon, 2 Jun 2025 12:32:31 +0200 Subject: [PATCH 09/41] setting up mongodb --- package.json | 1 + server.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/package.json b/package.json index 44cce74..9c9d350 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dotenv": "^16.5.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.15.1", "nodemon": "^3.1.10" } } diff --git a/server.js b/server.js index 3f19e8c..89b84ec 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,7 @@ import express from "express"; import cors from "cors"; import listEndpoints from "express-list-endpoints"; +import mongoose from "mongoose"; import data from "./data/data.json"; @@ -11,8 +12,31 @@ const app = express(); app.use(cors()); app.use(express.json()); +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts"; +mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); +mongoose.Promise = Promise; + +// adapt this for the thoughts instead +const Animal = mongoose.model("Animal", { + name: String, + age: Number, + isFurry: Boolean, +}); + +Animal.deleteMany().then(() => { + new Animal({ + name: "Alfons", + age: 2, + isFurry: true, + }).save(); +}); + // API DOCUMENTATION // app.get("/", (req, res) => { + Animal.find().then((animals) => { + res.json(animals); + }); + const endpoints = listEndpoints(app); res.json({ message: "Welcome to the Happy Thougts API", From 37ba143e1bb857e56778bba156e40bfc71e55c15 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Tue, 3 Jun 2025 10:54:54 +0200 Subject: [PATCH 10/41] setting up the schema --- server.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/server.js b/server.js index 89b84ec..851a78a 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ -import express from "express"; import cors from "cors"; +import express from "express"; import listEndpoints from "express-list-endpoints"; import mongoose from "mongoose"; @@ -12,19 +12,24 @@ const app = express(); app.use(cors()); app.use(express.json()); +// connect to database const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts"; -mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.Promise = Promise; - -// adapt this for the thoughts instead -const Animal = mongoose.model("Animal", { - name: String, - age: Number, - isFurry: Boolean, +mongoose.connect(mongoUrl); + +// CHECK THIS +const thoughtSchema = new mongoose.Schema({ + id: Number, + message: String, + hearts: Number, + likedBy: [String], + created: { + type: Date, + default: Date.now, + }, }); -Animal.deleteMany().then(() => { - new Animal({ +Thought.deleteMany().then(() => { + new Thought({ name: "Alfons", age: 2, isFurry: true, @@ -33,8 +38,8 @@ Animal.deleteMany().then(() => { // API DOCUMENTATION // app.get("/", (req, res) => { - Animal.find().then((animals) => { - res.json(animals); + Thought.find().then((thoughts) => { + res.json(thoughts); }); const endpoints = listEndpoints(app); From bec058403f08e544df48508c586e9c2905e9bb3c Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Tue, 3 Jun 2025 10:55:55 +0200 Subject: [PATCH 11/41] connect schema to model --- server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.js b/server.js index 851a78a..edbe5c1 100644 --- a/server.js +++ b/server.js @@ -28,6 +28,8 @@ const thoughtSchema = new mongoose.Schema({ }, }); +const Thought = mongoose.model("Thought", thoughtSchema); + Thought.deleteMany().then(() => { new Thought({ name: "Alfons", From bbfeac86d29a563dcbe40b7d2714bffbd5d5e0b8 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Tue, 3 Jun 2025 11:19:56 +0200 Subject: [PATCH 12/41] connecting the server --- server.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server.js b/server.js index edbe5c1..9f2c540 100644 --- a/server.js +++ b/server.js @@ -16,7 +16,7 @@ app.use(express.json()); const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts"; mongoose.connect(mongoUrl); -// CHECK THIS +// DB CONNECTION (CHECK THIS!) const thoughtSchema = new mongoose.Schema({ id: Number, message: String, @@ -30,19 +30,21 @@ const thoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", thoughtSchema); -Thought.deleteMany().then(() => { - new Thought({ - name: "Alfons", - age: 2, - isFurry: true, - }).save(); -}); +if ((process.env.RESET_DB = "true")) { + const seedDatabase = async () => { + await Thought.deleteMany({}); + data.forEach((thought) => { + new Thought(thought).save(); + }); + }; + seedDatabase(); +} // API DOCUMENTATION // app.get("/", (req, res) => { - Thought.find().then((thoughts) => { - res.json(thoughts); - }); + // Thought.find().then((thoughts) => { + // res.json(thoughts); + // }); const endpoints = listEndpoints(app); res.json({ From 9fbbbfb25940d63408442e635481e6f17c274074 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Tue, 3 Jun 2025 11:25:02 +0200 Subject: [PATCH 13/41] filtering --- server.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 9f2c540..0f07c74 100644 --- a/server.js +++ b/server.js @@ -22,7 +22,7 @@ const thoughtSchema = new mongoose.Schema({ message: String, hearts: Number, likedBy: [String], - created: { + createdAt: { type: Date, default: Date.now, }, @@ -55,10 +55,10 @@ app.get("/", (req, res) => { // ROUTES AND ENDPOINTS // // get all thoughts -app.get("/thoughts", (req, res) => { +app.get("/thoughts", async (req, res) => { const { category, sortBy, page = 1, limit = 10 } = req.query; - let filteredThoughts = data; + let filteredThoughts = await Thought.find(); if (category) { filteredThoughts = filteredThoughts.filter( From 67e425f24b7b36a003204eef86310ebfd0f51ac2 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Tue, 3 Jun 2025 11:36:23 +0200 Subject: [PATCH 14/41] error handling --- server.js | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/server.js b/server.js index 0f07c74..eabafac 100644 --- a/server.js +++ b/server.js @@ -58,19 +58,10 @@ app.get("/", (req, res) => { app.get("/thoughts", async (req, res) => { const { category, sortBy, page = 1, limit = 10 } = req.query; - let filteredThoughts = await Thought.find(); - - if (category) { - filteredThoughts = filteredThoughts.filter( - (item) => item.category.toLowerCase() === category.toLowerCase() - ); - } + // why do this??? + // const query = {} - if (sortBy === "date") { - filteredThoughts = filteredThoughts.sort( - (a, b) => new Date(b.createdAt) - new Date(a.createdAt) - ); - } + let filteredThoughts = await Thought.find(); const start = (page - 1) * limit; const end = start + +limit; @@ -83,6 +74,40 @@ app.get("/thoughts", async (req, res) => { total: filteredThoughts.length, thoughts: paginatedThoughts, }); + + // error handling + try { + if (filteredThoughts === 0) { + res.status(404).json({ + success: false, + response: [], + message: "No thoughts found for that query. Please try another one.", + }); + } + res.status(200).json({ + success: true, + response: filteredThoughts, + message: "Successful query.", + }); + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Something went wrong. Please check back later.", + }); + } + + if (category) { + filteredThoughts = filteredThoughts.filter( + (item) => item.category.toLowerCase() === category.toLowerCase() + ); + } + + if (sortBy === "date") { + filteredThoughts = filteredThoughts.sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt) + ); + } }); // get one thought From 5eb3668e46bb856bcf57312427d5d433c5efeaab Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 4 Jun 2025 10:18:36 +0200 Subject: [PATCH 15/41] changing the seeding function and repeated json calls --- server.js | 152 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 68 deletions(-) diff --git a/server.js b/server.js index eabafac..4182493 100644 --- a/server.js +++ b/server.js @@ -12,16 +12,18 @@ const app = express(); app.use(cors()); app.use(express.json()); -// connect to database +// MONGO DB CONNECTION // const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts"; mongoose.connect(mongoUrl); +mongoose.connection.on("error", (err) => console.error("MongoDB error:", err)); -// DB CONNECTION (CHECK THIS!) +// SCHEMA // const thoughtSchema = new mongoose.Schema({ id: Number, message: String, hearts: Number, likedBy: [String], + category: String, createdAt: { type: Date, default: Date.now, @@ -30,109 +32,123 @@ const thoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", thoughtSchema); -if ((process.env.RESET_DB = "true")) { - const seedDatabase = async () => { - await Thought.deleteMany({}); - data.forEach((thought) => { - new Thought(thought).save(); - }); - }; - seedDatabase(); -} +// Seed database +const seedDatabase = async () => { + await Thought.deleteMany({}); + data.forEach((thought) => { + new Thought(thought).save(); + }); +}; +seedDatabase(); // API DOCUMENTATION // app.get("/", (req, res) => { - // Thought.find().then((thoughts) => { - // res.json(thoughts); - // }); - const endpoints = listEndpoints(app); res.json({ - message: "Welcome to the Happy Thougts API", + message: "Welcome to the Happy Thoughts API", endpoints: endpoints, }); }); -// ROUTES AND ENDPOINTS // -// get all thoughts +// GET ALL THOUGHTS app.get("/thoughts", async (req, res) => { const { category, sortBy, page = 1, limit = 10 } = req.query; - // why do this??? - // const query = {} - - let filteredThoughts = await Thought.find(); + try { + let thoughts = await Thought.find(); - const start = (page - 1) * limit; - const end = start + +limit; + // Filter by category + if (category) { + thoughts = thoughts.filter( + (item) => item.category?.toLowerCase() === category.toLowerCase() + ); + } - const paginatedThoughts = filteredThoughts.slice(start, end); + // Sort by date + if (sortBy === "date") { + thoughts = thoughts.sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt) + ); + } - res.json({ - page: +page, - limit: +limit, - total: filteredThoughts.length, - thoughts: paginatedThoughts, - }); + // Paginate + const start = (page - 1) * limit; + const end = start + +limit; + const paginatedThoughts = thoughts.slice(start, end); - // error handling - try { - if (filteredThoughts === 0) { - res.status(404).json({ + if (paginatedThoughts.length === 0) { + return res.status(404).json({ success: false, + message: "No thoughts found for that query.", response: [], - message: "No thoughts found for that query. Please try another one.", }); } + res.status(200).json({ success: true, - response: filteredThoughts, - message: "Successful query.", + message: "Thoughts retrieved successfully.", + page: +page, + limit: +limit, + total: thoughts.length, + response: paginatedThoughts, }); } catch (error) { res.status(500).json({ success: false, + message: "Error fetching thoughts.", response: error, - message: "Something went wrong. Please check back later.", }); } - - if (category) { - filteredThoughts = filteredThoughts.filter( - (item) => item.category.toLowerCase() === category.toLowerCase() - ); - } - - if (sortBy === "date") { - filteredThoughts = filteredThoughts.sort( - (a, b) => new Date(b.createdAt) - new Date(a.createdAt) - ); - } }); -// get one thought -app.get("/thoughts/:id", (req, res) => { - const { id } = req.params; - const thought = data.find((item) => item.id === +id); +// GET ONE THOUGHT BY ID +app.get("/thoughts/:id", async (req, res) => { + try { + const { id } = req.params; + const thought = await Thought.findOne({ id: +id }); - if (!thought) { - return res.status(404).json({ error: "Thought not found" }); - } + if (!thought) { + return res.status(404).json({ + success: false, + message: "Thought not found.", + }); + } - res.json(thought); + res.status(200).json({ + success: true, + message: "Thought found.", + response: thought, + }); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error finding thought.", + response: error, + }); + } }); -// get liked thoughts -app.get("/thoughts/liked/:clientId", (req, res) => { - const { clientId } = req.params; - const likedThoughts = data.filter((thought) => - thought.likedBy.includes(clientId) - ); +// GET LIKED THOUGHTS FOR A USER +app.get("/thoughts/liked/:clientId", async (req, res) => { + try { + const { clientId } = req.params; + const likedThoughts = await Thought.find({ likedBy: clientId }); - res.json(likedThoughts); + res.status(200).json({ + success: true, + message: "Liked thoughts retrieved.", + response: likedThoughts, + }); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error retrieving liked thoughts.", + response: error, + }); + } }); -// cant do these yet i think (maybe next week?) +// PLACEHOLDER ROUTES // app.delete("/thoughts:id", (req, res) => res.send("placeholder")); app.post("/thoughts", (req, res) => res.send("placeholder")); @@ -141,7 +157,7 @@ app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); app.delete("/thoughts/:id/like", (req, res) => res.send("placeholder")); -// SERVER START // +// START SERVER // app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From 34eb57d3401185fbf341e1d1d753b4320773540b Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 4 Jun 2025 10:45:40 +0200 Subject: [PATCH 16/41] adding post request and adding more to the schema --- server.js | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/server.js b/server.js index 4182493..9a066a0 100644 --- a/server.js +++ b/server.js @@ -19,15 +19,12 @@ mongoose.connection.on("error", (err) => console.error("MongoDB error:", err)); // SCHEMA // const thoughtSchema = new mongoose.Schema({ - id: Number, - message: String, - hearts: Number, - likedBy: [String], - category: String, - createdAt: { - type: Date, - default: Date.now, - }, + id: { type: Number, default: Date.now }, // ok! + message: { type: String, required: true }, + hearts: { type: Number, default: 0 }, + likedBy: { type: [String], default: [] }, + category: { type: String, default: "General" }, + createdAt: { type: Date, default: Date.now }, }); const Thought = mongoose.model("Thought", thoughtSchema); @@ -148,11 +145,30 @@ app.get("/thoughts/liked/:clientId", async (req, res) => { } }); +// POST THOUGHT (check what we are sending) +app.post("/thoughts", async (req, res) => { + const { message, category } = req.body; + + try { + const newThought = await new Thought({ message, category }).save(); + + res.status(200).json({ + success: true, + response: newThought, + message: "Thought created successfully.", + }); + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Couldn't create thought.", + }); + } +}); + // PLACEHOLDER ROUTES // app.delete("/thoughts:id", (req, res) => res.send("placeholder")); -app.post("/thoughts", (req, res) => res.send("placeholder")); - app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); app.delete("/thoughts/:id/like", (req, res) => res.send("placeholder")); From 07d8500458f2a54c3c7335d22a8ec8b3bff7177f Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 4 Jun 2025 11:25:56 +0200 Subject: [PATCH 17/41] setting up the delete thought endpoint --- server.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 9a066a0..84b91b1 100644 --- a/server.js +++ b/server.js @@ -166,9 +166,35 @@ app.post("/thoughts", async (req, res) => { } }); -// PLACEHOLDER ROUTES // -app.delete("/thoughts:id", (req, res) => res.send("placeholder")); +// DELETE THOUGHT +app.delete("/thoughts:id", async (req, res) => { + const { id } = req.params; + + try { + const thought = await Thought.findByIdAndDelete(id); + + if (!thought) { + res.status(404).json({ + success: false, + response: null, + message: "Thought could not be found. Can't delete,", + }); + } + res.status(200).json({ + success: true, + response: thought, + message: "Thought successfully deleted.", + }); + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Couldn't delete thought.", + }); + } +}); +// PLACEHOLDER ROUTES // app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); app.delete("/thoughts/:id/like", (req, res) => res.send("placeholder")); From 4e88d61d1d9b4550f4001da35564318725f67a49 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 4 Jun 2025 11:57:07 +0200 Subject: [PATCH 18/41] setting up a patch endpoint --- server.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 84b91b1..b9f3672 100644 --- a/server.js +++ b/server.js @@ -20,7 +20,7 @@ mongoose.connection.on("error", (err) => console.error("MongoDB error:", err)); // SCHEMA // const thoughtSchema = new mongoose.Schema({ id: { type: Number, default: Date.now }, // ok! - message: { type: String, required: true }, + message: { type: String, required: true, minlength: 3 }, hearts: { type: Number, default: 0 }, likedBy: { type: [String], default: [] }, category: { type: String, default: "General" }, @@ -194,6 +194,41 @@ app.delete("/thoughts:id", async (req, res) => { } }); +// PATCH A THOUGHT +app.patch("thoughts/:id", async (req, res) => { + const { id } = req.params; + const { newMessage } = req.body; + + try { + const thought = await Thought.findByIdAndUpdate( + id, + { + message: newMessage, + }, + { new: true, runValidators: true } + ); + + if (!thought) { + return res.status(404).json({ + success: false, + response: null, + messsage: "Thought couldn't be found.", + }); + } + res.status(200).json({ + success: true, + response: thought, + message: "Thought updated successfully.", + }); + } catch (error) { + res.status(500).json({ + success: false, + response: error, + message: "Thought unable to be updated.", + }); + } +}); + // PLACEHOLDER ROUTES // app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); From 20f9995c01733e99192dea78624a5636ae4c342c Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Tue, 10 Jun 2025 15:29:37 +0200 Subject: [PATCH 19/41] removing unneeded notes --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index b9f3672..4efe19d 100644 --- a/server.js +++ b/server.js @@ -19,7 +19,7 @@ mongoose.connection.on("error", (err) => console.error("MongoDB error:", err)); // SCHEMA // const thoughtSchema = new mongoose.Schema({ - id: { type: Number, default: Date.now }, // ok! + id: { type: Number, default: Date.now }, message: { type: String, required: true, minlength: 3 }, hearts: { type: Number, default: 0 }, likedBy: { type: [String], default: [] }, From 6e7c2571d89b29b5a60a5db2816a44b8f7b3b7ea Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 11:43:57 +0200 Subject: [PATCH 20/41] fixing some typos --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 4efe19d..28cc8a5 100644 --- a/server.js +++ b/server.js @@ -167,7 +167,7 @@ app.post("/thoughts", async (req, res) => { }); // DELETE THOUGHT -app.delete("/thoughts:id", async (req, res) => { +app.delete("/thoughts/:id", async (req, res) => { const { id } = req.params; try { @@ -195,7 +195,7 @@ app.delete("/thoughts:id", async (req, res) => { }); // PATCH A THOUGHT -app.patch("thoughts/:id", async (req, res) => { +app.patch("/thoughts/:id", async (req, res) => { const { id } = req.params; const { newMessage } = req.body; From 2d373692e423f54f578a7a2a4571ba28e2b38adf Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 14:06:23 +0200 Subject: [PATCH 21/41] adding a user model --- models/User.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 models/User.js diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..bde62e1 --- /dev/null +++ b/models/User.js @@ -0,0 +1,27 @@ +import bcrypt from "bcrypt"; +import mongoose from "mongoose"; + +const userSchema = new mongoose.Schema({ + username: { type: String, required: true, unique: true, minlength: 3 }, + email: { type: String, required: true, unique: true, lowercase: true }, + password: { type: String, required: true, minlength: 6 }, +}); + +userSchema.pre("save", async function (next) { + if (!this.isModified("password")) return next(); + + try { + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); + next(); + } catch (err) { + next(err); + } +}); + +userSchema.methods.comparePassword = async function (candidatePassword) { + return bcrypt.compare(candidatePassword, this.password); +}; + +const User = mongoose.model("User", userSchema); +export default User; From 14a94ae53cc7119cda506082cf3668ec75bdcc84 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 14:06:57 +0200 Subject: [PATCH 22/41] fixing --- models/User.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/User.js b/models/User.js index bde62e1..b63cc02 100644 --- a/models/User.js +++ b/models/User.js @@ -14,8 +14,8 @@ userSchema.pre("save", async function (next) { const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); - } catch (err) { - next(err); + } catch (error) { + next(error); } }); From 7d16c70733cf0543c7e62f670fec7cdda529ee08 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 14:08:11 +0200 Subject: [PATCH 23/41] setting up authorisation routes --- routes/auth.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 routes/auth.js diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..63971e2 --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,73 @@ +import express from "express"; +import jwt from "jsonwebtoken"; + +import User from "../models/User.js"; + +const router = express.Router(); +const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_here"; // replace with env variable + +// signing up as a user +router.post("/signup", async (req, res) => { + const { username, email, password } = req.body; + + try { + const existingUser = await User.findOne({ $or: [{ email }, { username }] }); + if (existingUser) { + return res + .status(400) + .json({ success: false, message: "Username or email already exists." }); + } + + const newUser = new User({ username, email, password }); + await newUser.save(); + + const token = jwt.sign( + { id: newUser._id, username: newUser.username }, + JWT_SECRET, + { + expiresIn: "7d", + } + ); + + res.status(201).json({ success: true, message: "User created", token }); + } catch (error) { + res + .status(500) + .json({ success: false, message: "Error creating user", error }); + } +}); + +// login +router.post("/login", async (req, res) => { + const { email, password } = req.body; + + try { + const user = await User.findOne({ email }); + if (!user) { + return res + .status(401) + .json({ success: false, message: "Invalid email or password" }); + } + + const isMatch = await user.comparePassword(password); + if (!isMatch) { + return res + .status(401) + .json({ success: false, message: "Invalid email or password" }); + } + + const token = jwt.sign( + { id: user._id, username: user.username }, + JWT_SECRET, + { + expiresIn: "7d", + } + ); + + res.status(200).json({ success: true, message: "Logged in", token }); + } catch (error) { + res.status(500).json({ success: false, message: "Login error", error }); + } +}); + +export default router; From 2c5f5df22dcdae27b4d6e3b656c5ddae23b70d64 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 14:09:11 +0200 Subject: [PATCH 24/41] adding middleware --- middleware/auth.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 middleware/auth.js diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..9bb4442 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,22 @@ +import jwt from "jsonwebtoken"; + +const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_here"; + +export const authenticate = (req, res, next) => { + const authHeader = req.headers.authorization; + + if (!authHeader) + return res.status(401).json({ message: "Authorization header missing" }); + + const token = authHeader.split(" ")[1]; // Bearer TOKEN + + if (!token) return res.status(401).json({ message: "Token missing" }); + + try { + const decoded = jwt.verify(token, JWT_SECRET); + req.user = decoded; + next(); + } catch (error) { + res.status(401).json({ message: "Invalid or expired token" }); + } +}; From 37323daf51c1957bbb029a48b45e19a0345fc500 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 14:11:05 +0200 Subject: [PATCH 25/41] updating server for auth routes and middleware --- server.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/server.js b/server.js index 28cc8a5..37283d1 100644 --- a/server.js +++ b/server.js @@ -11,6 +11,7 @@ const app = express(); // MIDDLEWARES // app.use(cors()); app.use(express.json()); +app.use("/auth", authRoutes); // MONGO DB CONNECTION // const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts"; @@ -146,7 +147,7 @@ app.get("/thoughts/liked/:clientId", async (req, res) => { }); // POST THOUGHT (check what we are sending) -app.post("/thoughts", async (req, res) => { +app.post("/thoughts", authenticate, async (req, res) => { const { message, category } = req.body; try { @@ -167,7 +168,7 @@ app.post("/thoughts", async (req, res) => { }); // DELETE THOUGHT -app.delete("/thoughts/:id", async (req, res) => { +app.delete("/thoughts/:id", authenticate, async (req, res) => { const { id } = req.params; try { @@ -195,7 +196,7 @@ app.delete("/thoughts/:id", async (req, res) => { }); // PATCH A THOUGHT -app.patch("/thoughts/:id", async (req, res) => { +app.patch("/thoughts/:id", authenticate, async (req, res) => { const { id } = req.params; const { newMessage } = req.body; @@ -230,9 +231,13 @@ app.patch("/thoughts/:id", async (req, res) => { }); // PLACEHOLDER ROUTES // -app.post("/thoughts/:id/like", (req, res) => res.send("placeholder")); +app.post("/thoughts/:id/like", authenticate, (req, res) => + res.send("placeholder") +); -app.delete("/thoughts/:id/like", (req, res) => res.send("placeholder")); +app.delete("/thoughts/:id/like", authenticate, (req, res) => + res.send("placeholder") +); // START SERVER // app.listen(port, () => { From c887becbfed7f376a235f80060e071fca2cef4b8 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 15:09:27 +0200 Subject: [PATCH 26/41] routes for liking and unlikeing based on logged in user and authentication --- server.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/server.js b/server.js index 37283d1..37552b7 100644 --- a/server.js +++ b/server.js @@ -230,14 +230,68 @@ app.patch("/thoughts/:id", authenticate, async (req, res) => { } }); -// PLACEHOLDER ROUTES // -app.post("/thoughts/:id/like", authenticate, (req, res) => - res.send("placeholder") -); - -app.delete("/thoughts/:id/like", authenticate, (req, res) => - res.send("placeholder") -); +// LIKE +app.post("/thoughts/:id/like", authenticate, async (req, res) => { + try { + const thought = await Thought.findById(req.params.id); + + if (!thought) { + return res + .status(404) + .json({ success: false, message: "Thought not found" }); + } + + if (thought.likedBy.includes(req.user.id)) { + return res.status(400).json({ + success: false, + message: "You have already liked this thought", + }); + } + + thought.hearts += 1; + thought.likedBy.push(req.user.id); + await thought.save(); + + res.status(200).json({ success: true, message: "Thought liked", thought }); + } catch (error) { + res + .status(500) + .json({ success: false, message: "Error liking thought", error }); + } +}); + +// UNLIKE +app.delete("/thoughts/:id/like", authenticate, async (req, res) => { + try { + const thought = await Thought.findById(req.params.id); + + if (!thought) { + return res + .status(404) + .json({ success: false, message: "Thought not found" }); + } + + if (!thought.likedBy.includes(req.user.id)) { + return res + .status(400) + .json({ success: false, message: "You haven't liked this thought" }); + } + + thought.hearts = Math.max(0, thought.hearts - 1); + thought.likedBy = thought.likedBy.filter( + (userId) => userId !== req.user.id + ); + await thought.save(); + + res + .status(200) + .json({ success: true, message: "Thought unliked", thought }); + } catch (error) { + res + .status(500) + .json({ success: false, message: "Error unliking thought", error }); + } +}); // START SERVER // app.listen(port, () => { From a776933b33791674927755faffb25bb8a1180360 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 15:12:01 +0200 Subject: [PATCH 27/41] adding the authenticate import --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index 37552b7..78d8e67 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,4 @@ +import { authenticate } from "./middlewares/authenticate"; import cors from "cors"; import express from "express"; import listEndpoints from "express-list-endpoints"; From 888ca9cbe05dfb13bd8430f4465bc8d4e5af2795 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 15:15:12 +0200 Subject: [PATCH 28/41] making notes cohesive --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 78d8e67..2d7afb8 100644 --- a/server.js +++ b/server.js @@ -31,7 +31,7 @@ const thoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", thoughtSchema); -// Seed database +// SeSEED DTABASE const seedDatabase = async () => { await Thought.deleteMany({}); data.forEach((thought) => { From dcbc1a7d52607168eee1fba6d3866da2dfa35ac1 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 15:41:22 +0200 Subject: [PATCH 29/41] fixing imports and missing packages --- {middleware => middlewares}/auth.js | 0 package.json | 2 ++ server.js | 12 +++++------- 3 files changed, 7 insertions(+), 7 deletions(-) rename {middleware => middlewares}/auth.js (100%) diff --git a/middleware/auth.js b/middlewares/auth.js similarity index 100% rename from middleware/auth.js rename to middlewares/auth.js diff --git a/package.json b/package.json index 9c9d350..eb38449 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.15.1", "nodemon": "^3.1.10" } diff --git a/server.js b/server.js index 2d7afb8..7e6d41d 100644 --- a/server.js +++ b/server.js @@ -1,10 +1,11 @@ -import { authenticate } from "./middlewares/authenticate"; import cors from "cors"; import express from "express"; import listEndpoints from "express-list-endpoints"; import mongoose from "mongoose"; import data from "./data/data.json"; +import { authenticate } from "./middlewares/auth.js"; +import authRoutes from "./routes/auth.js"; const port = process.env.PORT || 8080; const app = express(); @@ -21,7 +22,6 @@ mongoose.connection.on("error", (err) => console.error("MongoDB error:", err)); // SCHEMA // const thoughtSchema = new mongoose.Schema({ - id: { type: Number, default: Date.now }, message: { type: String, required: true, minlength: 3 }, hearts: { type: Number, default: 0 }, likedBy: { type: [String], default: [] }, @@ -31,12 +31,10 @@ const thoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", thoughtSchema); -// SeSEED DTABASE +// SEED DTABASE const seedDatabase = async () => { await Thought.deleteMany({}); - data.forEach((thought) => { - new Thought(thought).save(); - }); + await Promise.all(data.map((thought) => new Thought(thought).save())); }; seedDatabase(); @@ -104,7 +102,7 @@ app.get("/thoughts", async (req, res) => { app.get("/thoughts/:id", async (req, res) => { try { const { id } = req.params; - const thought = await Thought.findOne({ id: +id }); + const thought = await Thought.findById(id); if (!thought) { return res.status(404).json({ From b85f82f7adc44f682794e564df7caedf7e73ae99 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Wed, 11 Jun 2025 16:00:37 +0200 Subject: [PATCH 30/41] adding environmental variables --- server.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.js b/server.js index 7e6d41d..d06d54b 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,5 @@ import cors from "cors"; +import dotenv from "dotenv"; import express from "express"; import listEndpoints from "express-list-endpoints"; import mongoose from "mongoose"; @@ -7,6 +8,8 @@ import data from "./data/data.json"; import { authenticate } from "./middlewares/auth.js"; import authRoutes from "./routes/auth.js"; +dotenv.config(); + const port = process.env.PORT || 8080; const app = express(); From dc226b1fce507d20657048e69350ec155e2f0e89 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Fri, 13 Jun 2025 10:01:06 +0200 Subject: [PATCH 31/41] fixing the secret key namin issues --- middlewares/auth.js | 2 +- routes/auth.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/middlewares/auth.js b/middlewares/auth.js index 9bb4442..17687de 100644 --- a/middlewares/auth.js +++ b/middlewares/auth.js @@ -8,7 +8,7 @@ export const authenticate = (req, res, next) => { if (!authHeader) return res.status(401).json({ message: "Authorization header missing" }); - const token = authHeader.split(" ")[1]; // Bearer TOKEN + const token = authHeader.split(" ")[1]; if (!token) return res.status(401).json({ message: "Token missing" }); diff --git a/routes/auth.js b/routes/auth.js index 63971e2..d20fb03 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -4,7 +4,7 @@ import jwt from "jsonwebtoken"; import User from "../models/User.js"; const router = express.Router(); -const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_here"; // replace with env variable +const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_here"; // signing up as a user router.post("/signup", async (req, res) => { From 745449e534091c9b9058bc1d9046d6083cc39233 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Fri, 13 Jun 2025 10:03:11 +0200 Subject: [PATCH 32/41] fixing the token names --- routes/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/auth.js b/routes/auth.js index d20fb03..f10f6a8 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -4,7 +4,7 @@ import jwt from "jsonwebtoken"; import User from "../models/User.js"; const router = express.Router(); -const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_here"; +const JWT_SECRET = process.env.JWT_SECRET; // signing up as a user router.post("/signup", async (req, res) => { From 145574990f005f78621da368abbbb4c88807a893 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Fri, 13 Jun 2025 10:05:17 +0200 Subject: [PATCH 33/41] fixing token names --- middlewares/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middlewares/auth.js b/middlewares/auth.js index 17687de..67006ec 100644 --- a/middlewares/auth.js +++ b/middlewares/auth.js @@ -1,6 +1,6 @@ import jwt from "jsonwebtoken"; -const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret_here"; +const JWT_SECRET = process.env.JWT_SECRET; export const authenticate = (req, res, next) => { const authHeader = req.headers.authorization; From 230ba893779e741df775e302bbce98673a0edc9c Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sun, 15 Jun 2025 10:05:28 +0200 Subject: [PATCH 34/41] fix env variable names and remove seeding --- server.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/server.js b/server.js index d06d54b..96cce3f 100644 --- a/server.js +++ b/server.js @@ -19,7 +19,7 @@ app.use(express.json()); app.use("/auth", authRoutes); // MONGO DB CONNECTION // -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts"; +const mongoUrl = process.env.MONGO_URL; mongoose.connect(mongoUrl); mongoose.connection.on("error", (err) => console.error("MongoDB error:", err)); @@ -34,13 +34,6 @@ const thoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", thoughtSchema); -// SEED DTABASE -const seedDatabase = async () => { - await Thought.deleteMany({}); - await Promise.all(data.map((thought) => new Thought(thought).save())); -}; -seedDatabase(); - // API DOCUMENTATION // app.get("/", (req, res) => { const endpoints = listEndpoints(app); From ffc5332afce90811ecbddd6ae1c18c1ecf23d87a Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Thu, 21 Aug 2025 11:25:12 +0200 Subject: [PATCH 35/41] sreamlining error handling --- server.js | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/server.js b/server.js index 96cce3f..4a66d71 100644 --- a/server.js +++ b/server.js @@ -77,7 +77,7 @@ app.get("/thoughts", async (req, res) => { }); } - res.status(200).json({ + return res.status(200).json({ success: true, message: "Thoughts retrieved successfully.", page: +page, @@ -86,7 +86,7 @@ app.get("/thoughts", async (req, res) => { response: paginatedThoughts, }); } catch (error) { - res.status(500).json({ + return res.status(500).json({ success: false, message: "Error fetching thoughts.", response: error, @@ -107,13 +107,13 @@ app.get("/thoughts/:id", async (req, res) => { }); } - res.status(200).json({ + return res.status(200).json({ success: true, message: "Thought found.", response: thought, }); } catch (error) { - res.status(500).json({ + return res.status(500).json({ success: false, message: "Error finding thought.", response: error, @@ -141,20 +141,20 @@ app.get("/thoughts/liked/:clientId", async (req, res) => { } }); -// POST THOUGHT (check what we are sending) +// POST THOUGHT app.post("/thoughts", authenticate, async (req, res) => { const { message, category } = req.body; try { const newThought = await new Thought({ message, category }).save(); - res.status(200).json({ + return res.status(200).json({ success: true, response: newThought, message: "Thought created successfully.", }); } catch (error) { - res.status(500).json({ + return res.status(500).json({ success: false, response: error, message: "Couldn't create thought.", @@ -170,19 +170,20 @@ app.delete("/thoughts/:id", authenticate, async (req, res) => { const thought = await Thought.findByIdAndDelete(id); if (!thought) { - res.status(404).json({ + return res.status(404).json({ success: false, response: null, - message: "Thought could not be found. Can't delete,", + message: "Thought could not be found. Can't delete.", }); } - res.status(200).json({ + + return res.status(200).json({ success: true, response: thought, message: "Thought successfully deleted.", }); } catch (error) { - res.status(500).json({ + return res.status(500).json({ success: false, response: error, message: "Couldn't delete thought.", @@ -198,9 +199,7 @@ app.patch("/thoughts/:id", authenticate, async (req, res) => { try { const thought = await Thought.findByIdAndUpdate( id, - { - message: newMessage, - }, + { message: newMessage }, { new: true, runValidators: true } ); @@ -208,16 +207,17 @@ app.patch("/thoughts/:id", authenticate, async (req, res) => { return res.status(404).json({ success: false, response: null, - messsage: "Thought couldn't be found.", + message: "Thought couldn't be found.", }); } - res.status(200).json({ + + return res.status(200).json({ success: true, response: thought, message: "Thought updated successfully.", }); } catch (error) { - res.status(500).json({ + return res.status(500).json({ success: false, response: error, message: "Thought unable to be updated.", @@ -225,7 +225,7 @@ app.patch("/thoughts/:id", authenticate, async (req, res) => { } }); -// LIKE +// LIKE A THOUGHT app.post("/thoughts/:id/like", authenticate, async (req, res) => { try { const thought = await Thought.findById(req.params.id); @@ -247,15 +247,17 @@ app.post("/thoughts/:id/like", authenticate, async (req, res) => { thought.likedBy.push(req.user.id); await thought.save(); - res.status(200).json({ success: true, message: "Thought liked", thought }); + return res + .status(200) + .json({ success: true, message: "Thought liked", thought }); } catch (error) { - res + return res .status(500) .json({ success: false, message: "Error liking thought", error }); } }); -// UNLIKE +// UNLIKE A THOUGHT app.delete("/thoughts/:id/like", authenticate, async (req, res) => { try { const thought = await Thought.findById(req.params.id); @@ -278,11 +280,11 @@ app.delete("/thoughts/:id/like", authenticate, async (req, res) => { ); await thought.save(); - res + return res .status(200) .json({ success: true, message: "Thought unliked", thought }); } catch (error) { - res + return res .status(500) .json({ success: false, message: "Error unliking thought", error }); } From f2ec628cb1b86eab471464da50d3d0f810bdb8e9 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sat, 18 Oct 2025 10:39:39 +0200 Subject: [PATCH 36/41] trying to fix authorisation headers missing issue that only shows up in live site --- server.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 4a66d71..5e2c851 100644 --- a/server.js +++ b/server.js @@ -8,13 +8,33 @@ import data from "./data/data.json"; import { authenticate } from "./middlewares/auth.js"; import authRoutes from "./routes/auth.js"; +const allowedOrigins = [ + "hhttp://localhost:5173", + "https://happy-thoughts-messaging-app.netlify.app/", +]; + dotenv.config(); const port = process.env.PORT || 8080; const app = express(); // MIDDLEWARES // -app.use(cors()); +// app.use(cors()); + +app.use( + cors({ + origin: (origin, callback) => { + if (!origin || allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error("Not allowed by CORS")); + } + }, + methods: ["GET", "POST", "PATCH", "DELETE"], + allowedHeaders: ["Content-Type", "Authorization"], + }) +); + app.use(express.json()); app.use("/auth", authRoutes); From e5e03cd10fd63b57310607423d98f38571c65077 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sat, 18 Oct 2025 10:46:44 +0200 Subject: [PATCH 37/41] typo --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 5e2c851..b440598 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,7 @@ import { authenticate } from "./middlewares/auth.js"; import authRoutes from "./routes/auth.js"; const allowedOrigins = [ - "hhttp://localhost:5173", + "http://localhost:5173", "https://happy-thoughts-messaging-app.netlify.app/", ]; From 8eb258186f07e9f04651dfa79e328941dfa47167 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sat, 18 Oct 2025 10:49:11 +0200 Subject: [PATCH 38/41] typo --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index b440598..cc56230 100644 --- a/server.js +++ b/server.js @@ -10,7 +10,7 @@ import authRoutes from "./routes/auth.js"; const allowedOrigins = [ "http://localhost:5173", - "https://happy-thoughts-messaging-app.netlify.app/", + "https://happy-thoughts-messaging-app.netlify.app", ]; dotenv.config(); From 4c4c0fe04c9529827e28a61b6d6e86a0d4b681b3 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sat, 18 Oct 2025 10:56:11 +0200 Subject: [PATCH 39/41] still trying to fix --- server.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index cc56230..a907198 100644 --- a/server.js +++ b/server.js @@ -20,7 +20,6 @@ const app = express(); // MIDDLEWARES // // app.use(cors()); - app.use( cors({ origin: (origin, callback) => { @@ -30,11 +29,14 @@ app.use( callback(new Error("Not allowed by CORS")); } }, - methods: ["GET", "POST", "PATCH", "DELETE"], + methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization"], + optionsSuccessStatus: 200, }) ); +app.options("*", cors()); + app.use(express.json()); app.use("/auth", authRoutes); From 5bbf12b922cf1025ffd197e328aa2de198f617a0 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sat, 18 Oct 2025 11:07:53 +0200 Subject: [PATCH 40/41] fix --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index a907198..78ebdbe 100644 --- a/server.js +++ b/server.js @@ -10,7 +10,7 @@ import authRoutes from "./routes/auth.js"; const allowedOrigins = [ "http://localhost:5173", - "https://happy-thoughts-messaging-app.netlify.app", + "https://happy-thoughts-messaging-app.netlify.app/*", ]; dotenv.config(); From 8b40232c9b08cbcd93f9a67e3f1760156c403a18 Mon Sep 17 00:00:00 2001 From: christina-baldwin Date: Sat, 18 Oct 2025 11:11:35 +0200 Subject: [PATCH 41/41] fix --- server.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 78ebdbe..73a936b 100644 --- a/server.js +++ b/server.js @@ -10,7 +10,7 @@ import authRoutes from "./routes/auth.js"; const allowedOrigins = [ "http://localhost:5173", - "https://happy-thoughts-messaging-app.netlify.app/*", + "https://happy-thoughts-messaging-app.netlify.app", ]; dotenv.config(); @@ -23,7 +23,12 @@ const app = express(); app.use( cors({ origin: (origin, callback) => { - if (!origin || allowedOrigins.includes(origin)) { + if (!origin) return callback(null, true); + + const allowed = allowedOrigins.map((o) => o.replace(/\/$/, "")); + const normalized = origin.replace(/\/$/, ""); + + if (allowed.includes(normalized)) { callback(null, true); } else { callback(new Error("Not allowed by CORS")); @@ -31,7 +36,8 @@ app.use( }, methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization"], - optionsSuccessStatus: 200, + credentials: true, + optionsSuccessStatus: 204, }) );