functional RESTful API
This commit is contained in:
parent
e5ac1a96bb
commit
7ea24df56b
14 changed files with 2677 additions and 1 deletions
65
.gitignore
vendored
Normal file
65
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# deta-space internal files
|
||||||
|
.space
|
||||||
|
Spacefile
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
# express-blog-api
|
# express-blog-api
|
||||||
|
|
||||||
Backend with two different front-ends for accessing and editing blog posts. One of the front-end sites will be for people that want to read and comment on posts while the other one will allow editing, writing and publishing of posts.
|
RESTful blog API implemented in Express. Has POST/GET/PUT/DELETE methods for all posts and comments.
|
||||||
|
Post Creation, Updates and Deletion require jwt token returned when post completed at /api/login.
|
||||||
|
Hosted on Deta Space at https://expressblogapi-1-v2871156.deta.app/api/
|
||||||
|
|
|
||||||
41
app.js
Normal file
41
app.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
const express = require("express");
|
||||||
|
const path = require("path");
|
||||||
|
const cors = require("cors");
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const apiRouter = require("./routes/api.js");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// get db
|
||||||
|
const mongoDB = `mongodb+srv://${process.env.USER}:${process.env.PASS}@odin.eftl02o.mongodb.net/blogAPI?retryWrites=true&w=majority`;
|
||||||
|
// or throw error
|
||||||
|
main().catch((err) => console.log(err));
|
||||||
|
async function main() {
|
||||||
|
await mongoose.connect(mongoDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set paths
|
||||||
|
app.use(
|
||||||
|
"/styles/css",
|
||||||
|
express.static(path.join(__dirname, "node_modules/bootstrap/dist/css"))
|
||||||
|
);
|
||||||
|
app.use(express.static(path.join(__dirname, "public")));
|
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set("views", path.join(__dirname, "views"));
|
||||||
|
app.set("view engine", "ejs");
|
||||||
|
|
||||||
|
// parsing
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(express.static(path.join(__dirname, "public")));
|
||||||
|
|
||||||
|
// cors
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
// routing
|
||||||
|
app.use("/api", apiRouter);
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
90
bin/www
Executable file
90
bin/www
Executable file
|
|
@ -0,0 +1,90 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = require('../app');
|
||||||
|
var debug = require('debug')('express-blog-api:server');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3000');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
var bind = typeof addr === 'string'
|
||||||
|
? 'pipe ' + addr
|
||||||
|
: 'port ' + addr.port;
|
||||||
|
debug('Listening on ' + bind);
|
||||||
|
}
|
||||||
155
controllers/comment.js
Normal file
155
controllers/comment.js
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
const { default: mongoose } = require("mongoose");
|
||||||
|
const asyncHandler = require("express-async-handler");
|
||||||
|
const { body, validationResult } = require("express-validator");
|
||||||
|
|
||||||
|
const Comment = require("../models/comment.js");
|
||||||
|
|
||||||
|
// makes new comment - C
|
||||||
|
exports.post = [
|
||||||
|
// Validate and sanitize text
|
||||||
|
body("text", "Please enter comment!").isLength({ min: 1 }).trim().escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize text
|
||||||
|
body("author", "Please enter comment author!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize password
|
||||||
|
body("password", "Please enter password for future comment modification!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Process request after authentication, validation and sanitization
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
|
||||||
|
// if there are validation errors, render with errors
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Comment produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// else data is valid, make post object
|
||||||
|
const comment = new Comment({
|
||||||
|
date: new Date(),
|
||||||
|
text: req.body.text,
|
||||||
|
author: req.body.author,
|
||||||
|
post: req.params.postID,
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
password: req.body.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
// save to DB
|
||||||
|
await comment.save();
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Comment uploaded!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// returns comment in json format - R
|
||||||
|
exports.get = asyncHandler(async (req, res, next) => {
|
||||||
|
const comment = await Comment.findOne({ _id: req.params.commentID })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
return res.status(200).json({ comment });
|
||||||
|
});
|
||||||
|
|
||||||
|
// updates comment - U
|
||||||
|
exports.put = [
|
||||||
|
// Validate and sanitize text
|
||||||
|
body("text", "Please enter comment!").isLength({ min: 1 }).trim().escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize author name
|
||||||
|
body("author", "Please enter comment author!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize password
|
||||||
|
body("password", "Please enter password to modify comment!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Process request after sanitization and validation
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
|
||||||
|
// if there are validation errors, render with errors
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Comment produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbComment = await Comment.findOne({ _id: req.params.commentID })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (req.body.author === dbComment.author) {
|
||||||
|
if (req.body.password === dbComment.password) {
|
||||||
|
const comment = {
|
||||||
|
date: new Date(),
|
||||||
|
text: req.body.text,
|
||||||
|
author: req.body.author,
|
||||||
|
post: dbComment.post,
|
||||||
|
_id: dbComment._id,
|
||||||
|
password: dbComment.password,
|
||||||
|
};
|
||||||
|
|
||||||
|
await comment.save();
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Post updated!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.status(401).json({
|
||||||
|
message: "Comments can only be updated with their original password!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.status(401).json({
|
||||||
|
message: "Comments can only be updated by their original author!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// deletes a comment - D
|
||||||
|
exports.delete = [
|
||||||
|
// Validate and sanitize password
|
||||||
|
body("password", "Please enter comment deletion password!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Process request after authentication, validation and sanitization
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
|
||||||
|
// if there are validation errors, render with errors
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Comment password produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbComment = await Comment.findOne({ _id: req.params.commentID })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (req.body.password === dbComment.password) {
|
||||||
|
await Comment.findByIdAndDelete({ _id: req.params.commentID }).exec();
|
||||||
|
return res.status(200).json({ message: "Comment deleted!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(401).json({
|
||||||
|
message: "Incorrect password!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
24
controllers/login.js
Normal file
24
controllers/login.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
const asyncHandler = require("express-async-handler");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
const User = require("../models/user.js");
|
||||||
|
|
||||||
|
let opts = {};
|
||||||
|
|
||||||
|
exports.post = asyncHandler(async (req, res, next) => {
|
||||||
|
const admin = await User.findOne({}).lean().exec(); // only one user in DB - admin. pull it up, jamie!
|
||||||
|
const { username, password } = req.body; // get fields from body
|
||||||
|
if (username === admin.username) {
|
||||||
|
console.log();
|
||||||
|
const match = await bcrypt.compare(password, admin.password); // compare bcrypt hashed passwords
|
||||||
|
if (match) {
|
||||||
|
opts.expiresIn = 120;
|
||||||
|
const token = jwt.sign({ username }, process.env.SECRET_KEY, opts); // create token and return below
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Authentication complete",
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.status(401).json({ message: "Authentication failed" }); // get rekt nerd
|
||||||
|
});
|
||||||
124
controllers/post.js
Normal file
124
controllers/post.js
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
const { default: mongoose } = require("mongoose");
|
||||||
|
const asyncHandler = require("express-async-handler");
|
||||||
|
const { body, validationResult } = require("express-validator");
|
||||||
|
|
||||||
|
const Post = require("../models/post.js");
|
||||||
|
const Comment = require("../models/comment.js");
|
||||||
|
|
||||||
|
const passport = require("passport");
|
||||||
|
const jwtStrategy = require("../strategy/jwt.js");
|
||||||
|
passport.use(jwtStrategy);
|
||||||
|
|
||||||
|
// returns json object with ALL posts and comments
|
||||||
|
exports.index = asyncHandler(async (req, res, next) => {
|
||||||
|
const dbPosts = await Post.find().lean().exec();
|
||||||
|
const posts = [];
|
||||||
|
for (let i = 0; i < dbPosts.length; i++) {
|
||||||
|
const comments = await Comment.find({ post: dbPosts[i]._id });
|
||||||
|
const post = {
|
||||||
|
title: dbPosts[i].title,
|
||||||
|
date: dbPosts[i].date,
|
||||||
|
text: dbPosts[i].text,
|
||||||
|
author: dbPosts[i].author,
|
||||||
|
published: dbPosts[i].published,
|
||||||
|
_id: dbPosts[i]._id,
|
||||||
|
comments: comments,
|
||||||
|
};
|
||||||
|
posts.push(post);
|
||||||
|
}
|
||||||
|
return res.status(200).json({ posts });
|
||||||
|
});
|
||||||
|
|
||||||
|
// makes new post - C
|
||||||
|
(exports.post = passport.authenticate("jwt", { session: false })),
|
||||||
|
[
|
||||||
|
// Validate and sanitize title
|
||||||
|
body("title", "Please enter blog post title!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize text
|
||||||
|
body("text", "Please enter blog post text!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Process request after authentication, validation and sanitization
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
|
||||||
|
// if there are validation errors, render with errors
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
res.render("/admin/create"),
|
||||||
|
{
|
||||||
|
errors: errors.array(),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else data is valid, make post object
|
||||||
|
const post = new Post({
|
||||||
|
title: req.body.title,
|
||||||
|
date: new Date(),
|
||||||
|
text: req.body.text,
|
||||||
|
author: req.user.username,
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// save to DB
|
||||||
|
await post.save();
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Post created!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// returns post in json format - R
|
||||||
|
exports.get = asyncHandler(async (req, res, next) => {
|
||||||
|
const post = await Post.findOne({ _id: req.params.postID }).lean().exec();
|
||||||
|
return res.status(200).json({ post });
|
||||||
|
});
|
||||||
|
|
||||||
|
// updates post - U
|
||||||
|
(exports.put = passport.authenticate("jwt", { session: false })),
|
||||||
|
[
|
||||||
|
// Validate and sanitize title
|
||||||
|
body("title", "Please enter blog post title!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize text
|
||||||
|
body("text", "Please enter blog post text!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Process request after sanitization and validation
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
const dbPost = await Post.findOne({ _id: req.params.postID })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
const post = {
|
||||||
|
title: req.body.title,
|
||||||
|
date: new Date(),
|
||||||
|
text: req.body.text,
|
||||||
|
author: dbPost.author,
|
||||||
|
published: dbPost.published,
|
||||||
|
_id: dbPost._id,
|
||||||
|
};
|
||||||
|
await post.save();
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Post updated!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// deletes a post - D
|
||||||
|
(exports.delete = passport.authenticate("jwt", { session: false })),
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
await Post.findByIdAndDelete({ _id: req.params.postID }).exec();
|
||||||
|
return res.status(200).json({ message: "Post deleted!" });
|
||||||
|
});
|
||||||
20
models/comment.js
Normal file
20
models/comment.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
const CommentSchema = new Schema({
|
||||||
|
date: { type: Date, required: true },
|
||||||
|
text: { type: String, required: true },
|
||||||
|
author: { type: String, required: true },
|
||||||
|
post: { type: mongoose.ObjectId, required: true },
|
||||||
|
_id: { type: mongoose.ObjectId, required: true },
|
||||||
|
password: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Virtual for comment RESTful functions
|
||||||
|
CommentSchema.virtual("url").get(function () {
|
||||||
|
// We don't use an arrow function as we'll need the this object
|
||||||
|
return `/api/${post}/${this._id}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export model
|
||||||
|
module.exports = mongoose.model("Comment", CommentSchema);
|
||||||
20
models/post.js
Normal file
20
models/post.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
const PostSchema = new Schema({
|
||||||
|
title: { type: String, required: true },
|
||||||
|
date: { type: Date, required: true },
|
||||||
|
text: { type: String, required: true },
|
||||||
|
author: { type: String, required: true },
|
||||||
|
published: { type: Boolean, required: true },
|
||||||
|
_id: { type: mongoose.ObjectId, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Virtual for message URL
|
||||||
|
PostSchema.virtual("url").get(function () {
|
||||||
|
// We don't use an arrow function as we'll need the this object
|
||||||
|
return `/api/${this._id}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export model
|
||||||
|
module.exports = mongoose.model("Post", PostSchema);
|
||||||
12
models/user.js
Normal file
12
models/user.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
const mongoose = require("mongoose");
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
const UserSchema = new Schema({
|
||||||
|
username: { type: String, required: true },
|
||||||
|
password: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// we will NOT be exposing any URLs for users
|
||||||
|
|
||||||
|
// Export model
|
||||||
|
module.exports = mongoose.model("User", UserSchema);
|
||||||
2039
package-lock.json
generated
Normal file
2039
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
package.json
Normal file
27
package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "express-blog-api",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./bin/www"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"bootstrap": "^5.3.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"debug": "~2.6.9",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
|
"ejs-lint": "^2.0.0",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-async-handler": "^1.2.0",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
|
"express-validator": "^7.0.1",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mongoose": "^7.5.1",
|
||||||
|
"passport": "^0.6.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"passport-local-mongoose": "^8.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
37
routes/api.js
Normal file
37
routes/api.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
const login_controller = require("../controllers/login.js");
|
||||||
|
const post_controller = require("../controllers/post.js");
|
||||||
|
const comment_controller = require("../controllers/comment.js");
|
||||||
|
|
||||||
|
// list all posts and append comments to each post based on id, return as json
|
||||||
|
router.get("/", post_controller.index);
|
||||||
|
|
||||||
|
// login page - should work with json
|
||||||
|
router.post("/login", login_controller.post);
|
||||||
|
|
||||||
|
// post post
|
||||||
|
router.post("/new_post", post_controller.post);
|
||||||
|
|
||||||
|
// post get
|
||||||
|
router.get("/:postID", post_controller.get);
|
||||||
|
|
||||||
|
// post put
|
||||||
|
router.put("/:postID", post_controller.put);
|
||||||
|
|
||||||
|
// post delete
|
||||||
|
router.delete("/:postID", post_controller.delete);
|
||||||
|
|
||||||
|
// comment post
|
||||||
|
router.post("/:postID/new_comment", comment_controller.post);
|
||||||
|
|
||||||
|
// comment get
|
||||||
|
router.get("/:postID/:commentID", comment_controller.get);
|
||||||
|
|
||||||
|
// comment put
|
||||||
|
router.put("/:postID/:commentID", comment_controller.put);
|
||||||
|
|
||||||
|
// comment delete
|
||||||
|
router.delete("/:postID/:commentID", comment_controller.delete);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
20
strategy/jwt.js
Normal file
20
strategy/jwt.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
const User = require("../models/user.js");
|
||||||
|
const JwtStrategy = require("passport-jwt").Strategy;
|
||||||
|
const ExtractJwt = require("passport-jwt").ExtractJwt;
|
||||||
|
|
||||||
|
const opts = {};
|
||||||
|
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
|
||||||
|
opts.secretOrKey = process.env.SECRET_KEY;
|
||||||
|
|
||||||
|
const getAdmin = async () => {
|
||||||
|
const admin = await User.findOne({}).lean().exec(); // only one user in DB - admin. pull it up, jamie!
|
||||||
|
return admin;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = new JwtStrategy(opts, (jwt_payload, done) => {
|
||||||
|
const admin = getAdmin();
|
||||||
|
if (jwt_payload.username === admin.username) {
|
||||||
|
return done(null, true);
|
||||||
|
}
|
||||||
|
return done(null, false);
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue