//node js create api server without any dependency github autopilot // must use https and http module // https module is used for https server // http module is used for http server // https module is used for https server // npm install sqlite3 // sudo node index-local.js // sudo nodemon index-local.js // sudo nodemon index-local.js npm install nodemon -g /** * database layout * post - title, content, date, user_id, tag, url * user - name, color, background color, font size, font family, font weight, font style, data theme * comment on post - post_id, user_id, comment, date * tag - post_title, post_content, post_date, post_user_id, post_tag, post_comment * tag/post_title/comments */ const http = require('http'); const https = require('https'); const fs = require('fs'); const url = require('url'); const StringDecoder = require('string_decoder').StringDecoder; const crypto = require('crypto'); const console = require('console'); //const hostname = '0.0.0.0'; const hostname = '127.0.0.1'; //const port = 443; const port = 8000; const domain ='xxxx.xxxx' const MAX_CHARACTERS = 2048; const secret_hash_key = 'xxxxxxxxxxxxxxxxxxxxx'; //read the key and certificate /**const options = { key: fs.readFileSync('/xxxxxx_private_key.key'), cert: fs.readFileSync('/home/xxxxxxx_ssl_certificate.cer') };**/ //const options = { // key: fs.readFileSync('/home/xxxxxxprivate_key.key'), //cert: fs.readFileSync('/home/xxxxxxx_ssl_certificate.cer') //}; const user_file_path = './user.json'; const post_file_path = './post.json'; const tag_file_path = './tag.json'; const comment_file_path = './comment.json'; //check if the user.json file does not exist function isValidHttpsUrl(string) { let url; try { url = new URL(string); } catch (_) { return false; } return url.protocol === "https:"; } // Redirect from http port 80 to https /**http.createServer(function (req, res) { res.writeHead(301, { "Location": "https://www." + domain }); res.end(); }).listen(80); **/ //create vanila http server that handles buffer overflow and handles routes using regexp //create https server ssl tsl http.createServer(function (req, res) { //https.createServer(options, function (req, res) { let buffer = ''; const decoder = new StringDecoder('utf-8'); req.on('data', function(data) { buffer += decoder.write(data); //check if the buffer is too large if (buffer.length > MAX_CHARACTERS) { // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST req.destroy(); } }); req.on('end', function() { buffer += decoder.end(); const parsedUrl = url.parse(req.url, true); const path = parsedUrl.pathname; let ip_address = req.headers['x-forwarded-for'] || req.socket.remoteAddress; let user_agent = req.headers['user-agent'] || 'guest'; //console.log('ip_address: ' + ip_address); //console.log('user_agent: ' + user_agent); if(ip_address == '' || ip_address == null || ip_address == undefined){ ip_address = 'guest'; } if(user_agent == '' || user_agent == null || user_agent == undefined){ user_agent = 'guest'; } let user_hash = crypto.createHash('sha256').update(ip_address + user_agent + secret_hash_key).digest('hex'); if(path == '/robots.txt'){ res.writeHead(200, {'Content-Type': 'text/plain'}); //allow all robots res.end('User-agent: *\nAllow: /'); } else if(path == '/favicon.ico'){ fs.readFile('./favicon.ico', function(err, data) { if(err) { console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else { res.writeHead(200, {'Content-Type': 'image/x-icon'} ); res.end(data); } }); } else if(path == '/api/admin/check'){ // check if the files are created if (!fs.existsSync(user_file_path)) { //file does not exist //create user.json file before starting the server fs.writeFileSync(user_file_path, JSON.stringify([]), 'utf8'); } //check if the user.json file does not exist if (!fs.existsSync(post_file_path)) { //file does not exist //create user.json file fs.writeFileSync(post_file_path, JSON.stringify([]), 'utf8'); } if(!fs.existsSync(tag_file_path)){ fs.writeFileSync(tag_file_path, JSON.stringify([]), 'utf8'); } if(!fs.existsSync(comment_file_path)){ fs.writeFileSync(comment_file_path, JSON.stringify([]), 'utf8'); } } else if(path == '/api/user/check') { //check if user is in the database //get ip address of the client fs.readFile(user_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else { obj = JSON.parse(data); //now it an object let user_id = -1; //check if the user is the json file using a loop for(let i = 0; i < obj.length; i++){ //if the user is in the json file then get the user info from the database if(obj[i]['user_hash'] == user_hash){ //get user info from the database using ip address user_id = obj[i]['id']; break; } } //if the user id is not in the json file, then add the user to the json file starting at 0 if(user_id == -1){ obj.push({'ip_address':ip_address, 'user_agent': user_agent, 'id': obj.length, 'user_hash': user_hash, 'date': new Date().toISOString().slice(0, 19).replace('T', ' '), 'post_count': 0, 'background_color': '#ffffff', 'text_color': '#000000', 'font_size': '12px', 'font_family': 'Arial', 'font_weight': 'normal', 'font_style': 'normal', 'data_theme': 'dark' }); //write the user.json file fs.writeFile(user_file_path, JSON.stringify(obj), 'utf8', function(err){ if(err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } }); //redirect to home page so will get user id again user_id = obj.length; //send the user id to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(obj[user_id])); } else{ //send the user id to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(obj[user_id])); } } }); } else if(path == '/api/user/profile'){ fs.readFile(user_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else { obj = JSON.parse(data); //now it an object let user_id = -1; //check if the user is the json file using a loop for(let i = 0; i < obj.length; i++){ //if the user is in the json file then get the user info from the database if(obj[i]['user_hash'] == user_hash){ //get user info from the database using ip address user_id = obj[i]['id']; break; } } //if the user id is not in the json file, then add the user to the json file starting at 0 if(user_id == -1){ res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else{ //send the user profile to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(obj[user_id])); } } }); } else if(path == '/api/user/update/data_theme'){ //form data //get json data from the request let json_data = JSON.parse(buffer); fs.readFile(user_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else { //find user in the json file obj = JSON.parse(data); //now it an object let user_id = -1; //check if the user is the json file using a loop for(let i = 0; i < obj.length; i++){ //if the user is in the json file then get the user info from the database if(obj[i]['user_hash'] == user_hash){ //get user info from the database using ip address user_id = obj[i]['id']; break; } } //if the user id is not in the json file, then error if(user_id == -1){ res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else{ //update the user profile obj[user_id]['data_theme'] = json_data['data_theme']; //write the user.json file fs.writeFile(user_file_path, JSON.stringify(obj), 'utf8', function(err){ if(err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } }); //send the user profile to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(obj[user_id])); } } }); } else if(path == '/api/post/add') { //get json data from the request let json_data = JSON.parse(buffer); //get the post title let post_title = json_data['post_title']; let post_background_color = json_data['post_background_color']; let post_text_color = json_data['post_text_color']; //get the post content let post_content = json_data['post_content']; //get the post url let post_url = json_data['post_url']; //check if post url is valid if(!isValidHttpsUrl(post_url)){ post_url=''; } fs.readFile(post_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else { let posts = JSON.parse(data); //now it an object posts.push({'post_title':post_title, 'post_content':post_content, 'post_url':post_url, 'post_id': posts.length, 'post_background_color': post_background_color, 'post_text_color': post_text_color, 'post_user_hash':user_hash, 'post_created_date': new Date().toISOString().slice(0, 19).replace('T', ' '), 'post_updated_date': new Date().toISOString().slice(0, 19).replace('T', ' '), 'post_comments': [], 'post_is_deleted': false, 'liked_by': [] }); fs.writeFile(post_file_path, JSON.stringify(posts),'utf8', function(err){ if(err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } }); //send the user profile to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(posts[posts.length-1])); } }); } else if(path == '/api/post/get/all') { fs.readFile(post_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } else { let posts = JSON.parse(data); //now it an object //send the posts to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(posts)); } }); } else if(path == '/api/post/get') { //get json data from the request let json_data = JSON.parse(buffer); let post_id = json_data['post_id']; fs.readFile(post_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify({'success':false,'message':'error reading post file'})); } else { let posts = JSON.parse(data); //now it an object //send the posts to the client res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(posts[post_id])); } }); } else if(path == '/api/post/copy') { //get json data from the request let json_data = JSON.parse(buffer); let post_id = json_data['post_id']; fs.readFile(post_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify({'success':false,'message':'error reading post file'})); } else { let posts = JSON.parse(data); //now it an object //copy the post, warning array values are copied by reference by default //need to copy the array values by value let post = posts[post_id]; let post_value = Object.assign({}, post); //copies all the values of the post object to {} and assigns it to post_value post_value['post_id'] = posts.length; post_value['post_user_hash'] = user_hash; post_value['post_updated_date'] = new Date().toISOString().slice(0, 19).replace('T', ' '); post_value['post_comments'] = Object.assign([], post['post_comments']); //copy the liked_by by value post_value['liked_by'] = Object.assign([], post['liked_by']); //copy the liked_by by value posts.push(post_value); //write the posts to the file fs.writeFile(post_file_path, JSON.stringify(posts), 'utf8', function(err){ if(err){ console.log(err); res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify({'success':false,'message':'error writing post file'})); } else { res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify(posts)); } }); } }); } else if(path == '/api/post/update') { //get json data from the request let json_data = JSON.parse(buffer); //get the post title let post_title = json_data['post_title']; let post_background_color = json_data['post_background_color']; let post_text_color = json_data['post_text_color']; let post_content = json_data['post_content']; let post_url = json_data['post_url']; //check if post url is valid if(!isValidHttpsUrl(post_url)){ post_url=''; } //get the post id let post_id = json_data['post_id']; //update the post in the json database fs.readFile(post_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify({'success':false,'message':'error reading post file'})); } else { let posts = JSON.parse(data); //now it an object let index_id = -1; for(let i = 0; i < posts.length; i++){ //if the post id is found if(posts[i]['post_id'] == post_id){ index_id = i; break; } } if(index_id != -1){ //update the post posts[index_id]['post_title'] = post_title; posts[index_id]['post_content'] = post_content; posts[index_id]['post_url'] = post_url; posts[index_id]['post_background_color'] = post_background_color; posts[index_id]['post_text_color'] = post_text_color; posts[index_id]['post_updated_date'] = new Date().toISOString().slice(0, 19).replace('T', ' '); //write the post.json file fs.writeFile(post_file_path, JSON.stringify(posts),'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify({'success':false,'message':'error writing post file'})); } else { res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify(posts)); } }); }else{ //respond with error message json res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({'success':false,'message':'post not found'})); } } }); } else if(path == '/api/post/delete'){ let json_data = JSON.parse(buffer); let post_id = json_data['post_id']; //delete the post in the json database by blanking out all values fs.readFile(post_file_path, 'utf8', function readFileCallback(err, data){ if (err){ console.log(err); res.writeHead(404, {'Content-Type': 'application/json'}); return res.end(JSON.stringify({'success':false,'message':'error reading post file'})); } else { let posts = JSON.parse(data); //now it an object //loop through the posts to find the post id let index_id = -1; for(let i = 0; i < posts.length; i++){ //if the post id is found if(posts[i]['post_id'] == post_id){ index_id = i; break; } } console.log(index_id); if(index_id != -1){ //delete the post posts[index_id]['post_title'] = ''; posts[index_id]['post_content'] = ''; posts[index_id]['post_url'] = ''; posts[index_id]['post_background_color'] = ''; posts[index_id]['post_text_color'] = ''; posts[index_id]['post_is_deleted'] = true; posts[index_id]['post_updated_date'] = new Date().toISOString().slice(0, 19).replace('T', ' '); //write the post.json file fs.writeFile(post_file_path, JSON.stringify(posts),'utf8', function(err){ if(err){ console.log(err); res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({'success':false,'message':'error writing post.json'})); } else{ //respond with posts json res.writeHead(200, {'Content-Type': 'application/json'}); return res.end(JSON.stringify(posts)); } }); }else{ //respond with error message json res.writeHead(200, {'Content-Type': 'application/json'}); res.end(JSON.stringify({'success':false,'message':'post not found'})); } } }); } else if(path.match(/^\/[A-Za-z0-9_-]+?$/)){ //get the post title, watch out if tag title is api let tag_title = path.split('/')[1]; if(tag_title == 'api'){ //redirect to home page if api is the tag title res.writeHead(301, { "Location": "/" }); res.end('api'); return; } //check if the tag title exists let tags = JSON.parse(fs.readFileSync(tag_file_path, 'utf8')); //loop through the posts to find the post id let tag_id = -1; for(let i = 0; i < tags.length; i++){ //if the tag title is not found if(tags[i]['tag_title'] == tag_title){ tag_id = tags[i]['tag_id']; break; } } //if the tag id is not found if(tag_id == -1){ //redirect to home page res.writeHead(301, { "Location": "/" }); res.end('post not found'); return; } //show the first 20 posts with the tag date desc let posts = JSON.parse(fs.readFileSync(tag_file_path, 'utf8')); let post_json = []; for(let i = posts.length - 1; i >= 0; i--){ //if the tag id is found in the post , add it to the post_json if(posts[i]['post_tag_id'] == tag_id){ post_json.push(posts[i]); } //if the post_json is 20 then break if(post_json.length == 20){ break; } } res.writeHead(200, {'Content-Type': 'text/json'}); res.end(JSON.stringify(post_json)); } else if(path.match(/^\/[A-Za-z0-9_-]+?\/[A-Za-z0-9_-]+?$/)){ //get the tag title and post title /tag_title/post_title let tag_title = path.split('/')[1]; if(tag_title == 'api'){ //redirect to home page res.writeHead(301, { "Location": "/" }); res.end('api'); return; } //check if the tag title exists let tags = JSON.parse(fs.readFileSync(tag_file_path, 'utf8')); //loop through the tags to find the tag id let tag_id = -1; for(let i = 0; i < tags.length; i++){ //if the tag title is not found if(tags[i]['tag_title'] == tag_title){ tag_id = tags[i]['tag_id']; break; } } //if the tag id is not found if(tag_id == -1){ //redirect to home page res.writeHead(301, { "Location": "/" }); res.end('tag not found'); return; } //get the post_title let post_title = path.split('/')[2]; let post_id = -1; //loop through the posts to find the post id let posts = JSON.parse(fs.readFileSync(post_file_path, 'utf8')); for(let i = 0; i < posts.length; i++){ //if the post title is not found if(posts[i]['post_title'] == post_title){ post_id = posts[i]['post_id']; break; } } //if the post id is not found if(post_id == -1){ //redirect to home page res.writeHead(301, { "Location": "/" }); res.end('post not found'); return; } else { //get the last 20 comments with the post id let comments = JSON.parse(fs.readFileSync(comment_file_path, 'utf8')); let comment_json = []; for(let i = 0; i < comments.length; i++){ //if the post id is found in the comment , add it to the comment_json if(comments[i]['comment_post_id'] == post_id){ comment_json.push(comments[i]); } //if the comment_json is 20 then break if(comment_json.length == 20){ break; } } res.writeHead(200, {'Content-Type': 'text/json'}); res.end(JSON.stringify(comment_json)); } } else { //if the path is empty then redirect to the home page res.writeHead(200, {"content-type":"text/html"}); //get file content as the template fs.readFile('index.html', function(err, data) { if (err) { console.log(err); }else{ res.writeHead(200, {"content-type":"text/html"}); res.end(data); } }); } }); //end of req.on end function }).listen();