diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2926895 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +DB_HOST=localhost +DB_USER=root +DB_PASS= +DB_DATABASE=twitter_clone diff --git a/.gitignore b/.gitignore index 3fa1434..3427dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ typings/ .next *.spf +.env diff --git a/README.md b/README.md index fcdb8f2..dac01cb 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,71 @@ You cna run this collection in Postman with following button: Or Download and import in Postman from here: Download + +----- + +```sql +CREATE DATABASE twitter_clone; + +use twitter_clone; + +CREATE TABLE users( + id int NOT NULL AUTO_INCREMENT, + username varchar(15) NOT NULL, + password varchar(32) NOT NULL, + followers int DEFAULT 0, + following int DEFAULT 0, + tweets int DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE TABLE following( + id int NOT NULL AUTO_INCREMENT, + user1_id int REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + user2_id int REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (id) +); + +CREATE TABLE tweets( + id int NOT NULL AUTO_INCREMENT, + username varchar(15) NOT NULL, + user_id int REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + tweet varchar(140) NOT NULL, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +INSERT INTO USERS(username, password) VALUE('pankaj', MD5('pankaj')); + +INSERT INTO USERS(username, password) VALUE('tim', MD5('tim')); + +INSERT INTO USERS(username, password) VALUE('jim', MD5('jim')); + +INSERT INTO TWEETS(username, user_id, tweet) VALUE('pankaj', 1, 'Hello World Again!'); + +INSERT INTO FOLLOWING(user1_id, user2_id) VALUE(1, 2); +INSERT INTO FOLLOWING(user1_id, user2_id) VALUE(1, 3); +INSERT INTO FOLLOWING(user1_id, user2_id) VALUE(3, 1); + +SELECT * FROM tweets; + +# Followers + +SELECT +USER_INFO.*, username as user1_username +FROM (SELECT +user1_id, user2_id, username as user2_username +FROM FOLLOWING LEFT JOIN USERS ON user2_id = users.id +WHERE user1_id = 1) as USER_INFO +LEFT JOIN USERS ON user1_id = users.id ; + +# Following + +SELECT +USER_INFO.*, username as user1_username +FROM (SELECT +user1_id, user2_id, username as user2_username +FROM FOLLOWING LEFT JOIN USERS ON user2_id = users.id +WHERE user2_id = 1) as USER_INFO +LEFT JOIN USERS ON user1_id = users.id ; +``` diff --git a/Twitter Clone.postman_collection.json b/Twitter Clone.postman_collection.json index f9ca891..6d6705f 100644 --- a/Twitter Clone.postman_collection.json +++ b/Twitter Clone.postman_collection.json @@ -2,7 +2,7 @@ "info": { "_postman_id": "78149020-40a3-4657-80a9-7683bdae2c78", "name": "Twitter Clone", - "description": "This collection is created to help understand the Rest API creation on Node.js with Express and MySQL\n\nThe original article can be found here: https://time2hack.com/2019/09/creating-rest-api-in-node-js-with-express-and-mysql", + "description": "This collection is created to help understand the Rest API creation on Node.js with Express and MySQL\n\nThe original article can be found here: https://time2hack.com/2019/09/creating-rest-api-in-node-js-with-express-and-mysql/", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ @@ -223,12 +223,12 @@ "urlencoded": [ { "key": "username", - "value": "jim", + "value": "r_{{$randomUserName}}", "type": "text" }, { "key": "password", - "value": "jim", + "value": "random-password", "type": "text" } ] @@ -241,18 +241,6 @@ "path": [ "auth", "register" - ], - "query": [ - { - "key": "username", - "value": "", - "disabled": true - }, - { - "key": "password", - "value": "", - "disabled": true - } ] }, "description": "# Register New User\n---\n\n## Params\n\n`username`: `String`\n`password`: `String`\n\nReturns the new User's ID and Set's to Environment" @@ -319,12 +307,46 @@ "response": [] } ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "9f38fe7c-852d-47ea-94c0-a30a02fe80f1", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "fbd831b3-89ea-4af7-9e1c-b34bd57dba5e", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], "variable": [ { - "id": "363978da-cd5b-42e5-97e7-a8f9aab3fbd0", + "id": "163f6187-2a19-4028-b6d8-ec8709bc560b", + "key": "url", + "value": "http://localhost:3000", + "type": "string" + }, + { + "id": "974aa83a-3a22-483b-8363-e514a2742d5e", "key": "user", "value": "1", "type": "string" + }, + { + "id": "ddcb6d1f-4270-4364-bdcf-5e75ec9afeb4", + "key": "randomPassword", + "value": "random-password", + "type": "string" } ], "protocolProfileBehavior": {} diff --git a/app-middlewares/auth.js b/app-middlewares/auth.js index 38d88ba..7034a6e 100644 --- a/app-middlewares/auth.js +++ b/app-middlewares/auth.js @@ -5,19 +5,23 @@ const query = require('../helpers/query'); const router = express.Router(); const dbConfig = require('../dbConfig'); +const create = require('../crud/create'); + router.post('/register', async (req, res) => { const { username, password } = req.body; const conn = await connection(dbConfig).catch(e => {}); - const user = await query( + const result = await create( conn, - `INSERT INTO USERS(username, password) VALUE(?, MD5(?));`, - [username, password] + 'USERS', + ['username', 'password'], + [username, { toString: () => `MD5('${password}')`}] ); - if (user.insertId) { - res.send(await query(conn, `SELECT id, username FROM USERS WHERE ID=?`, [user.insertId])) - return; - } - res.send({ id: null, username: null }); + + const [user = {}] = result; + res.send({ + id: user.id || null, + username: user.username || null, + }); }); router.post('/login', async (req, res) => { diff --git a/crud/create.js b/crud/create.js new file mode 100644 index 0000000..a21bd6e --- /dev/null +++ b/crud/create.js @@ -0,0 +1,20 @@ +const query = require('../helpers/query'); +const valuesForQuery = require('../helpers/values-for-insert'); + +/** + * @param {} conn MySQL Connection reference + * @param {String} table Table to insert the values + * @param {[String]} columns Array of column names + * @param {[String]} values Array of values for those column names, can be multidientional + */ +module.exports = async (conn, table, columns, values) => { + const VALUES = valuesForQuery(values) + try { + const user = await query(conn, `INSERT INTO ${table}(${columns.join(', ')}) VALUES ${VALUES};`); + if (user.insertId) { + console.log(user.insertId); + return await query(conn, `SELECT * FROM ${table} WHERE ID=?`, [user.insertId]); + } + return user; + } catch(e) { console.log(e)} +} diff --git a/dbConfig.js b/dbConfig.js index 620f94e..71a972b 100644 --- a/dbConfig.js +++ b/dbConfig.js @@ -1,3 +1,7 @@ +const dotenv = require("dotenv") + +dotenv.config(); + // Get the Host from Environment or use default const host = process.env.DB_HOST || 'localhost'; diff --git a/helpers/query.js b/helpers/query.js index 5aff6b4..51f157d 100644 --- a/helpers/query.js +++ b/helpers/query.js @@ -8,4 +8,4 @@ module.exports = async (conn, q, params) => new Promise( resolve(result); } conn.query(q, params, handler); -}); +}).catch(console.log); diff --git a/helpers/values-for-insert.js b/helpers/values-for-insert.js new file mode 100644 index 0000000..f394259 --- /dev/null +++ b/helpers/values-for-insert.js @@ -0,0 +1,38 @@ +/** + * @param {[String]} values Array of values for those column names + */ +const valueForQuery = (_values) => { + const values = _values.map(item => { + let val; + switch (typeof item) { + case 'number': + return item; + case 'string': + val = item; + break; + default: + return item.toString(); + } + return `'${val}'`; + }) + return `(${values.join(', ')})`; +}; + +/** + * @param {[String]} values Array of values for those column names, can be multidientional + */ +const valuesForQuery = (values) => { + const value = values[0]; + let VALUES = ''; + if (value instanceof Array) { + VALUES = values.map(valueForQuery).join(', ') + } else { + VALUES = valueForQuery(values); + } + return VALUES; +} + +module.exports = { + valueForQuery, + valuesForQuery, +} diff --git a/package-lock.json b/package-lock.json index fe2c372..ecae6e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "2.1.24", + "mime-types": "~2.1.24", "negotiator": "0.6.2" } }, @@ -29,15 +29,15 @@ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { "bytes": "3.1.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", + "depd": "~1.1.2", "http-errors": "1.7.2", "iconv-lite": "0.4.24", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.7.0", "raw-body": "2.4.0", - "type-is": "1.6.18" + "type-is": "~1.6.17" } }, "bytes": { @@ -96,6 +96,11 @@ "resolved": "https://registry.npmjs.org/diff_match_patch/-/diff_match_patch-0.1.1.tgz", "integrity": "sha1-0/FNW3b7S1qc9EcGJh2ttb2X7bw=" }, + "dotenv": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.1.0.tgz", + "integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -129,36 +134,36 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "1.3.7", + "accepts": "~1.3.7", "array-flatten": "1.1.1", "body-parser": "1.19.0", "content-disposition": "0.5.3", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.2", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.3", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.5", + "proxy-addr": "~2.0.5", "qs": "6.7.0", - "range-parser": "1.2.1", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", "send": "0.17.1", "serve-static": "1.14.1", "setprototypeof": "1.1.1", - "statuses": "1.5.0", - "type-is": "1.6.18", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" } }, "finalhandler": { @@ -167,12 +172,12 @@ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.3", - "statuses": "1.5.0", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" } }, "formaterrors": { @@ -204,10 +209,10 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { - "depd": "1.1.2", + "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.1", - "statuses": "1.5.0", + "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, @@ -216,7 +221,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "inherits": { @@ -293,8 +298,8 @@ "resolved": "https://registry.npmjs.org/nodejs-md5/-/nodejs-md5-1.1.0.tgz", "integrity": "sha512-U4oSRfiATjfqBM37c5MBSo7JyLnHcWsD4xvyMUJw4AdPCOg0aMvB40M8H50yvHLMbPokY7HufPPokEJw+AwlpQ==", "requires": { - "exceptions": "0.1.1", - "fs": "0.0.1-security" + "exceptions": "^0.1.1", + "fs": "^0.0.1-security" } }, "on-finished": { @@ -325,7 +330,7 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", "requires": { - "forwarded": "0.1.2", + "forwarded": "~0.1.2", "ipaddr.js": "1.9.0" } }, @@ -355,13 +360,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.1", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "safe-buffer": { @@ -380,18 +385,18 @@ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.7.2", + "http-errors": "~1.7.2", "mime": "1.6.0", "ms": "2.1.1", - "on-finished": "2.3.0", - "range-parser": "1.2.1", - "statuses": "1.5.0" + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { "ms": { @@ -406,9 +411,9 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.3", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", "send": "0.17.1" } }, @@ -437,7 +442,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } }, "toidentifier": { @@ -451,7 +456,7 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.24" + "mime-types": "~2.1.24" } }, "unpipe": { diff --git a/package.json b/package.json index 1042af6..586d319 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "homepage": "https://github.com/time2hack/express-mysql#readme", "dependencies": { "body-parser": "^1.19.0", + "dotenv": "^8.1.0", "express": "^4.17.1", "mysql": "^2.17.1", "nodejs-md5": "^1.1.0"