From 4518281bb9e52a57f20323f4d6f758b1eecd0900 Mon Sep 17 00:00:00 2001 From: Dmitry Kostikov Date: Mon, 3 Jul 2023 21:49:28 +0400 Subject: [PATCH 1/4] The first draft. --- package.json | 25 ++++ src/db_service.ts | 237 ++++++++++++++++++++++++++++++ src/entities/User.ts | 18 +++ src/index.ts | 153 ++++++++++++++++++++ src/user_service.ts | 337 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 770 insertions(+) create mode 100644 package.json create mode 100644 src/db_service.ts create mode 100644 src/entities/User.ts create mode 100644 src/index.ts create mode 100644 src/user_service.ts diff --git a/package.json b/package.json new file mode 100644 index 0000000..b690d99 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "nodejs-crud-api", + "version": "1.0.0", + "description": "Implementation of simple CRUD API using in-memory database", + "main": "src/index.ts", + "scripts": { + "start": "node src", + "test": "test" + }, + "author": "docroot", + "license": "ISC", + "dependencies": { + "dotenv": "^16.3.1", + "ts-node": "^10.9.1", + "typescript": "^5.1.6", + "uuid": "^9.0.0", + "webpack": "^5.88.1" + }, + "devDependencies": { + "@types/uuid": "^9.0.2", + "eslint": "^8.44.0", + "nodemon": "^1.19.4", + "ts-node-dev": "^2.0.0" + } +} diff --git a/src/db_service.ts b/src/db_service.ts new file mode 100644 index 0000000..8011687 --- /dev/null +++ b/src/db_service.ts @@ -0,0 +1,237 @@ +import http, { IncomingMessage, ServerResponse } from 'http'; +import { parse } from 'url'; +import * as uuid from 'uuid'; +import { User } from './entities/User' + + +class DbService { + private users: User[]; + + constructor() { + this.users = []; + } + + createUser(user: User) { + this.users.push(user); + } + + getUsers() { + return this.users; + } + + getUserById(id: string) { + return this.users.find((user) => user.id === id); + } + + updateUser(id: string, updatedUser: User) { + const index = this.users.findIndex((user) => user.id === id); + if (index !== -1) { + this.users[index] = updatedUser; + return this.users[index]; + } + return false; + } + + deleteUser(id: string) { + const index = this.users.findIndex((user) => user.id === id); + if (index !== -1) { + this.users.splice(index, 1); + return true; + } + return false; + } +} + +const dbService = new DbService(); + +const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { + const { method, url } = req; + + const parsedUrl = parse(url || '', true); + const path = parsedUrl.pathname || ''; + + let requestBody = ''; + + req.on('data', (chunk) => { + requestBody += chunk; + }); + + req.on('end', () => { + let response; + res.setHeader('Content-Type', 'application/json'); + + if (method === 'POST' && (path === '/api' || path === '')) { + try { + res.statusCode = 200; + const request = JSON.parse(requestBody); + console.log(request); + if (request.cmd) { + const cmd = request.cmd; + console.log(`CMD is [${cmd}]`); + const data = request.data; + switch (cmd) { + case 'USERS': + response = JSON.stringify(dbService.getUsers()); + break; + case 'USER': + if (!data.id) { + res.statusCode = 400; + response = 'No user ID'; + throw new Error(response); + } + if (!uuid.validate(data.id)) { + res.statusCode = 400; + response = 'User ID is not valid'; + throw new Error(response); + } + console.log(`User ID: [${data.id}]`); + const user = dbService.getUserById(data.id); + if (user) { + response = JSON.stringify(user); + console.log(user); + } + else { + res.statusCode = 404; + response = 'User not found'; + throw new Error(response); + } + break; + case 'ADD': + console.log('ADD' + data); + if (!data.username || data.username === '' || !data.age) { + res.statusCode = 400; + response = 'User data is not valid'; + throw new Error(response); + } + else { + const hobbies = Array.isArray(data.hobbies) ? data.hobbies : []; + const user = new User(uuid.v4(), data.username, data.age, hobbies); + dbService.createUser(user); + response = JSON.stringify(user); + res.statusCode = 201; + } + break; + case 'UPDATE': + console.log('UPDATE' + data); + if (!data.id || !data.username || data.username === '' || !data.age) { + res.statusCode = 400; + response = 'User data is not valid'; + throw new Error(response); + } + if (!uuid.validate(data.id)) { + res.statusCode = 400; + response = 'User ID is not valid'; + throw new Error(response); + } + else { + const hobbies = Array.isArray(data.hobbies) ? data.hobbies : []; + const user = new User(data.id, data.username, data.age, hobbies); + const result = dbService.updateUser(data.id, user); + if (result) { + response = JSON.stringify(result); + //res.statusCode = 200; + } + else { + res.statusCode = 404; + response = 'User not found'; + throw new Error(response); + } + } + break; + case 'DELETE': + console.log('DELETE' + data); + if (!data.id) { + res.statusCode = 400; + response = 'No user ID'; + throw new Error(response); + } + if (!uuid.validate(data.id)) { + res.statusCode = 400; + response = 'User ID is not valid'; + throw new Error(response); + } + console.log(`User ID: [${data.id}]`); + const result = dbService.deleteUser(data.id); + if (result) { + res.statusCode = 204; + response = 'DELETED'; + } + else { + res.statusCode = 404; + response = 'User not found'; + throw new Error(response); + } + break; + default: + throw new Error(`Unknown command ${cmd}`); + } + } + } + catch (error) { + console.log("Incorrect request"); + console.log(error); + console.log(response);// = JSON.stringify(error); + if (res.statusCode === 200) res.statusCode = 400; + } + // response = userService.getUsers(); + // res.statusCode = 200; + } else { + response = { error: 'Not found' }; + res.statusCode = 404; + } + + res.end(response); + }); +}); + + +process.on('SIGTERM', () => { + process.exit(0); +}); + +process.on('exit', () => { + server.closeAllConnections(); + console.log("DB service done its work."); +}); + + +process.on('error', (error) => { + console.log(error); + process.exit(0); +}); + + +const createTestUsers = () => { + const json = [{ + id: uuid.v4(), + username: 'John', + age: 25, + hobbies: ['Ski', 'Videogames'], + }, + { + id: 'f8561522-0681-41b4-979b-3b1ef3ae09de',//uuid.v4(), + username: 'Emma', + age: 27, + hobbies: ['Writing', 'Gardening'], + }, + { + id: uuid.v4(), + username: 'Ben', + age: 52, + hobbies: ['Reading', 'Gardening'], + } + ]; + + json.forEach((element) => { + const user: User = User.fromJson(element); + // console.log(user); + dbService.createUser(user); + }); +} + +createTestUsers(); + +const port = 4000; +server.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); diff --git a/src/entities/User.ts b/src/entities/User.ts new file mode 100644 index 0000000..18e3109 --- /dev/null +++ b/src/entities/User.ts @@ -0,0 +1,18 @@ +export class User { + id: string; + username: string; + age: number; + hobbies: string[]; + + constructor(uuid: string, username: string, age: number, hobbies: string[]) { + this.id = uuid; + this.username = username; + this.age = age; + this.hobbies = hobbies; + } + + static fromJson(json: any): User { + const { id, username, age, hobbies } = json; + return new User(id, username, age, hobbies); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..57857d8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,153 @@ +import cluster, { Worker } from 'cluster'; +import http, { IncomingMessage, ServerResponse } from 'http'; +import { availableParallelism } from 'node:os'; +import process from 'process'; +import child_process, { spawn, ChildProcess } from 'child_process'; +import dotenv from 'dotenv'; + + +const numCPUs = availableParallelism() - 1; +let activeChildProcesses = 0; +const masterPort = 3000; +let currentWorkerPort = masterPort + 1; + + +if (cluster.isPrimary) { + console.log(`Primary ${process.pid} is running`); + + const child = spawn('ts-node', ['src/db_service.ts']); + + if (child) { + if (child.stdout) { + child.stdout.on('data', (data) => { + console.log(`Child process stdout: ${data}`); + }); + } + + child.on('exit', (code: number) => { + console.log(`Child process exited with code ${code}`); + }); + } + + const onExit = () => { + if (child !== null) { + child.kill('SIGTERM'); + } + const workers = cluster.workers; + if (workers) { + Object.values(workers).forEach((worker) => { + if (worker) { + worker.kill('SIGTERM'); + } + }); + } + } + + + process.stdin.on('SIGTERM', () => { + console.log("SIGTERM!!!!"); + process.exit(0); + }); + + process.stdin.on('data', (data) => { + if (String(data).trim() === 'exit') { + onExit(); + } + }); + + + + process.on('SIGINT', () => { + console.log('Ctrl+C!\n'); + process.kill(process.pid, 'SIGTERM'); + }); + + process.on('exit', () => { + onExit(); + console.log('EXIT!\n'); + }); + + + for (let i = 0; i < numCPUs; i++) { + // console.log(`Cur port: [${currentWorkerPort}]`); + cluster.fork({ 'WRK_PORT': currentWorkerPort }); + currentWorkerPort++; + } + + // const workers = cluster.workers; + // if (workers) { + // Object.values(workers).forEach((worker) => { + // // Event listener for 'exit' event + // if (worker) { + // worker.on('exit', (code, signal) => { + // console.log(`Worker ${worker.id} exited with code ${code} and signal ${signal}`); + // }); + // } + // }); + // } + + cluster.on('exit', (worker: Worker, code: number, signal: string) => { + console.log(`worker ${worker.process.pid} exited with code ${code} and signal ${signal}`); + }); + + cluster.on('listening', (worker, address) => { + console.log( + `A worker is now connected to ${address.address}:${address.port}`); + }); + + // process.on('exit', () => { + // cluster.disconnect(() => { + // process.exit(0); + // }); + // // console.log("Done"); + // }); + + + const loadBalancer = http.createServer((req: IncomingMessage, res: ServerResponse) => { + const options = { + hostname: 'localhost', + port: 3002, + path: req.url, + method: req.method, + headers: req.headers + }; + + const outgoingReq = http.request(options, (outgoingRes) => { + + if (outgoingRes && outgoingRes.statusCode) { + res.writeHead(outgoingRes.statusCode, outgoingRes.headers); + outgoingRes.pipe(res); + } + else { + res.statusCode = 500; + res.end('Error: Could not resend request'); + } + }); + + outgoingReq.on('error', (error) => { + console.error(error); + res.statusCode = 500; + res.end('Error: Could not resend request'); + }); + + req.pipe(outgoingReq); + }).listen(masterPort); + +} else if (cluster.isWorker) { + + console.log(`Cur port: [${process.env.WRK_PORT}]`); + if (cluster.worker) console.log(`Worker ID: [${cluster.worker.id}]`); + + http.createServer((req, res) => { + req.pipe(process.stdout); + res.writeHead(200); + res.end('hello world\n'); + }).listen(process.env.WRK_PORT); + + process.on('SIGTERM', () => { + process.exit(0); + }); + + console.log(`Worker ${process.pid} started`); +} + diff --git a/src/user_service.ts b/src/user_service.ts new file mode 100644 index 0000000..b6c0754 --- /dev/null +++ b/src/user_service.ts @@ -0,0 +1,337 @@ +import http, { IncomingMessage, ServerResponse } from 'http'; +import { parse, URL } from 'url'; +import * as uuid from 'uuid'; +import { User } from './entities/User' + +const baseUrl = 'https://localhost'; + +const dbPort = '4000'; +const dbUrl = new URL('/api/user', baseUrl + ':' + dbPort); + + +class UserService { + private users: User[]; + + constructor() { + this.users = []; + } + + async createUser(user: User) { + const options = { + hostname: 'localhost', + port: dbPort, + path: '/api', + method: 'POST', + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + console.log(responseData); + if (res.statusCode === 201) { + try { + const newUser = User.fromJson(JSON.parse(responseData)); + console.log(newUser); + resolve(newUser); + } catch (error) { + reject('Incorrect user data'); + } + } + else { + reject('Incorrect user data'); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.write(JSON.stringify({ 'cmd': 'ADD', 'data': { 'username': user.username, 'age': user.age, 'hobbies': user.hobbies } })); + req.end(); + }); + } + + async getUsers() { + const options = { + hostname: 'localhost', + port: dbPort, + path: '/api', + method: 'POST', + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + console.log(responseData); + const users: User[] = []; + try { + for (const userInfo of Object.values(JSON.parse(responseData))) { + const user = User.fromJson(userInfo); + users.push(user); + console.log(user); + } + resolve(users); + } catch (error) { + reject(error); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.write(JSON.stringify({ 'cmd': 'USERS', 'data': '' })); + req.end(); + }); + } + + async getUserById(id: string) { + const options = { + hostname: 'localhost', + port: dbPort, + path: '/api', + method: 'POST', + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + if (res.statusCode === 200) { + console.log("RESP:\n" + responseData); + try { + const user = User.fromJson(JSON.parse(responseData)); + console.log(user); + resolve(user); + } catch (error) { + reject('Incorrect user data'); + } + } + else { + reject('Uer not found'); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + req.write(JSON.stringify({ 'cmd': 'USER', 'data': { 'id': id } })); + req.end(); + }); + } + + + async updateUser(id: string, user: User) { + const options = { + hostname: 'localhost', + port: dbPort, + path: '/api', + method: 'POST', + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + console.log(responseData); + if (res.statusCode === 200) { + try { + const updUser = User.fromJson(JSON.parse(responseData)); + console.log(updUser); + resolve(updUser); + } catch (error) { + reject('Incorrect user data'); + } + } + else { + reject('Uer not found'); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.write(JSON.stringify({ 'cmd': 'UPDATE', 'data': { 'id': user.id, 'username': user.username, 'age': user.age, 'hobbies': user.hobbies } })); + req.end(); + }); + } + + async deleteUser(id: string) { + const options = { + hostname: 'localhost', + port: dbPort, + path: '/api', + method: 'POST', + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + console.log(responseData); + if (res.statusCode === 204) { + console.log(responseData); + resolve('User deleted'); + } + else { + reject('User not found'); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + req.write(JSON.stringify({ 'cmd': 'DELETE', 'data': { 'id': id } })); + req.end(); + }); + } +} + +const userService = new UserService(); + +const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { + const { method, url } = req; + + const parsedUrl = parse(url || '', true); + const path = parsedUrl.pathname || ''; + + let requestBody = ''; + + req.on('data', (chunk) => { + requestBody += chunk; + }); + + req.on('end', () => { + let response; + res.setHeader('Content-Type', 'application/json'); + console.log(`PATH: [${path}]`); + + if (method === 'GET' && (path === '/users' || path === '/users/')) { + userService.getUsers().then((resolve) => { + res.statusCode = 200; + response = resolve; + console.log("====" + response + "===="); + res.end(JSON.stringify(response)); + }).catch((error) => { + res.statusCode = 400; + throw error + }); + } else if (method === 'GET' && path.startsWith('/users/')) { + const userId = path.split('/')[2]; + if (!userId || !uuid.validate(userId)) { + response = 'User ID is not valid'; + res.statusCode = 400; + res.end(response); + } + else { + userService.getUserById(userId).then((resolve) => { + response = resolve; + res.statusCode = 200; + res.end(JSON.stringify(response)); + }).catch((error) => { + response = 'User not found'; + res.statusCode = 404; + res.end(response); + }); + } + } else if (method === 'POST' && path === '/users') { + console.log("POST"); + console.log(requestBody); + const userInfo = JSON.parse(requestBody); + userInfo.id = ''; + let user = User.fromJson(userInfo); + userService.createUser(user).then((resolve) => { + response = resolve; + res.statusCode = 201; + res.end(JSON.stringify(response)); + }).catch((error) => { + response = 'Unables to create user'; + res.statusCode = 400; + res.end(response); + }); + } else if (method === 'PUT' && path.startsWith('/users/')) { + console.log("PUT"); + const userId = path.split('/')[2]; + if (!userId || !uuid.validate(userId)) { + response = 'User ID is not valid'; + res.statusCode = 400; + res.end(response); + } + else { + const userInfo = JSON.parse(requestBody); + userInfo.id = userId; + let user = User.fromJson(userInfo); + userService.updateUser(userId, userInfo).then((resolve) => { + response = resolve; + res.statusCode = 200; + res.end(JSON.stringify(response)); + }).catch((error) => { + response = 'Unables to update user'; + res.statusCode = 404; + res.end(response); + }); + } + } else if (method === 'DELETE' && path.startsWith('/users/')) { + console.log("DELETE"); + const userId = path.split('/')[2]; + if (!userId || !uuid.validate(userId)) { + response = 'User ID is not valid'; + res.statusCode = 400; + res.end(response); + } + else { + userService.deleteUser(userId).then((resolve) => { + response = 'User deleted successfully'; + res.statusCode = 200; + res.end(response); + }).catch((error) => { + response = 'User not found'; + res.statusCode = 404; + res.end(response); + }); + } + } else { + response = { error: 'Not found' }; + res.statusCode = 404; + } + }); +}); + +const port = 3100; +server.listen(port, () => { + console.log(`Server is running on port ${port}`); +}); From c2ab023e1d129c6be4852f0ead508b1590b34bd1 Mon Sep 17 00:00:00 2001 From: Dmitry Kostikov Date: Mon, 3 Jul 2023 22:43:22 +0400 Subject: [PATCH 2/4] Get listening ports from ENV variables. --- .env | 4 ++ src/db_service.ts | 6 +-- src/user_service.ts | 100 +++++++++++++++++--------------------------- 3 files changed, 45 insertions(+), 65 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..3f90462 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +CRUD_BASE_URL=http://localhost +CRUD_US_PORT=4000 +CRUD_DB_HOST=localhost +CRUD_DB_PORT=4100 diff --git a/src/db_service.ts b/src/db_service.ts index 8011687..0372ef7 100644 --- a/src/db_service.ts +++ b/src/db_service.ts @@ -3,6 +3,7 @@ import { parse } from 'url'; import * as uuid from 'uuid'; import { User } from './entities/User' +const dbPort = process.env['CRUD_DB_PORT'] ? process.env['CRUD_US_PORT'] : '4100'; class DbService { private users: User[]; @@ -231,7 +232,6 @@ const createTestUsers = () => { createTestUsers(); -const port = 4000; -server.listen(port, () => { - console.log(`Server is running on port ${port}`); +server.listen(dbPort, () => { + console.log(`Server is running on port ${dbPort}`); }); diff --git a/src/user_service.ts b/src/user_service.ts index b6c0754..4f08689 100644 --- a/src/user_service.ts +++ b/src/user_service.ts @@ -3,29 +3,34 @@ import { parse, URL } from 'url'; import * as uuid from 'uuid'; import { User } from './entities/User' -const baseUrl = 'https://localhost'; +// import dotenv from 'dotenv'; -const dbPort = '4000'; -const dbUrl = new URL('/api/user', baseUrl + ':' + dbPort); +// dotenv.config(); +// Object.keys(process.env).forEach((key) => { +// if (key.startsWith('CRUD')) +// console.log(`${key}=[${process.env[key]}]`); +// }); -class UserService { - private users: User[]; - constructor() { - this.users = []; - } +//const baseUrl = process.env['CURD_BASE_URL'] ? process.env['CURD_BASE_URL'] : 'http://localhost'; +const usPort = process.env['CRUD_US_PORT'] ? process.env['CRUD_US_PORT'] : '4000'; +const dbHost = process.env['CRUD_DB_HOST'] ? process.env['CRUD_DB_HOST'] : 'localhost'; +const dbPort = process.env['CRUD_DB_PORT'] ? process.env['CRUD_DB_PORT'] : '4100'; + +const db_req_options = { + hostname: dbHost, + port: dbPort, + path: '/api', + method: 'POST', +}; - async createUser(user: User) { - const options = { - hostname: 'localhost', - port: dbPort, - path: '/api', - method: 'POST', - }; +class UserService { + + async createUser(user: User) { return new Promise((resolve, reject) => { - const req = http.request(options, (res) => { + const req = http.request(db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -58,16 +63,10 @@ class UserService { }); } - async getUsers() { - const options = { - hostname: 'localhost', - port: dbPort, - path: '/api', - method: 'POST', - }; + async getUsers() { return new Promise((resolve, reject) => { - const req = http.request(options, (res) => { + const req = http.request(db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -99,16 +98,10 @@ class UserService { }); } - async getUserById(id: string) { - const options = { - hostname: 'localhost', - port: dbPort, - path: '/api', - method: 'POST', - }; + async getUserById(id: string) { return new Promise((resolve, reject) => { - const req = http.request(options, (res) => { + const req = http.request(db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -117,7 +110,6 @@ class UserService { res.on('end', () => { if (res.statusCode === 200) { - console.log("RESP:\n" + responseData); try { const user = User.fromJson(JSON.parse(responseData)); console.log(user); @@ -142,15 +134,8 @@ class UserService { async updateUser(id: string, user: User) { - const options = { - hostname: 'localhost', - port: dbPort, - path: '/api', - method: 'POST', - }; - return new Promise((resolve, reject) => { - const req = http.request(options, (res) => { + const req = http.request(db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -158,7 +143,6 @@ class UserService { }); res.on('end', () => { - console.log(responseData); if (res.statusCode === 200) { try { const updUser = User.fromJson(JSON.parse(responseData)); @@ -183,16 +167,10 @@ class UserService { }); } - async deleteUser(id: string) { - const options = { - hostname: 'localhost', - port: dbPort, - path: '/api', - method: 'POST', - }; + async deleteUser(id: string) { return new Promise((resolve, reject) => { - const req = http.request(options, (res) => { + const req = http.request(db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -200,7 +178,6 @@ class UserService { }); res.on('end', () => { - console.log(responseData); if (res.statusCode === 204) { console.log(responseData); resolve('User deleted'); @@ -240,7 +217,7 @@ const server = http.createServer((req: IncomingMessage, res: ServerResponse) => res.setHeader('Content-Type', 'application/json'); console.log(`PATH: [${path}]`); - if (method === 'GET' && (path === '/users' || path === '/users/')) { + if (method === 'GET' && (path === '/api/users' || path === '/api/users/')) { userService.getUsers().then((resolve) => { res.statusCode = 200; response = resolve; @@ -250,8 +227,8 @@ const server = http.createServer((req: IncomingMessage, res: ServerResponse) => res.statusCode = 400; throw error }); - } else if (method === 'GET' && path.startsWith('/users/')) { - const userId = path.split('/')[2]; + } else if (method === 'GET' && path.startsWith('/api/users/')) { + const userId = path.split('/')[3]; if (!userId || !uuid.validate(userId)) { response = 'User ID is not valid'; res.statusCode = 400; @@ -268,7 +245,7 @@ const server = http.createServer((req: IncomingMessage, res: ServerResponse) => res.end(response); }); } - } else if (method === 'POST' && path === '/users') { + } else if (method === 'POST' && path === '/api/users') { console.log("POST"); console.log(requestBody); const userInfo = JSON.parse(requestBody); @@ -283,9 +260,9 @@ const server = http.createServer((req: IncomingMessage, res: ServerResponse) => res.statusCode = 400; res.end(response); }); - } else if (method === 'PUT' && path.startsWith('/users/')) { + } else if (method === 'PUT' && path.startsWith('/api/users/')) { console.log("PUT"); - const userId = path.split('/')[2]; + const userId = path.split('/')[3]; if (!userId || !uuid.validate(userId)) { response = 'User ID is not valid'; res.statusCode = 400; @@ -305,9 +282,9 @@ const server = http.createServer((req: IncomingMessage, res: ServerResponse) => res.end(response); }); } - } else if (method === 'DELETE' && path.startsWith('/users/')) { + } else if (method === 'DELETE' && path.startsWith('/api/users/')) { console.log("DELETE"); - const userId = path.split('/')[2]; + const userId = path.split('/')[3]; if (!userId || !uuid.validate(userId)) { response = 'User ID is not valid'; res.statusCode = 400; @@ -331,7 +308,6 @@ const server = http.createServer((req: IncomingMessage, res: ServerResponse) => }); }); -const port = 3100; -server.listen(port, () => { - console.log(`Server is running on port ${port}`); +server.listen(usPort, () => { + console.log(`Server is running on port ${usPort}`); }); From c0234a7884ceee924106949beab05e99b7427615 Mon Sep 17 00:00:00 2001 From: Dmitry Kostikov Date: Tue, 4 Jul 2023 03:23:00 +0400 Subject: [PATCH 3/4] Implemented single mode. --- .env | 2 +- package.json | 5 +- src/db_service.ts | 370 ++++++++++++++++++++++---------------------- src/single_mode.ts | 17 ++ src/user_service.ts | 270 ++++++++++++++++---------------- 5 files changed, 338 insertions(+), 326 deletions(-) create mode 100644 src/single_mode.ts diff --git a/.env b/.env index 3f90462..ec636f2 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ CRUD_BASE_URL=http://localhost CRUD_US_PORT=4000 CRUD_DB_HOST=localhost -CRUD_DB_PORT=4100 +CRUD_DB_PORT=4500 diff --git a/package.json b/package.json index b690d99..1c2ba74 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Implementation of simple CRUD API using in-memory database", "main": "src/index.ts", "scripts": { - "start": "node src", + "start:dev": "npm exec nodemon src/single_mode.ts", + "start:prod": "", "test": "test" }, "author": "docroot", @@ -22,4 +23,4 @@ "nodemon": "^1.19.4", "ts-node-dev": "^2.0.0" } -} +} \ No newline at end of file diff --git a/src/db_service.ts b/src/db_service.ts index 0372ef7..50960de 100644 --- a/src/db_service.ts +++ b/src/db_service.ts @@ -3,13 +3,14 @@ import { parse } from 'url'; import * as uuid from 'uuid'; import { User } from './entities/User' -const dbPort = process.env['CRUD_DB_PORT'] ? process.env['CRUD_US_PORT'] : '4100'; -class DbService { +export class DbService { private users: User[]; + private dbPort: string; constructor() { this.users = []; + this.dbPort = process.env['CRUD_DB_PORT'] ? process.env['CRUD_DB_PORT'] : '4100'; } createUser(user: User) { @@ -41,197 +42,194 @@ class DbService { } return false; } -} -const dbService = new DbService(); - -const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { - const { method, url } = req; - - const parsedUrl = parse(url || '', true); - const path = parsedUrl.pathname || ''; - - let requestBody = ''; - - req.on('data', (chunk) => { - requestBody += chunk; - }); - - req.on('end', () => { - let response; - res.setHeader('Content-Type', 'application/json'); - - if (method === 'POST' && (path === '/api' || path === '')) { - try { - res.statusCode = 200; - const request = JSON.parse(requestBody); - console.log(request); - if (request.cmd) { - const cmd = request.cmd; - console.log(`CMD is [${cmd}]`); - const data = request.data; - switch (cmd) { - case 'USERS': - response = JSON.stringify(dbService.getUsers()); - break; - case 'USER': - if (!data.id) { - res.statusCode = 400; - response = 'No user ID'; - throw new Error(response); - } - if (!uuid.validate(data.id)) { - res.statusCode = 400; - response = 'User ID is not valid'; - throw new Error(response); - } - console.log(`User ID: [${data.id}]`); - const user = dbService.getUserById(data.id); - if (user) { - response = JSON.stringify(user); - console.log(user); - } - else { - res.statusCode = 404; - response = 'User not found'; - throw new Error(response); - } - break; - case 'ADD': - console.log('ADD' + data); - if (!data.username || data.username === '' || !data.age) { - res.statusCode = 400; - response = 'User data is not valid'; - throw new Error(response); - } - else { - const hobbies = Array.isArray(data.hobbies) ? data.hobbies : []; - const user = new User(uuid.v4(), data.username, data.age, hobbies); - dbService.createUser(user); - response = JSON.stringify(user); - res.statusCode = 201; - } - break; - case 'UPDATE': - console.log('UPDATE' + data); - if (!data.id || !data.username || data.username === '' || !data.age) { - res.statusCode = 400; - response = 'User data is not valid'; - throw new Error(response); - } - if (!uuid.validate(data.id)) { - res.statusCode = 400; - response = 'User ID is not valid'; - throw new Error(response); - } - else { - const hobbies = Array.isArray(data.hobbies) ? data.hobbies : []; - const user = new User(data.id, data.username, data.age, hobbies); - const result = dbService.updateUser(data.id, user); - if (result) { - response = JSON.stringify(result); - //res.statusCode = 200; - } - else { - res.statusCode = 404; - response = 'User not found'; - throw new Error(response); - } - } - break; - case 'DELETE': - console.log('DELETE' + data); - if (!data.id) { - res.statusCode = 400; - response = 'No user ID'; - throw new Error(response); - } - if (!uuid.validate(data.id)) { - res.statusCode = 400; - response = 'User ID is not valid'; - throw new Error(response); - } - console.log(`User ID: [${data.id}]`); - const result = dbService.deleteUser(data.id); - if (result) { - res.statusCode = 204; - response = 'DELETED'; + start() { + const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { + const { method, url } = req; + + const parsedUrl = parse(url || '', true); + const path = parsedUrl.pathname || ''; + + let requestBody = ''; + + req.on('data', (chunk) => { + requestBody += chunk; + }); + + req.on('end', () => { + let response; + res.setHeader('Content-Type', 'application/json'); + + if (method === 'POST' && (path === '/api' || path === '')) { + try { + res.statusCode = 200; + const request = JSON.parse(requestBody); + // console.log(request); + if (request.cmd) { + const cmd = request.cmd; + // console.log(`CMD is [${cmd}]`); + const data = request.data; + switch (cmd) { + case 'USERS': + response = JSON.stringify(this.getUsers()); + break; + case 'USER': + if (!data.id) { + res.statusCode = 400; + response = 'No user ID'; + throw new Error(response); + } + if (!uuid.validate(data.id)) { + res.statusCode = 400; + response = 'User ID is not valid'; + throw new Error(response); + } + // console.log(`User ID: [${data.id}]`); + const user = this.getUserById(data.id); + if (user) { + response = JSON.stringify(user); + // console.log(user); + } + else { + res.statusCode = 404; + response = 'User not found'; + throw new Error(response); + } + break; + case 'ADD': + // console.log('ADD' + data); + if (!data.username || data.username === '' || !data.age) { + res.statusCode = 400; + response = 'User data is not valid'; + throw new Error(response); + } + else { + const hobbies = Array.isArray(data.hobbies) ? data.hobbies : []; + const user = new User(uuid.v4(), data.username, data.age, hobbies); + this.createUser(user); + response = JSON.stringify(user); + res.statusCode = 201; + } + break; + case 'UPDATE': + // console.log('UPDATE' + data); + if (!data.id || !data.username || data.username === '' || !data.age) { + res.statusCode = 400; + response = 'User data is not valid'; + throw new Error(response); + } + if (!uuid.validate(data.id)) { + res.statusCode = 400; + response = 'User ID is not valid'; + throw new Error(response); + } + else { + const hobbies = Array.isArray(data.hobbies) ? data.hobbies : []; + const user = new User(data.id, data.username, data.age, hobbies); + const result = this.updateUser(data.id, user); + if (result) { + response = JSON.stringify(result); + } + else { + res.statusCode = 404; + response = 'User not found'; + throw new Error(response); + } + } + break; + case 'DELETE': + // console.log('DELETE' + data); + if (!data.id) { + res.statusCode = 400; + response = 'No user ID'; + throw new Error(response); + } + if (!uuid.validate(data.id)) { + res.statusCode = 400; + response = 'User ID is not valid'; + throw new Error(response); + } + // console.log(`User ID: [${data.id}]`); + const result = this.deleteUser(data.id); + if (result) { + res.statusCode = 204; + response = 'DELETED'; + } + else { + res.statusCode = 404; + response = 'User not found'; + throw new Error(response); + } + break; + default: + throw new Error(`Unknown command ${cmd}`); } - else { - res.statusCode = 404; - response = 'User not found'; - throw new Error(response); - } - break; - default: - throw new Error(`Unknown command ${cmd}`); + } + } + catch (error) { + // console.log("Incorrect request"); + // console.log(error); + // console.log(response); + if (res.statusCode === 200) res.statusCode = 400; } + // response = userService.getUsers(); + // res.statusCode = 200; + } else { + response = { error: 'Not found' }; + res.statusCode = 404; } - } - catch (error) { - console.log("Incorrect request"); - console.log(error); - console.log(response);// = JSON.stringify(error); - if (res.statusCode === 200) res.statusCode = 400; - } - // response = userService.getUsers(); - // res.statusCode = 200; - } else { - response = { error: 'Not found' }; - res.statusCode = 404; - } - res.end(response); - }); -}); - - -process.on('SIGTERM', () => { - process.exit(0); -}); - -process.on('exit', () => { - server.closeAllConnections(); - console.log("DB service done its work."); -}); - - -process.on('error', (error) => { - console.log(error); - process.exit(0); -}); - - -const createTestUsers = () => { - const json = [{ - id: uuid.v4(), - username: 'John', - age: 25, - hobbies: ['Ski', 'Videogames'], - }, - { - id: 'f8561522-0681-41b4-979b-3b1ef3ae09de',//uuid.v4(), - username: 'Emma', - age: 27, - hobbies: ['Writing', 'Gardening'], - }, - { - id: uuid.v4(), - username: 'Ben', - age: 52, - hobbies: ['Reading', 'Gardening'], + res.end(response); + }); + }); + + + server.listen(this.dbPort, () => { + console.log(`DB service is running on port ${this.dbPort}`); + }); } - ]; - json.forEach((element) => { - const user: User = User.fromJson(element); - // console.log(user); - dbService.createUser(user); - }); + + createTestUsers() { + const json = [{ + id: uuid.v4(), + username: 'John', + age: 25, + hobbies: ['Ski', 'Videogames'], + }, + { + id: 'f8561522-0681-41b4-979b-3b1ef3ae09de',//uuid.v4(), + username: 'Emma', + age: 27, + hobbies: ['Writing', 'Gardening'], + }, + { + id: uuid.v4(), + username: 'Ben', + age: 52, + hobbies: ['Reading', 'Gardening'], + } + ]; + + json.forEach((element) => { + const user: User = User.fromJson(element); + this.createUser(user); + }); + } } -createTestUsers(); -server.listen(dbPort, () => { - console.log(`Server is running on port ${dbPort}`); -}); +// process.on('SIGTERM', () => { +// process.exit(0); +// }); + +// process.on('exit', () => { +// server.closeAllConnections(); +// console.log("DB service done its work."); +// }); + + +// process.on('error', (error) => { +// console.log(error); +// process.exit(0); +// }); diff --git a/src/single_mode.ts b/src/single_mode.ts new file mode 100644 index 0000000..b574450 --- /dev/null +++ b/src/single_mode.ts @@ -0,0 +1,17 @@ +import { DbService } from "./db_service"; +import { UserService } from "./user_service"; +import dotenv from 'dotenv'; + +dotenv.config(); + +// Object.keys(process.env).forEach((key) => { +// if (key.startsWith('CRUD')) +// console.log(`${key}=[${process.env[key]}]`); +// }); + +const dbService = new DbService(); +//dbService.createTestUsers(); +dbService.start(); + +const userService = new UserService(); +userService.start(); diff --git a/src/user_service.ts b/src/user_service.ts index 4f08689..afdc756 100644 --- a/src/user_service.ts +++ b/src/user_service.ts @@ -1,36 +1,31 @@ -import http, { IncomingMessage, ServerResponse } from 'http'; +import http, { IncomingMessage, ServerResponse, RequestOptions } from 'http'; import { parse, URL } from 'url'; import * as uuid from 'uuid'; import { User } from './entities/User' -// import dotenv from 'dotenv'; - -// dotenv.config(); - -// Object.keys(process.env).forEach((key) => { -// if (key.startsWith('CRUD')) -// console.log(`${key}=[${process.env[key]}]`); -// }); - - -//const baseUrl = process.env['CURD_BASE_URL'] ? process.env['CURD_BASE_URL'] : 'http://localhost'; -const usPort = process.env['CRUD_US_PORT'] ? process.env['CRUD_US_PORT'] : '4000'; -const dbHost = process.env['CRUD_DB_HOST'] ? process.env['CRUD_DB_HOST'] : 'localhost'; -const dbPort = process.env['CRUD_DB_PORT'] ? process.env['CRUD_DB_PORT'] : '4100'; - -const db_req_options = { - hostname: dbHost, - port: dbPort, - path: '/api', - method: 'POST', -}; +export class UserService { + usPort: string;// = process.env['CRUD_US_PORT'] ? process.env['CRUD_US_PORT'] : '4000'; + dbHost: string;// = process.env['CRUD_DB_HOST'] ? process.env['CRUD_DB_HOST'] : 'localhost'; + dbPort: string;// = process.env['CRUD_DB_PORT'] ? process.env['CRUD_DB_PORT'] : '4100'; + db_req_options: RequestOptions; + + constructor() { + this.usPort = process.env['CRUD_US_PORT'] ? process.env['CRUD_US_PORT'] : '4000'; + this.dbHost = process.env['CRUD_DB_HOST'] ? process.env['CRUD_DB_HOST'] : 'localhost'; + this.dbPort = process.env['CRUD_DB_PORT'] ? process.env['CRUD_DB_PORT'] : '4100'; + this.db_req_options = { + hostname: this.dbHost, + port: this.dbPort, + path: '/api', + method: 'POST', + }; + } -class UserService { async createUser(user: User) { return new Promise((resolve, reject) => { - const req = http.request(db_req_options, (res) => { + const req = http.request(this.db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -66,7 +61,7 @@ class UserService { async getUsers() { return new Promise((resolve, reject) => { - const req = http.request(db_req_options, (res) => { + const req = http.request(this.db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -84,13 +79,13 @@ class UserService { } resolve(users); } catch (error) { - reject(error); + reject({ code: 500, message: 'Internal server error: unable to parse data' }); } }); }); req.on('error', (error) => { - reject(error); + reject({ code: 500, message: 'Internal server error' }); }); req.write(JSON.stringify({ 'cmd': 'USERS', 'data': '' })); @@ -101,7 +96,7 @@ class UserService { async getUserById(id: string) { return new Promise((resolve, reject) => { - const req = http.request(db_req_options, (res) => { + const req = http.request(this.db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -135,7 +130,7 @@ class UserService { async updateUser(id: string, user: User) { return new Promise((resolve, reject) => { - const req = http.request(db_req_options, (res) => { + const req = http.request(this.db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -170,7 +165,7 @@ class UserService { async deleteUser(id: string) { return new Promise((resolve, reject) => { - const req = http.request(db_req_options, (res) => { + const req = http.request(this.db_req_options, (res) => { let responseData = ''; res.on('data', (chunk) => { @@ -196,118 +191,119 @@ class UserService { req.end(); }); } -} - -const userService = new UserService(); -const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { - const { method, url } = req; + start() { + const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { + const { method, url } = req; - const parsedUrl = parse(url || '', true); - const path = parsedUrl.pathname || ''; + const parsedUrl = parse(url || '', true); + const path = parsedUrl.pathname || ''; - let requestBody = ''; + let requestBody = ''; - req.on('data', (chunk) => { - requestBody += chunk; - }); - - req.on('end', () => { - let response; - res.setHeader('Content-Type', 'application/json'); - console.log(`PATH: [${path}]`); - - if (method === 'GET' && (path === '/api/users' || path === '/api/users/')) { - userService.getUsers().then((resolve) => { - res.statusCode = 200; - response = resolve; - console.log("====" + response + "===="); - res.end(JSON.stringify(response)); - }).catch((error) => { - res.statusCode = 400; - throw error + req.on('data', (chunk) => { + requestBody += chunk; }); - } else if (method === 'GET' && path.startsWith('/api/users/')) { - const userId = path.split('/')[3]; - if (!userId || !uuid.validate(userId)) { - response = 'User ID is not valid'; - res.statusCode = 400; - res.end(response); - } - else { - userService.getUserById(userId).then((resolve) => { - response = resolve; - res.statusCode = 200; - res.end(JSON.stringify(response)); - }).catch((error) => { - response = 'User not found'; + + req.on('end', () => { + let response; + res.setHeader('Content-Type', 'application/json'); + // console.log(`PATH: [${path}]`); + + if (method === 'GET' && (path === '/api/users' || path === '/api/users/')) { + this.getUsers().then((resolve) => { + res.statusCode = 200; + response = resolve; + res.end(JSON.stringify(response)); + }).catch((reject) => { + // console.log(reject); + res.statusCode = reject.code; + res.end(reject.message); + }); + } else if (method === 'GET' && path.startsWith('/api/users/')) { + const userId = path.split('/')[3]; + if (!userId || !uuid.validate(userId)) { + response = 'User ID is not valid'; + res.statusCode = 400; + res.end(response); + } + else { + this.getUserById(userId).then((resolve) => { + response = resolve; + res.statusCode = 200; + res.end(JSON.stringify(response)); + }).catch((error) => { + response = 'User not found'; + res.statusCode = 404; + res.end(response); + }); + } + } else if (method === 'POST' && path === '/api/users') { + // console.log("POST"); + // console.log(requestBody); + const userInfo = JSON.parse(requestBody); + userInfo.id = ''; + let user = User.fromJson(userInfo); + this.createUser(user).then((resolve) => { + response = resolve; + res.statusCode = 201; + res.end(JSON.stringify(response)); + }).catch((error) => { + response = 'Unables to create user'; + res.statusCode = 400; + res.end(response); + }); + } else if (method === 'PUT' && path.startsWith('/api/users/')) { + // console.log("PUT"); + const userId = path.split('/')[3]; + if (!userId || !uuid.validate(userId)) { + response = 'User ID is not valid'; + res.statusCode = 400; + res.end(response); + } + else { + const userInfo = JSON.parse(requestBody); + userInfo.id = userId; + let user = User.fromJson(userInfo); + this.updateUser(userId, userInfo).then((resolve) => { + response = resolve; + res.statusCode = 200; + res.end(JSON.stringify(response)); + }).catch((error) => { + response = 'Unables to update user'; + res.statusCode = 404; + res.end(response); + }); + } + } else if (method === 'DELETE' && path.startsWith('/api/users/')) { + // console.log("DELETE"); + const userId = path.split('/')[3]; + if (!userId || !uuid.validate(userId)) { + response = 'User ID is not valid'; + res.statusCode = 400; + res.end(response); + } + else { + this.deleteUser(userId).then((resolve) => { + response = 'User deleted successfully'; + res.statusCode = 200; + res.end(response); + }).catch((error) => { + response = 'User not found'; + res.statusCode = 404; + res.end(response); + }); + } + } else { + response = 'Incorrect API entry point'; res.statusCode = 404; res.end(response); - }); - } - } else if (method === 'POST' && path === '/api/users') { - console.log("POST"); - console.log(requestBody); - const userInfo = JSON.parse(requestBody); - userInfo.id = ''; - let user = User.fromJson(userInfo); - userService.createUser(user).then((resolve) => { - response = resolve; - res.statusCode = 201; - res.end(JSON.stringify(response)); - }).catch((error) => { - response = 'Unables to create user'; - res.statusCode = 400; - res.end(response); + } }); - } else if (method === 'PUT' && path.startsWith('/api/users/')) { - console.log("PUT"); - const userId = path.split('/')[3]; - if (!userId || !uuid.validate(userId)) { - response = 'User ID is not valid'; - res.statusCode = 400; - res.end(response); - } - else { - const userInfo = JSON.parse(requestBody); - userInfo.id = userId; - let user = User.fromJson(userInfo); - userService.updateUser(userId, userInfo).then((resolve) => { - response = resolve; - res.statusCode = 200; - res.end(JSON.stringify(response)); - }).catch((error) => { - response = 'Unables to update user'; - res.statusCode = 404; - res.end(response); - }); - } - } else if (method === 'DELETE' && path.startsWith('/api/users/')) { - console.log("DELETE"); - const userId = path.split('/')[3]; - if (!userId || !uuid.validate(userId)) { - response = 'User ID is not valid'; - res.statusCode = 400; - res.end(response); - } - else { - userService.deleteUser(userId).then((resolve) => { - response = 'User deleted successfully'; - res.statusCode = 200; - res.end(response); - }).catch((error) => { - response = 'User not found'; - res.statusCode = 404; - res.end(response); - }); - } - } else { - response = { error: 'Not found' }; - res.statusCode = 404; - } - }); -}); - -server.listen(usPort, () => { - console.log(`Server is running on port ${usPort}`); -}); + }); + + server.listen(this.usPort, () => { + console.log(`Users service is running on port ${this.usPort}`); + }); + } +} From a3636a0305e9835c593779628c312ef542bc7de4 Mon Sep 17 00:00:00 2001 From: Dmitry Kostikov Date: Tue, 4 Jul 2023 03:57:48 +0400 Subject: [PATCH 4/4] Started imolenentation of multi-process mode. --- package.json | 28 +++++++++--- src/index.ts | 106 ++++++++------------------------------------ src/user_service.ts | 6 +-- 3 files changed, 43 insertions(+), 97 deletions(-) diff --git a/package.json b/package.json index 1c2ba74..9f0bcf2 100644 --- a/package.json +++ b/package.json @@ -3,24 +3,40 @@ "version": "1.0.0", "description": "Implementation of simple CRUD API using in-memory database", "main": "src/index.ts", + "engines": { + "node": ">=18.0.0" + }, "scripts": { "start:dev": "npm exec nodemon src/single_mode.ts", - "start:prod": "", - "test": "test" + "start:prod": "npx webpack --mode production", + "start:multi": "npm exec nodemon src/index.ts" }, "author": "docroot", "license": "ISC", "dependencies": { "dotenv": "^16.3.1", "ts-node": "^10.9.1", - "typescript": "^5.1.6", - "uuid": "^9.0.0", - "webpack": "^5.88.1" + "uuid": "^9.0.0" }, "devDependencies": { "@types/uuid": "^9.0.2", "eslint": "^8.44.0", "nodemon": "^1.19.4", - "ts-node-dev": "^2.0.0" + "ts-loader": "^9.4.4", + "ts-node-dev": "^2.0.0", + "typescript": "^5.1.6", + "webpack": "^5.88.1", + "webpack-cli": "^5.1.4" + }, + "compilerOptions": { + "outDir": "./dist", + "module": "es6", + "target": "es5", + "jsx": "react-jsx", + "lib": [ + "es6", + "dom" + ], + "sourceMap": true } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 57857d8..23b7576 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,88 +4,34 @@ import { availableParallelism } from 'node:os'; import process from 'process'; import child_process, { spawn, ChildProcess } from 'child_process'; import dotenv from 'dotenv'; +import { DbService } from "./db_service"; +import { UserService } from "./user_service"; +dotenv.config(); + +// Object.keys(process.env).forEach((key) => { +// if (key.startsWith('CRUD')) +// console.log(`${key}=[${process.env[key]}]`); +// }); const numCPUs = availableParallelism() - 1; let activeChildProcesses = 0; -const masterPort = 3000; -let currentWorkerPort = masterPort + 1; - +const masterPort = process.env['CRUD_US_PORT'] ? process.env['CRUD_US_PORT'] : '4000'; +let currentWorkerPort = parseInt(masterPort) + 1; if (cluster.isPrimary) { console.log(`Primary ${process.pid} is running`); - const child = spawn('ts-node', ['src/db_service.ts']); - - if (child) { - if (child.stdout) { - child.stdout.on('data', (data) => { - console.log(`Child process stdout: ${data}`); - }); - } - - child.on('exit', (code: number) => { - console.log(`Child process exited with code ${code}`); - }); - } - - const onExit = () => { - if (child !== null) { - child.kill('SIGTERM'); - } - const workers = cluster.workers; - if (workers) { - Object.values(workers).forEach((worker) => { - if (worker) { - worker.kill('SIGTERM'); - } - }); - } - } - - - process.stdin.on('SIGTERM', () => { - console.log("SIGTERM!!!!"); - process.exit(0); - }); - - process.stdin.on('data', (data) => { - if (String(data).trim() === 'exit') { - onExit(); - } - }); - - - - process.on('SIGINT', () => { - console.log('Ctrl+C!\n'); - process.kill(process.pid, 'SIGTERM'); - }); - - process.on('exit', () => { - onExit(); - console.log('EXIT!\n'); - }); + const dbService = new DbService(); + // dbService.createTestUsers(); + dbService.start(); for (let i = 0; i < numCPUs; i++) { - // console.log(`Cur port: [${currentWorkerPort}]`); - cluster.fork({ 'WRK_PORT': currentWorkerPort }); + cluster.fork({ 'CRUD_US_PORT': currentWorkerPort }); currentWorkerPort++; } - // const workers = cluster.workers; - // if (workers) { - // Object.values(workers).forEach((worker) => { - // // Event listener for 'exit' event - // if (worker) { - // worker.on('exit', (code, signal) => { - // console.log(`Worker ${worker.id} exited with code ${code} and signal ${signal}`); - // }); - // } - // }); - // } - cluster.on('exit', (worker: Worker, code: number, signal: string) => { console.log(`worker ${worker.process.pid} exited with code ${code} and signal ${signal}`); }); @@ -95,18 +41,10 @@ if (cluster.isPrimary) { `A worker is now connected to ${address.address}:${address.port}`); }); - // process.on('exit', () => { - // cluster.disconnect(() => { - // process.exit(0); - // }); - // // console.log("Done"); - // }); - - const loadBalancer = http.createServer((req: IncomingMessage, res: ServerResponse) => { const options = { hostname: 'localhost', - port: 3002, + port: 4001, path: req.url, method: req.method, headers: req.headers @@ -135,19 +73,11 @@ if (cluster.isPrimary) { } else if (cluster.isWorker) { - console.log(`Cur port: [${process.env.WRK_PORT}]`); + console.log(`Cur port: [${process.env.CRUD_US_PORT}]`); if (cluster.worker) console.log(`Worker ID: [${cluster.worker.id}]`); - http.createServer((req, res) => { - req.pipe(process.stdout); - res.writeHead(200); - res.end('hello world\n'); - }).listen(process.env.WRK_PORT); - - process.on('SIGTERM', () => { - process.exit(0); - }); + const userService = new UserService(); + userService.start(); console.log(`Worker ${process.pid} started`); } - diff --git a/src/user_service.ts b/src/user_service.ts index afdc756..98c589b 100644 --- a/src/user_service.ts +++ b/src/user_service.ts @@ -5,9 +5,9 @@ import { User } from './entities/User' export class UserService { - usPort: string;// = process.env['CRUD_US_PORT'] ? process.env['CRUD_US_PORT'] : '4000'; - dbHost: string;// = process.env['CRUD_DB_HOST'] ? process.env['CRUD_DB_HOST'] : 'localhost'; - dbPort: string;// = process.env['CRUD_DB_PORT'] ? process.env['CRUD_DB_PORT'] : '4100'; + usPort: string; + dbHost: string; + dbPort: string; db_req_options: RequestOptions; constructor() {