diff --git a/.gitignore b/.gitignore index a28d73d..daee295 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ postgres/db .env todo sqldumps/ +logs/ diff --git a/backend/src/controllers/recipeController.js b/backend/src/controllers/recipeController.js index 07de088..be9284e 100644 --- a/backend/src/controllers/recipeController.js +++ b/backend/src/controllers/recipeController.js @@ -78,7 +78,7 @@ exports.deleteRecipe = async (req, res) => { const id = parseInt(req.body.id, 10); try { await model.deleteRecipe(id); - res.status(204).send(); + res.json({ success: "true" }); } catch (error) { res.status(500).json({ msg: "Failed to delete recipe", diff --git a/backend/src/models/recipeModel.js b/backend/src/models/recipeModel.js index 743aa5f..9c93965 100644 --- a/backend/src/models/recipeModel.js +++ b/backend/src/models/recipeModel.js @@ -1,4 +1,6 @@ const { PrismaClient } = require("@prisma/client"); +const Logger = require("../utils/logger.js"); +const logger = new Logger(); class recipeModel { constructor() { @@ -21,6 +23,7 @@ class recipeModel { include: { recipeSteps: true, recipeIngredients: true }, }); if (!recipe) { + logger.warn(`recipe with id ${id} cannot be found`); return null; } const data = { @@ -41,7 +44,8 @@ class recipeModel { }; return data; } catch (err) { - console.error("Error finding recipe:", err); + console.log("Error finding recipe:", err); + logger.error("error finding recipe", err); throw new Error(err.message); } } @@ -77,9 +81,14 @@ class recipeModel { }, }); + logger.info("new recipe created", { + id: createdRecipe.id, + name: createdRecipe.name, + }); return createdRecipe; } catch (error) { console.log(error); + logger.error("error creating recipe", err); throw new Error("Failed to add recipe"); } } @@ -93,6 +102,7 @@ class recipeModel { return { message: "stars updated" }; } catch (err) { console.error("Error updating stars:", err); + logger.error("error setting stars", err); throw new Error(err.message); } } @@ -100,18 +110,23 @@ class recipeModel { async deleteRecipe(id) { try { await this.prisma.recipe_ingredients.deleteMany({ - where: { recipe_id: id }, // Ensure you have the right foreign key relation + where: { recipe_id: id }, }); await this.prisma.recipe_steps.deleteMany({ - where: { recipe_id: id }, // Ensure you have the right foreign key relation + where: { recipe_id: id }, }); const deletedRecipe = await this.prisma.recipes.delete({ where: { id }, }); + logger.info("recipe deleted", { + id: deletedRecipe.id, + name: deletedRecipe.name, + }); return { message: "Recipe deleted successfully" }; } catch (err) { console.error("Error deleting recipe:", err); + logger.error("error deleting recipe", err); throw new Error(err.message); } } diff --git a/backend/src/utils/logger.js b/backend/src/utils/logger.js new file mode 100644 index 0000000..6c2b66a --- /dev/null +++ b/backend/src/utils/logger.js @@ -0,0 +1,33 @@ +const fs = require("fs"); + +class Logger { + constructor(filePath) { + this.filePath = "/logs/app.log"; + } + + log(level, message, params) { + const logEntry = { + timestamp: new Date().toISOString(), + level: level, + message: message, + params: params, + }; + fs.appendFile(this.filePath, JSON.stringify(logEntry) + "\n", (err) => { + if (err) throw err; + }); + } + + info(message, params = {}) { + this.log("info", message, params); + } + + warn(message, params = {}) { + this.log("warn", message, params); + } + + error(message, params = {}) { + this.log("error", message, params); + } +} + +module.exports = Logger; diff --git a/backend/src/utils/logging.js b/backend/src/utils/logging.js deleted file mode 100644 index 65b3dba..0000000 --- a/backend/src/utils/logging.js +++ /dev/null @@ -1 +0,0 @@ -// todo diff --git a/docker-compose.yaml b/docker-compose.yaml index 26e5f54..ebcb296 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,6 +24,7 @@ services: - "${BACKEND_PORT}:3000" volumes: - ./backend:/usr/src/app + - ./logs:/logs environment: - NODE_ENV=${NODE_ENV} - DB_USER=${DB_USER} diff --git a/frontend/src/pages/RecipeIngredients.tsx b/frontend/src/pages/RecipeIngredients.tsx index bbdf549..fd290c3 100644 --- a/frontend/src/pages/RecipeIngredients.tsx +++ b/frontend/src/pages/RecipeIngredients.tsx @@ -1,9 +1,7 @@ import { getRecipeIngredients } from "../services/frontendApi.js"; import { useState, useEffect } from "react"; -import { type Ingredient } from "../types/Recipe.ts" function RecipeIngredients() { - const [recipeIngredients, setRecipeIngredients] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); @@ -13,36 +11,32 @@ function RecipeIngredients() { try { const recipeIngredients = await getRecipeIngredients(); setRecipeIngredients(recipeIngredients); - console.log(recipeIngredients) + console.log(recipeIngredients); } catch (err) { console.log(err); setError("Failed to load recipe ingredients..."); - console.log(error) + console.log(error); } finally { setLoading(false); } }; loadRecipeIngredients(); }, []); - console.log(recipeIngredients) + console.log(recipeIngredients); return ( - // should this be a string[]? only if we are only returning raw. otherwise i will need to type and return the ingredient object. This template shoudl work for steps though, so maybe setting that up is a good first step -
+
{loading ? (
Loading...
) : (
- {recipeIngredients.map(ing => ( -
  • - {ing.raw} -
  • + {recipeIngredients.map((ing) => ( +
  • {ing.raw}
  • ))}
    )}
    - ) - + ); } -export default RecipeIngredients +export default RecipeIngredients; diff --git a/frontend/src/pages/RecipePage.tsx b/frontend/src/pages/RecipePage.tsx index 97ee6c4..4f42de4 100644 --- a/frontend/src/pages/RecipePage.tsx +++ b/frontend/src/pages/RecipePage.tsx @@ -1,17 +1,21 @@ import { useParams, useNavigate, Link } from "react-router-dom"; import { useState, useEffect } from "react"; -import { getRecipeById, deleteRecipe, setDBStars } from "../services/frontendApi.js"; -import { type Recipe, type Ingredient } from "../types/Recipe" -import Modal from '../components/Modal.tsx' -import DemoModal from '../components/DemoModal.tsx' -import StarRating from "../components/StarRating.tsx" -import TimeDisplay from '../components/TimeDisplay.tsx' +import { + getRecipeById, + deleteRecipe, + setDBStars, +} from "../services/frontendApi.js"; +import { type Recipe } from "../types/Recipe"; +import Modal from "../components/Modal.tsx"; +import DemoModal from "../components/DemoModal.tsx"; +import StarRating from "../components/StarRating.tsx"; +import TimeDisplay from "../components/TimeDisplay.tsx"; function RecipePage() { const [recipe, setRecipe] = useState({ details: {}, ingredients: [], - steps: [] + steps: [], }); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); @@ -20,16 +24,21 @@ function RecipePage() { const [showConfirmModal, setShowConfirmModal] = useState(false); const [showDemoModal, setShowDemoModal] = useState(false); const { id } = useParams(); - const isWebSource = recipe && recipe.details && recipe.details.author - ? /http|com/.test(recipe.details.author) //etc - : false; + const isWebSource = + recipe && recipe.details && recipe.details.author + ? /http|com/.test(recipe.details.author) //etc + : false; const navigate = useNavigate(); - const openModal = () => { setShowConfirmModal(true) }; - const closeModal = () => { setShowConfirmModal(false) }; + const openModal = () => { + setShowConfirmModal(true); + }; + const closeModal = () => { + setShowConfirmModal(false); + }; const confirmDelete = () => { - if (process.env.NODE_ENV === 'demo') { + if (process.env.NODE_ENV === "demo") { closeModal(); setShowDemoModal(true); } else { @@ -43,13 +52,13 @@ function RecipePage() { try { const recipe = await getRecipeById(id); if (!recipe.details) { - setError("Sorry, this recipe no longer exists") + setError("Sorry, this recipe no longer exists"); } else { setRecipe(recipe); - setStars(recipe.details?.stars ?? 0) + setStars(recipe.details?.stars ?? 0); setInitialStars(recipe.details?.stars ?? 0); - if (process.env.NODE_ENV === 'dev') { - console.log(recipe) + if (process.env.NODE_ENV === "dev") { + console.log(recipe); } } } catch (error) { @@ -77,38 +86,56 @@ function RecipePage() { const handleDelete = async (id: number | void) => { try { await deleteRecipe(id); - navigate('/') + navigate("/"); } catch (error) { console.error("Error deleting recipe:", error); } }; return (
    - - {loading ? (
    Loading...
    ) : error ? (
    {error}
    - + Return to Cookbook
    ) : ( -
    - -

    {recipe.details.name}

    - + +

    + {recipe.details.name} +

    +
    -

    {recipe.details.cuisine}

    -

    prep: | cook:

    +

    + {recipe.details.cuisine} +

    +

    + prep: {" "} + | cook:{" "} + +

    @@ -121,7 +148,7 @@ function RecipePage() { {recipe.ingredients.map((ingredient: Ingredient, index) => (
  • - {ingredient.raw} + {ingredient}
  • ))} @@ -132,14 +159,20 @@ function RecipePage() { Instructions:
      - {recipe.steps && Object.keys(recipe.steps || {}).map((stepNumber) => ( -
    1. - - {recipe.steps[parseInt(stepNumber)].step_number} - - {recipe.steps[parseInt(stepNumber)].instruction} -
    2. - ))} + {recipe.steps && + Object.keys(recipe.steps || {}).map((stepNumber) => ( +
    3. + + {recipe.steps[parseInt(stepNumber)].step_number} + + + {recipe.steps[parseInt(stepNumber)].instruction} + +
    4. + ))}
    @@ -152,13 +185,15 @@ function RecipePage() { From the kitchen of {recipe.details.author} )} - setStars(newRating)} /> + setStars(newRating)} + />
    - ) - } + )} setShowDemoModal(false)} /> )} - + ); } diff --git a/frontend/src/types/Recipe.ts b/frontend/src/types/Recipe.ts index 97298aa..eedf2cb 100644 --- a/frontend/src/types/Recipe.ts +++ b/frontend/src/types/Recipe.ts @@ -4,13 +4,6 @@ interface Step { instruction: string; } -interface Ingredient { - id?: number; - name?: string; - quantity?: number; - unit?: string; - raw?: string; -} interface Recipe { details: { id?: number; @@ -20,8 +13,8 @@ interface Recipe { cuisine?: string; prep_minutes?: number; cook_minutes?: number; - }, - ingredients: Ingredient[], + }; + ingredients: string[]; steps: Step[]; } // smaller Recipe type returned by backend at /recipes for all @@ -34,5 +27,4 @@ interface RecipeSmall { prep_minutes: number; } - -export type { Recipe, Ingredient, Step, RecipeSmall } +export type { Recipe, Step, RecipeSmall };