diff --git a/.env b/.env new file mode 100644 index 0000000..48c9ad9 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +NODE_PORT=3001 +MONGO_PORT=27017 +MONGO_CONN_STRING="" +MONGO_NAME="" +COLLECTION_NAME="" \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 0967ef4..8db60ca 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1 +1,3 @@ -{} +{ + "singleQuote": true +} diff --git a/compose.yml b/compose.yml index b54e53d..b87aa0a 100644 --- a/compose.yml +++ b/compose.yml @@ -3,17 +3,31 @@ services: container_name: ts-catservice-docker restart: always ports: - - 3001:${PORT} + - 3001:${NODE_PORT} env_file: - .env build: context: . target: production args: - - PORT=${PORT} + - PORT=${NODE_PORT} volumes: - volume:/usr/src/app - ignore:/usr/src/app/node_modules/ + networks: + - catservice-network + depends_on: + - mongo-database + mongo-database: + container_name: mongo-database + image: mongo + ports: + - 27017:${MONGO_PORT} + networks: + - catservice-network +networks: + catservice-network: + driver: bridge volumes: ignore: volume: diff --git a/eslint.config.js b/eslint.config.js index 4035671..149ac8f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -14,6 +14,7 @@ export default defineConfig([ { files: ["**/*.md"], plugins: { markdown }, language: "markdown/gfm", extends: ["markdown/recommended"] }, { rules: { ...tseslint.configs.recommended.rules, - "no-explicit-any": "warn" + "no-explicit-any": "warn", + "quotes": ["error", "single"] }} ]); diff --git a/package-lock.json b/package-lock.json index 87c56d2..70f36ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "cors": "^2.8.5", "express": "^4.16.1", "jsonschema": "^1.4.1", + "mongodb": "^6.17.0", "uuid": "^9.0.0" }, "devDependencies": { @@ -1416,6 +1417,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1769,6 +1779,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.12", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", @@ -2529,6 +2554,15 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5148,6 +5182,12 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -5871,6 +5911,62 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6320,7 +6416,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6661,6 +6756,15 @@ "source-map": "^0.6.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6891,6 +6995,18 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -7206,6 +7322,28 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8359,6 +8497,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8679,6 +8825,19 @@ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", "dev": true }, + "@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "requires": { + "@types/webidl-conversions": "*" + } + }, "@types/yargs": { "version": "17.0.12", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", @@ -9185,6 +9344,11 @@ "node-int64": "^0.4.0" } }, + "bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -11098,6 +11262,11 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -11514,6 +11683,25 @@ "brace-expansion": "^1.1.7" } }, + "mongodb": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "requires": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + } + }, + "mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "requires": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -11842,8 +12030,7 @@ "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { "version": "6.10.3", @@ -12075,6 +12262,14 @@ "source-map": "^0.6.0" } }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "requires": { + "memory-pager": "^1.0.2" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12240,6 +12435,14 @@ "nopt": "~1.0.10" } }, + "tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "requires": { + "punycode": "^2.3.1" + } + }, "ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -12436,6 +12639,20 @@ "makeerror": "1.0.12" } }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "requires": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index dbbfb6c..4e80216 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "cors": "^2.8.5", "express": "^4.16.1", "jsonschema": "^1.4.1", + "mongodb": "^6.17.0", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/src/app/index.ts b/src/app/index.ts index a6e9b85..25ce860 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -1,11 +1,11 @@ -import express from "express"; -import cors from "cors"; -import bodyParser from "body-parser"; -import { catRoutes } from "./v3/routes/catRoutes.js"; +import express from 'express'; +import cors from 'cors'; +import bodyParser from 'body-parser'; +import { catRoutes } from './v3/routes/catRoutes.js'; const PORT = process.env.PORT || 3001; const corsOptions = { - origin: "*", + origin: '*', }; const app = express(); @@ -14,6 +14,6 @@ app.use(bodyParser.json()); app.use(cors(corsOptions)); // V3 -> TypeScript and Express approach -app.use("/api/v3/cats", catRoutes()); +app.use('/api/v3/cats', catRoutes()); app.listen(PORT); diff --git a/src/app/v3/data/Cat.ts b/src/app/v3/data/Cat.ts index 0af3897..aea6c3d 100644 --- a/src/app/v3/data/Cat.ts +++ b/src/app/v3/data/Cat.ts @@ -1,5 +1,5 @@ -import { getDatabase, saveToDatabase } from "./utils.js"; -import type { SearchParams, Cat, StoredCat } from "../shared/types.js"; +import { getDatabase, saveToDatabase } from './utils.js'; +import type { SearchParams, Cat, StoredCat } from '../shared/types.js'; export const getAll = (params: SearchParams) => { try { @@ -93,7 +93,7 @@ export const update = (catId: string, cat: StoredCat) => { const updatedCat = { ...DB.Cats[indexForUpdate], ...cat, - updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }), + updatedAt: new Date().toLocaleString('en-US', { timeZone: 'UTC' }), }; DB.Cats[indexForUpdate] = updatedCat; saveToDatabase(DB); diff --git a/src/app/v3/data/CatRouter.ts b/src/app/v3/data/CatRouter.ts new file mode 100644 index 0000000..7e133fa --- /dev/null +++ b/src/app/v3/data/CatRouter.ts @@ -0,0 +1,119 @@ +import { getDatabase, saveToDatabase } from './utils.js'; +import type { SearchParams, Cat, StoredCat } from '../shared/types.js'; +import { ObjectId } from 'mongodb'; +import { collections } from './DBService.js'; +import MongoCat from './MongoCat.js'; + +export const getAll = async (params: SearchParams) => { + try { + // Optional params to filter cats based on colorType and favoriteMeal + const { colorType, favoriteMeal } = params; + const cats = (await collections.cats + ?.find({ + ...(colorType && { + colorType: { $eq: params.colorType?.toLowerCase() }, + }), + ...(favoriteMeal && { + favoriteMeals: { + $elemMatch: { $eq: params.favoriteMeal?.toLowerCase() }, + }, + }), + }) + .toArray()) satisfies MongoCat[] | undefined; + + return { + cats: cats ?? [], + }; + } catch (error) { + throw { status: 500, message: error }; + } +}; + +export const get = async (catId: string) => { + try { + const cat = await collections.cats?.findOne({ + _id: new ObjectId(catId), + }); + if (!cat) { + throw { + status: 400, + message: `Can't find a cat with the id '${catId}'`, + }; + } + return cat; + } catch (error: any) { + throw { status: error?.status || 500, message: error?.message || error }; + } +}; + +export const add = async (newCat: Cat) => { + try { + const sameNameCat = (await collections.cats?.findOne({ + firstName: { $eq: newCat.firstName }, + lastName: { $eq: newCat.lastName }, + })) satisfies MongoCat | null | undefined; + + if (sameNameCat) { + throw { + status: 400, + message: `Cat with name ${newCat.firstName} ${newCat.lastName} already exists`, + }; + } + + const result = await collections.cats?.insertOne(newCat as MongoCat); + return result; + } catch (error: any) { + throw { + status: 500, + message: error?.message || error, + }; + } +}; + +export const update = (catId: string, cat: StoredCat) => { + try { + const DB = getDatabase(); + const indexForUpdate = DB.Cats.findIndex( + (cat: StoredCat) => cat.id === catId, + ); + if (indexForUpdate === -1) { + throw { + status: 400, + message: `Can't find cat with id '${catId}'`, + }; + } + const updatedCat = { + ...DB.Cats[indexForUpdate], + ...cat, + updatedAt: new Date().toLocaleString('en-US', { timeZone: 'UTC' }), + }; + DB.Cats[indexForUpdate] = updatedCat; + saveToDatabase(DB); + return updatedCat; + } catch (error: any) { + throw { + status: error?.status || 500, + message: error?.message || error, + }; + } +}; + +export const remove = (catId: string) => { + try { + const DB = getDatabase(); + const indexForDeletion = DB.Cats.findIndex( + (cat: StoredCat) => cat.id === catId, + ); + if (indexForDeletion === -1) { + throw { + status: 400, + message: `Can't find cat with the id '${catId}'`, + }; + } + + DB.Cats.splice(indexForDeletion, 1); + saveToDatabase(DB); + } catch (error: any) { + throw { status: error?.status || 500, message: error?.message || error }; + } +}; diff --git a/src/app/v3/data/DBService.ts b/src/app/v3/data/DBService.ts new file mode 100644 index 0000000..4e66e4a --- /dev/null +++ b/src/app/v3/data/DBService.ts @@ -0,0 +1,20 @@ +import * as mongoDB from 'mongodb'; +import type MongoCat from './MongoCat.js'; + +export const collections: { cats?: mongoDB.Collection } = {}; + +export async function connectToDatabase() { + const { DB_CONN_STRING, DB_NAME, CATS_COLLECTION_NAME } = process.env; + if (!DB_CONN_STRING || !DB_NAME || !CATS_COLLECTION_NAME) { + throw new Error('Missing environment variables'); + } + + const client: mongoDB.MongoClient = new mongoDB.MongoClient(DB_CONN_STRING); + + await client.connect(); + const db: mongoDB.Db = client.db(DB_NAME); + + const catsCollection: mongoDB.Collection = + db.collection(CATS_COLLECTION_NAME); + collections.cats = catsCollection; +} diff --git a/src/app/v3/data/MongoCat.ts b/src/app/v3/data/MongoCat.ts new file mode 100644 index 0000000..092cc5e --- /dev/null +++ b/src/app/v3/data/MongoCat.ts @@ -0,0 +1,14 @@ +import { ObjectId } from 'mongodb'; + +export default class MongoCat { + constructor( + public firstName: string, + public lastName: string, + public colorType: string, + public age: number, + public nicknames?: string[], + public favoriteMeals?: string[], + public externalId?: string, + public id?: ObjectId, + ) {} +} diff --git a/src/app/v3/data/utils.js b/src/app/v3/data/utils.js index a765816..2a054e9 100644 --- a/src/app/v3/data/utils.js +++ b/src/app/v3/data/utils.js @@ -1,14 +1,14 @@ -import fs from "fs"; +import fs from 'fs'; export const saveToDatabase = (/** @type {any} */ DB) => { - fs.writeFileSync("./src/app/v1/data/db.json", JSON.stringify(DB, null, 2), { - encoding: "utf-8", + fs.writeFileSync('./src/app/v1/data/db.json', JSON.stringify(DB, null, 2), { + encoding: 'utf-8', }); }; export const getDatabase = () => { const DB = JSON.parse( - fs.readFileSync("./src/app/v1/data/db.json").toString(), + fs.readFileSync('./src/app/v1/data/db.json').toString(), ); return DB; }; diff --git a/src/app/v3/routes/catRoutes.js b/src/app/v3/routes/catRoutes.js index b6d4983..8a501c2 100644 --- a/src/app/v3/routes/catRoutes.js +++ b/src/app/v3/routes/catRoutes.js @@ -1,17 +1,17 @@ -import express from "express"; -import * as catController from "../controllers/catController.js"; +import express from 'express'; +import * as catController from '../controllers/catController.js'; export const catRoutes = () => { const router = express.Router(); - router.get("/", catController.getAllCats); + router.get('/', catController.getAllCats); - router.get("/:catId", catController.getCat); + router.get('/:catId', catController.getCat); - router.post("/", catController.createCat); + router.post('/', catController.createCat); - router.patch("/:catId", catController.updateCat); + router.patch('/:catId', catController.updateCat); - router.delete("/:catId", catController.deleteCat); + router.delete('/:catId', catController.deleteCat); return router; }; diff --git a/src/app/v3/schemas/catSchema.js b/src/app/v3/schemas/catSchema.js deleted file mode 100644 index 525d8b8..0000000 --- a/src/app/v3/schemas/catSchema.js +++ /dev/null @@ -1,37 +0,0 @@ -const catSchema = { - id: "/Cat", - type: "object", - properties: { - firstName: { - type: "string", - description: "First name of cat", - }, - lastName: { - type: "string", - description: "Last name of cat", - }, - colorType: { - type: "string", - description: "Brief description about the cat color type", - }, - age: { - type: "integer", - description: "A numeric int representing the age of the cat", - }, - nicknames: { - type: "array", - items: { - type: "string", - }, - }, - favoriteMeals: { - type: "array", - items: { - type: "string", - }, - }, - }, - required: ["firstName", "lastName", "colorType", "age"], -}; - -export { catSchema }; diff --git a/src/app/v3/schemas/catSchema.ts b/src/app/v3/schemas/catSchema.ts new file mode 100644 index 0000000..1956af8 --- /dev/null +++ b/src/app/v3/schemas/catSchema.ts @@ -0,0 +1,35 @@ +export const catSchema = { + id: '/Cat', + type: 'object', + properties: { + firstName: { + type: 'string', + description: 'First name of cat', + }, + lastName: { + type: 'string', + description: 'Last name of cat', + }, + colorType: { + type: 'string', + description: 'Brief description about the cat color type', + }, + age: { + type: 'integer', + description: 'A numeric int representing the age of the cat', + }, + nicknames: { + type: 'array', + items: { + type: 'string', + }, + }, + favoriteMeals: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: ['firstName', 'lastName', 'colorType', 'age'], +}; diff --git a/src/app/v3/services/catService.ts b/src/app/v3/services/catService.ts index ca09e17..998f2e4 100644 --- a/src/app/v3/services/catService.ts +++ b/src/app/v3/services/catService.ts @@ -1,7 +1,7 @@ -import * as Cats from "../data/Cat.js"; -import { v4 as uuidv4 } from "uuid"; -import type { Cat, StoredCat } from "../shared/types.js"; -import type { ParsedQs } from "qs"; +import * as Cats from '../data/Cat.js'; +import { v4 as uuidv4 } from 'uuid'; +import type { Cat, StoredCat } from '../shared/types.js'; +import type { ParsedQs } from 'qs'; export const getCats = (params: ParsedQs) => Cats.getAll(params); @@ -11,8 +11,8 @@ export const addCat = (newCat: Cat) => { const catToInsert = { ...newCat, id: uuidv4(), - createdAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }), - updatedAt: new Date().toLocaleString("en-US", { timeZone: "UTC" }), + createdAt: new Date().toLocaleString('en-US', { timeZone: 'UTC' }), + updatedAt: new Date().toLocaleString('en-US', { timeZone: 'UTC' }), }; const addedCat = Cats.add(catToInsert); return addedCat; diff --git a/src/app/v3/validators/schemaValidator.ts b/src/app/v3/validators/schemaValidator.ts index 55e7504..e0c4ec3 100644 --- a/src/app/v3/validators/schemaValidator.ts +++ b/src/app/v3/validators/schemaValidator.ts @@ -1,8 +1,8 @@ -import { Validator } from "jsonschema"; -import { catSchema } from "../schemas/catSchema.js"; +import { Validator } from 'jsonschema'; +import { catSchema } from '../schemas/catSchema.js'; export const validateInput = (body: object) => { const v = new Validator(); - v.addSchema(catSchema, "/Cat"); + v.addSchema(catSchema, '/Cat'); return v.validate(body, catSchema); }; diff --git a/src/tests/v2/data/utils.test.js b/src/tests/v2/data/utils.test.js index 3254f3c..d1fbe20 100644 --- a/src/tests/v2/data/utils.test.js +++ b/src/tests/v2/data/utils.test.js @@ -1,16 +1,15 @@ +import DBConnection from '../../../app/v2/data/utils.js'; -import DBConnection from "../../../app/v2/data/utils.js"; - -describe("Common util functions tests", () => { - describe("Database interaction", () => { - test("Should retrieve the data from a json file", () => { +describe('Common util functions tests', () => { + describe('Database interaction', () => { + test('Should retrieve the data from a json file', () => { const DB = new DBConnection(); DB.getDatabase(); expect(DB).toBeDefined(); }); - test("Should save data into a file", () => { - const result = new DBConnection().saveToDatabase("", "data.json"); + test('Should save data into a file', () => { + const result = new DBConnection().saveToDatabase('', 'data.json'); expect(result).toBeTruthy(); }); }); diff --git a/src/tests/v2/services/catService.test.js b/src/tests/v2/services/catService.test.js index d2f743f..b3624f0 100644 --- a/src/tests/v2/services/catService.test.js +++ b/src/tests/v2/services/catService.test.js @@ -1,7 +1,5 @@ -import * as CatService from '../../../app/v2/services/catService.js' +import * as CatService from '../../../app/v2/services/catService.js'; -describe("Cat Service Layer related tests", () => { - describe("Get cats", () => { - - }) -}) \ No newline at end of file +describe('Cat Service Layer related tests', () => { + describe('Get cats', () => {}); +});