add typescript to backend
This commit is contained in:
parent
0a41568a2e
commit
30b28056de
10 changed files with 221 additions and 67 deletions
|
@ -4,11 +4,10 @@ WORKDIR /usr/src/app
|
|||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
RUN if [ "$NODE_ENV" = "dev" ]; then npm install; else npm install --only=production; fi
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# CMD ["npm", "run", "dev"]
|
||||
CMD npm run $NODE_ENV
|
||||
|
|
109
backend/package-lock.json
generated
109
backend/package-lock.json
generated
|
@ -21,6 +21,8 @@
|
|||
"typescript": "^5.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"nodemon": "^3.1.10"
|
||||
}
|
||||
},
|
||||
|
@ -170,6 +172,76 @@
|
|||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
||||
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
|
||||
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^5.0.0",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-serve-static-core": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz",
|
||||
"integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/qs": "*",
|
||||
"@types/range-parser": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
|
||||
|
@ -179,6 +251,43 @@
|
|||
"undici-types": "~7.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "0.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
|
||||
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mime": "^1",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/serve-static": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
|
||||
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
"@types/node": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon ./src/index.js",
|
||||
"production": "node ./src/index.js"
|
||||
"production": "tsc && node ./dist/index.js",
|
||||
"demo": "tsc && node ./dist/index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
@ -12,17 +13,19 @@
|
|||
"description": "",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.14.0",
|
||||
"@types/node": "^24.2.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.0.1",
|
||||
"express": "^5.1.0",
|
||||
"knex": "^3.1.0",
|
||||
"pg": "^8.16.3",
|
||||
"prisma": "^6.14.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
"prisma": "^6.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"nodemon": "^3.1.10"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
const RecipeModel = require("../models/recipeModel");
|
||||
import { Request, Response } from "express";
|
||||
import RecipeModel from "../models/recipeModel";
|
||||
|
||||
const model = new RecipeModel();
|
||||
|
||||
exports.test = async (req, res) => {
|
||||
export const test = async (req: Request, res: Response): Promise<void> => {
|
||||
console.log("test");
|
||||
res.json({ env: process.env.NODE_ENV });
|
||||
};
|
||||
|
||||
exports.getAllRecipes = async (req, res) => {
|
||||
export const getAllRecipes = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const recipes = await model.getAllRecipes();
|
||||
res.json(recipes);
|
||||
|
@ -14,12 +19,15 @@ exports.getAllRecipes = async (req, res) => {
|
|||
res.status(500).json({
|
||||
msg: "Failed to fetch all recipes",
|
||||
source: "recipeController",
|
||||
error: error.message,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.getRecipeById = async (req, res) => {
|
||||
export const getRecipeById = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
): Promise<void> => {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
try {
|
||||
const recipe = await model.findById(id);
|
||||
|
@ -32,12 +40,12 @@ exports.getRecipeById = async (req, res) => {
|
|||
res.status(500).json({
|
||||
msg: "Failed to fetch recipe",
|
||||
source: "recipeController",
|
||||
error: error.message,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.addRecipe = async (req, res) => {
|
||||
export const addRecipe = async (req: Request, res: Response): Promise<void> => {
|
||||
if (process.env.NODE_ENV === "demo") {
|
||||
return;
|
||||
}
|
||||
|
@ -48,30 +56,33 @@ exports.addRecipe = async (req, res) => {
|
|||
res.status(500).json({
|
||||
msg: "Failed to add recipe",
|
||||
source: "recipeController",
|
||||
error: error.message,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.setStars = async (req, res) => {
|
||||
export const setStars = async (req: Request, res: Response): Promise<void> => {
|
||||
if (process.env.NODE_ENV === "demo") {
|
||||
return;
|
||||
}
|
||||
const id = parseInt(req.body.id, 10);
|
||||
const stars = parseInt(req.body.stars, 10);
|
||||
try {
|
||||
const createdRecipe = await model.setStars(id, stars);
|
||||
res.status(202).json(createdRecipe);
|
||||
const updatedRecipe = await model.setStars(id, stars);
|
||||
res.status(202).json(updatedRecipe);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
msg: "Failed to set stars",
|
||||
source: "recipeController",
|
||||
error: error.message,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteRecipe = async (req, res) => {
|
||||
export const deleteRecipe = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
): Promise<void> => {
|
||||
if (process.env.NODE_ENV === "demo") {
|
||||
return;
|
||||
}
|
||||
|
@ -83,7 +94,16 @@ exports.deleteRecipe = async (req, res) => {
|
|||
res.status(500).json({
|
||||
msg: "Failed to delete recipe",
|
||||
source: "recipeController",
|
||||
error: error.message,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
test,
|
||||
getAllRecipes,
|
||||
getRecipeById,
|
||||
addRecipe,
|
||||
setStars,
|
||||
deleteRecipe,
|
||||
};
|
|
@ -1,18 +1,17 @@
|
|||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const appRoutes = require("./routes/appRoutes");
|
||||
import express, { Express } from "express";
|
||||
import cors from "cors";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import appRoutes from "./routes/appRoutes";
|
||||
|
||||
const app = express();
|
||||
const app: Express = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function setupMiddleware(app) {
|
||||
function setupMiddleware(app: Express) {
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use("/api", appRoutes);
|
||||
}
|
||||
|
||||
setupMiddleware(app);
|
||||
|
||||
// Start server
|
|
@ -1,29 +1,32 @@
|
|||
const { PrismaClient } = require("@prisma/client");
|
||||
const Logger = require("../utils/logger.js");
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import Logger from "../utils/logger";
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
class recipeModel {
|
||||
class RecipeModel {
|
||||
private prisma: PrismaClient;
|
||||
|
||||
constructor() {
|
||||
this.prisma = new PrismaClient();
|
||||
}
|
||||
|
||||
async getAllRecipes() {
|
||||
async getAllRecipes(): Promise<any[]> {
|
||||
try {
|
||||
return await this.prisma.recipes.findMany();
|
||||
} catch (err) {
|
||||
console.error("Error fetching all recipies:", err);
|
||||
throw new Error(err.message);
|
||||
console.error("Error fetching all recipes:", err);
|
||||
throw new Error(err instanceof Error ? err.message : "Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
async findById(id: number): Promise<any | null> {
|
||||
try {
|
||||
const recipe = await this.prisma.recipes.findUnique({
|
||||
where: { id },
|
||||
include: { recipeSteps: true, recipeIngredients: true },
|
||||
});
|
||||
if (!recipe) {
|
||||
logger.warn(`recipe with id ${id} cannot be found`);
|
||||
logger.warn(`Recipe with id ${id} cannot be found`);
|
||||
return null;
|
||||
}
|
||||
const data = {
|
||||
|
@ -37,7 +40,7 @@ class recipeModel {
|
|||
cook_minutes: recipe.cook_minutes,
|
||||
},
|
||||
ingredients: recipe.recipeIngredients.map((ing) => ing.raw),
|
||||
steps: recipe.recipeSteps.map((step, idx) => ({
|
||||
steps: recipe.recipeSteps.map((step) => ({
|
||||
step_number: step.step_number,
|
||||
instruction: step.instruction,
|
||||
})),
|
||||
|
@ -45,11 +48,23 @@ class recipeModel {
|
|||
return data;
|
||||
} catch (err) {
|
||||
console.log("Error finding recipe:", err);
|
||||
logger.error("error finding recipe", err);
|
||||
throw new Error(err.message);
|
||||
logger.error("Error finding recipe", {
|
||||
message: err instanceof Error ? err.message : "Unknown error",
|
||||
});
|
||||
throw new Error(err instanceof Error ? err.message : "Unknown error");
|
||||
}
|
||||
}
|
||||
async addRecipe(recipeData) {
|
||||
|
||||
async addRecipe(recipeData: {
|
||||
name: string;
|
||||
author: string;
|
||||
cuisine: string;
|
||||
stars: number;
|
||||
ingredients: string[];
|
||||
steps: { [key: string]: string };
|
||||
prep_minutes: number;
|
||||
cook_minutes: number;
|
||||
}): Promise<any> {
|
||||
const {
|
||||
name,
|
||||
author,
|
||||
|
@ -81,33 +96,37 @@ class recipeModel {
|
|||
},
|
||||
});
|
||||
|
||||
logger.info("new recipe created", {
|
||||
logger.info("New recipe created", {
|
||||
id: createdRecipe.id,
|
||||
name: createdRecipe.name,
|
||||
});
|
||||
return createdRecipe;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.error("error creating recipe", err);
|
||||
} catch (err) {
|
||||
console.log("Error creating recipe:", err);
|
||||
logger.error("Error creating recipe", {
|
||||
message: err instanceof Error ? err.message : "Unknown error",
|
||||
});
|
||||
throw new Error("Failed to add recipe");
|
||||
}
|
||||
}
|
||||
|
||||
async setStars(id, stars) {
|
||||
async setStars(id: number, stars: number): Promise<{ message: string }> {
|
||||
try {
|
||||
await this.prisma.recipes.update({
|
||||
where: { id },
|
||||
data: { stars },
|
||||
});
|
||||
return { message: "stars updated" };
|
||||
return { message: "Stars updated" };
|
||||
} catch (err) {
|
||||
console.error("Error updating stars:", err);
|
||||
logger.error("error setting stars", err);
|
||||
throw new Error(err.message);
|
||||
logger.error("Error setting stars", {
|
||||
message: err instanceof Error ? err.message : "Unknown error",
|
||||
});
|
||||
throw new Error(err instanceof Error ? err.message : "Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
async deleteRecipe(id) {
|
||||
async deleteRecipe(id: number): Promise<{ message: string }> {
|
||||
try {
|
||||
await this.prisma.recipe_ingredients.deleteMany({
|
||||
where: { recipe_id: id },
|
||||
|
@ -119,16 +138,19 @@ class recipeModel {
|
|||
const deletedRecipe = await this.prisma.recipes.delete({
|
||||
where: { id },
|
||||
});
|
||||
logger.info("recipe deleted", {
|
||||
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);
|
||||
logger.error("Error deleting recipe", {
|
||||
message: err instanceof Error ? err.message : "Unknown error",
|
||||
});
|
||||
throw new Error(err instanceof Error ? err.message : "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = recipeModel;
|
||||
|
||||
export default RecipeModel;
|
|
@ -1,5 +1,5 @@
|
|||
const express = require("express");
|
||||
const recipeController = require("../controllers/recipeController");
|
||||
import express, { Router } from "express";
|
||||
import recipeController from "../controllers/recipeController";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
@ -15,4 +15,4 @@ router.post("/set-stars", recipeController.setStars);
|
|||
|
||||
router.delete("/delete-recipe", recipeController.deleteRecipe);
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
|
@ -1,11 +1,13 @@
|
|||
const fs = require("fs");
|
||||
import fs from "fs";
|
||||
|
||||
class Logger {
|
||||
constructor(filePath) {
|
||||
this.filePath = "/logs/app.log";
|
||||
private filePath: string;
|
||||
|
||||
constructor(filePath: string = "/logs/app.log") {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
log(level, message, params) {
|
||||
private log(level: string, message: string, params?: object) {
|
||||
const logEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: level,
|
||||
|
@ -17,17 +19,17 @@ class Logger {
|
|||
});
|
||||
}
|
||||
|
||||
info(message, params = {}) {
|
||||
info(message: string, params: object = {}) {
|
||||
this.log("info", message, params);
|
||||
}
|
||||
|
||||
warn(message, params = {}) {
|
||||
warn(message: string, params: object = {}) {
|
||||
this.log("warn", message, params);
|
||||
}
|
||||
|
||||
error(message, params = {}) {
|
||||
error(message: string, params: object = {}) {
|
||||
this.log("error", message, params);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Logger;
|
||||
export default Logger;
|
|
@ -4,7 +4,7 @@ WORKDIR /usr/src/app
|
|||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
RUN if [ "$NODE_ENV" = "dev" ]; then npm install; else npm install --only=production; fi
|
||||
|
||||
COPY . .
|
||||
|
||||
|
|
|
@ -12,17 +12,17 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^24.2.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.2.0",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue