basic blog api
This commit is contained in:
commit
7c9799ccd3
|
@ -0,0 +1,3 @@
|
||||||
|
MONGODB_URI='mongodb://localhost:27017/express-practice'
|
||||||
|
PORT=4000
|
||||||
|
JWT_SECRET="3rwfesdj5rtghfb7ty7ujgh"
|
|
@ -0,0 +1,137 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# 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
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://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/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Public Private key files
|
||||||
|
private.key
|
||||||
|
public.key
|
||||||
|
|
||||||
|
# Temp files
|
||||||
|
test.js
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run prettify
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Express Practice
|
||||||
|
|
||||||
|
My Practice Repo for Express API
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "express-practice",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Express Testing Repo",
|
||||||
|
"main": "src/app.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nodemon src/app.js",
|
||||||
|
"start": "node src/app.js",
|
||||||
|
"prepare": "husky install",
|
||||||
|
"prettify": "prettier --write ."
|
||||||
|
},
|
||||||
|
"author": "Kausik07",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.0.1",
|
||||||
|
"express": "^4.18.1",
|
||||||
|
"express-autoload-router": "^1.0.5",
|
||||||
|
"helmet": "^5.1.1",
|
||||||
|
"joi": "^17.6.0",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"mongoose": "^6.5.2",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"napi-nanoid": "^0.0.4",
|
||||||
|
"node-cache": "^5.1.2",
|
||||||
|
"nodemailer": "^6.7.8",
|
||||||
|
"redis": "^4.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"husky": "^8.0.0",
|
||||||
|
"nodemon": "^2.0.19",
|
||||||
|
"prettier": "^2.7.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
const express = require('express')
|
||||||
|
const cors = require('cors')
|
||||||
|
const helmet = require('helmet')
|
||||||
|
const morgan = require('morgan')
|
||||||
|
const { PORT } = require('./configs')
|
||||||
|
const connectMongo = require('./services/mongodb.service')
|
||||||
|
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.use(cors())
|
||||||
|
app.use(helmet())
|
||||||
|
app.use(morgan('dev'))
|
||||||
|
app.use(express.json())
|
||||||
|
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.status(200).send('OK')
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('☄', 'Base Route', '/api')
|
||||||
|
const router = require('./routers')
|
||||||
|
app.use('/api', router)
|
||||||
|
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
console.log('💀', 'Error')
|
||||||
|
res.status(500).json({
|
||||||
|
status: "error",
|
||||||
|
message: err.message,
|
||||||
|
error: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use("*", (req, res) => {
|
||||||
|
res.status(404).json({
|
||||||
|
status: "error",
|
||||||
|
message: "Not found",
|
||||||
|
error: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(PORT, async () => {
|
||||||
|
connectMongo()
|
||||||
|
console.log(`🚀 API listening on port ${PORT}`)
|
||||||
|
})
|
|
@ -0,0 +1,8 @@
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MONGODB_URI:
|
||||||
|
process.env.MONGODB_URI || 'mongodb://localhost:27017/blog',
|
||||||
|
PORT: process.env.PORT || 3000,
|
||||||
|
JWT_SECRET: process.env.JWT_SECRET || 'secret',
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
const User = require('../models/user.model')
|
||||||
|
const { generateJWT } = require('../lib/auth.adapter')
|
||||||
|
const { validateUser } = require('../validators/user.validation')
|
||||||
|
|
||||||
|
const CreateUser = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const userRegData = await validateUser(req.body)
|
||||||
|
const { username, password, email, name } = userRegData
|
||||||
|
const user = new User({
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
attributes: {
|
||||||
|
role: 'user',
|
||||||
|
isDisabled: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await user.setPassword(String(password))
|
||||||
|
await user.save()
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User created successfully',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
role: user.attributes.role,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginUser(req, res, next) {
|
||||||
|
try {
|
||||||
|
const { login_user, password } = req.body
|
||||||
|
const user = await User.findOne({ $or: [{ username: login_user }, { email: login_user }] })
|
||||||
|
if(!user){
|
||||||
|
throw new Error('User not found')
|
||||||
|
}
|
||||||
|
if(!user.validatePassword(password)){
|
||||||
|
throw new Error('Invalid password')
|
||||||
|
}
|
||||||
|
const token = await generateJWT(user)
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User logged in successfully',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
access_token: token,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loginUser,
|
||||||
|
CreateUser,
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
const Blog = require('../models/blog.model');
|
||||||
|
const { validateBlog } = require('../validators/blog.validation')
|
||||||
|
|
||||||
|
const Create = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const blogData = await validateBlog(req.body);
|
||||||
|
const { title, content } = blogData;
|
||||||
|
const author = req.userData.sub;
|
||||||
|
const blog = new Blog({
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
author,
|
||||||
|
});
|
||||||
|
await blog.save();
|
||||||
|
res.status(201).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blog created successfully',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
id: blog._id,
|
||||||
|
title: blog.title,
|
||||||
|
content: blog.content,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListAll = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const blogs = await Blog.find({});
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blogs listed successfully',
|
||||||
|
error: null,
|
||||||
|
data: blogs
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListOne = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const blog = await Blog.findById(id);
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blog found successfully',
|
||||||
|
error: null,
|
||||||
|
data: blog
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Update = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
let blog = await Blog.findById(id);
|
||||||
|
if(blog.author != req.userData.sub) {
|
||||||
|
throw new Error('You are not authorized to update this blog');
|
||||||
|
}
|
||||||
|
const { title, content } = req.body;
|
||||||
|
blog = await Blog.findByIdAndUpdate(id, {
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
}, { new: true });
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blog updated successfully',
|
||||||
|
error: null,
|
||||||
|
data: blog
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Delete = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const blog = await Blog.findById(id);
|
||||||
|
if(blog.author != req.userData.sub) {
|
||||||
|
throw new Error('You are not authorized to delete this blog');
|
||||||
|
}
|
||||||
|
await Blog.findByIdAndUpdate(id, {
|
||||||
|
attributes: {
|
||||||
|
isDeleted: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blog deleted successfully',
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Publish = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const blog = await Blog.findById(id);
|
||||||
|
if(blog.author != req.userData.sub) {
|
||||||
|
throw new Error('You are not authorized to publish this blog');
|
||||||
|
}
|
||||||
|
await Blog.findByIdAndUpdate(id, {
|
||||||
|
attributes: {
|
||||||
|
isPublished: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blog Published successfully',
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Draft = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const blog = await Blog.findById(id);
|
||||||
|
if(blog.author != req.userData.sub) {
|
||||||
|
throw new Error('You are not authorized to draft this blog');
|
||||||
|
}
|
||||||
|
await Blog.findByIdAndUpdate(id, {
|
||||||
|
attributes: {
|
||||||
|
isPublished: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blog saved to Draft successfully',
|
||||||
|
error: null,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListMine = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const author = req.userData.sub;
|
||||||
|
const blogs = await Blog.find({ author });
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Blogs listed successfully',
|
||||||
|
error: null,
|
||||||
|
data: blogs
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Create,
|
||||||
|
ListAll,
|
||||||
|
ListOne,
|
||||||
|
Update,
|
||||||
|
Delete,
|
||||||
|
Publish,
|
||||||
|
Draft,
|
||||||
|
ListMine
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
const User = require('../models/user.model')
|
||||||
|
|
||||||
|
async function getCurrentUser(req, res, next){
|
||||||
|
try {
|
||||||
|
const user = await User.findById(req.userData.sub)
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User found',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
role: user.attributes.role,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCurrentUser(req, res, next){
|
||||||
|
try {
|
||||||
|
const { email, name } = req.body
|
||||||
|
const user = await User.findById(req.userData.sub)
|
||||||
|
if(email){
|
||||||
|
user.email = email
|
||||||
|
}
|
||||||
|
if(name){
|
||||||
|
user.name = name
|
||||||
|
}
|
||||||
|
await user.save()
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User updated successfully',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllUsers(req, res, next){
|
||||||
|
const rawUsers = await User.find({})
|
||||||
|
let users = []
|
||||||
|
for (const u of rawUsers) {
|
||||||
|
u.hash = undefined
|
||||||
|
u.salt = undefined
|
||||||
|
u.attributes = {
|
||||||
|
role: u.attributes.role,
|
||||||
|
}
|
||||||
|
users.push(u)
|
||||||
|
}
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'Users found',
|
||||||
|
error: null,
|
||||||
|
data: users
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserById(req, res, next){
|
||||||
|
try {
|
||||||
|
if(!req.params.usrid){
|
||||||
|
throw new Error('User id is required')
|
||||||
|
}
|
||||||
|
let user = {}
|
||||||
|
if(req.query.u_type === 'oid'){
|
||||||
|
user = await User.findById(req.params.usrid)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
user = await User.findOne({username: req.params.usrid})
|
||||||
|
}
|
||||||
|
if(!user){
|
||||||
|
throw new Error('User not found')
|
||||||
|
}
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User found',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
role: user.attributes.role,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateUserById(req, res, next){
|
||||||
|
try {
|
||||||
|
if(!req.params.usrid){
|
||||||
|
throw new Error('User id is required')
|
||||||
|
}
|
||||||
|
const { email, name } = req.body
|
||||||
|
const user = await User.findById(req.params.usrid)
|
||||||
|
if(email){
|
||||||
|
user.email = email
|
||||||
|
}
|
||||||
|
if(name){
|
||||||
|
user.name = name
|
||||||
|
}
|
||||||
|
await user.save()
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User updated successfully',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableUserById(req, res, next){
|
||||||
|
try {
|
||||||
|
if(!req.params.usrid){
|
||||||
|
throw new Error('User id is required')
|
||||||
|
}
|
||||||
|
const user = await User.findById(req.params.usrid)
|
||||||
|
if(!user){
|
||||||
|
throw new Error('User not found')
|
||||||
|
}
|
||||||
|
user.attributes.isDisabled = true
|
||||||
|
await user.save()
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'success',
|
||||||
|
message: 'User disabled successfully',
|
||||||
|
error: null,
|
||||||
|
data: {
|
||||||
|
username: user.username,
|
||||||
|
attributes: {
|
||||||
|
isDisabled: user.attributes.isDisabled,
|
||||||
|
role: user.attributes.role,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getCurrentUser,
|
||||||
|
updateCurrentUser,
|
||||||
|
getAllUsers,
|
||||||
|
getUserById,
|
||||||
|
updateUserById,
|
||||||
|
disableUserById,
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const { JWT_SECRET, } = require('../configs');
|
||||||
|
|
||||||
|
async function generateJWT(user) {
|
||||||
|
const payload = {
|
||||||
|
sub: user.id,
|
||||||
|
username: user.username,
|
||||||
|
role: user.attributes.role,
|
||||||
|
};
|
||||||
|
return jwt.sign(payload, JWT_SECRET, {
|
||||||
|
algorithm: 'HS256',
|
||||||
|
expiresIn: '1d',
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyJWT(token) {
|
||||||
|
const payload = jwt.verify(token, JWT_SECRET, {
|
||||||
|
algorithms: 'HS256',
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
if(!payload){
|
||||||
|
throw new Error('Invalid or expired token');
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
generateJWT,
|
||||||
|
verifyJWT,
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
const { verifyJWT } = require('../lib/auth.adapter')
|
||||||
|
|
||||||
|
async function verifyUser(req, res, next) {
|
||||||
|
try {
|
||||||
|
if(!req.headers.authorization){
|
||||||
|
throw new Error('No authorization header')
|
||||||
|
}
|
||||||
|
const token = (req.headers.authorization.split(' ')[1])
|
||||||
|
const decoded = await verifyJWT(token)
|
||||||
|
req.userData = decoded
|
||||||
|
next()
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyAdmin(req, res, next) {
|
||||||
|
try {
|
||||||
|
if(req.userData.role !== 'admin'){
|
||||||
|
throw new Error('You are not an admin')
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
verifyUser,
|
||||||
|
verifyAdmin
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const blogSchema = new mongoose.Schema({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
isPublished: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isDeleted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
comments: [{
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: 'Comments',
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
timestamps: true
|
||||||
|
})
|
||||||
|
|
||||||
|
blogSchema.methods.setAuthor = function(userID){
|
||||||
|
this.author = userID;
|
||||||
|
}
|
||||||
|
|
||||||
|
blogSchema.methods.publishBlog = function(condition){
|
||||||
|
this.attributes.isPublished = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
blogSchema.methods.deleteBlog = function(condition){
|
||||||
|
this.attributes.isDeleted = condition;
|
||||||
|
this.attributes.isPublished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Blog = mongoose.model('Blog', blogSchema)
|
||||||
|
|
||||||
|
module.exports = Blog
|
|
@ -0,0 +1,85 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const userSchema = new mongoose.Schema({
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
email:{
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
first: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
last: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
role: {
|
||||||
|
type: String,
|
||||||
|
enum: ['user', 'admin'],
|
||||||
|
default: 'user'
|
||||||
|
},
|
||||||
|
emailVerificationToken: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
resetToken: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
lastResetDate: {
|
||||||
|
type: Date,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
salt: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},{
|
||||||
|
timestamps: true
|
||||||
|
})
|
||||||
|
|
||||||
|
userSchema.methods.setPassword = function(password){
|
||||||
|
this.salt = crypto.randomBytes(16).toString('hex');
|
||||||
|
this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64, 'sha512').toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
userSchema.methods.validatePassword = function(password) {
|
||||||
|
const hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64, 'sha512').toString('hex');
|
||||||
|
return this.hash === hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
userSchema.methods.validateResetToken = function(token) {
|
||||||
|
if(this.attributes.resetToken === token) {
|
||||||
|
this.attributes.resetToken = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
userSchema.methods.generateResetToken = function() {
|
||||||
|
const token = crypto.randomBytes(32).toString('hex');
|
||||||
|
this.attributes.resetToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const User = mongoose.model('User', userSchema)
|
||||||
|
|
||||||
|
module.exports = User;
|
|
@ -0,0 +1,10 @@
|
||||||
|
const { Router } = require('express')
|
||||||
|
const { CreateUser, loginUser } = require('../controllers/auth.controller')
|
||||||
|
|
||||||
|
const route = new Router()
|
||||||
|
|
||||||
|
route.post('/register', CreateUser)
|
||||||
|
|
||||||
|
route.post('/login', loginUser)
|
||||||
|
|
||||||
|
module.exports = route
|
|
@ -0,0 +1,23 @@
|
||||||
|
const { Router } = require('express')
|
||||||
|
const { Create, Delete, ListAll, ListOne, Draft, Publish, Update, ListMine, } = require('../controllers/blog.controller')
|
||||||
|
const { verifyUser, verifyAdmin } = require('../middlewares/auth.middleware')
|
||||||
|
|
||||||
|
const route = new Router()
|
||||||
|
|
||||||
|
route.post('/create', verifyUser, Create)
|
||||||
|
|
||||||
|
route.patch('/publish/:id', verifyUser, Publish)
|
||||||
|
|
||||||
|
route.patch('/draft/:id', verifyUser, Draft)
|
||||||
|
|
||||||
|
route.delete('/:id', verifyUser, Delete)
|
||||||
|
|
||||||
|
route.get("/", ListAll)
|
||||||
|
|
||||||
|
route.get('/me', verifyUser, ListMine)
|
||||||
|
|
||||||
|
route.get("/:id", ListOne)
|
||||||
|
|
||||||
|
route.patch("/:id", verifyUser, Update)
|
||||||
|
|
||||||
|
module.exports = route
|
|
@ -0,0 +1,43 @@
|
||||||
|
const path = require('path')
|
||||||
|
const { readdirSync } = require('fs')
|
||||||
|
|
||||||
|
const { Router } = require('express')
|
||||||
|
|
||||||
|
const router = Router()
|
||||||
|
|
||||||
|
const isCompiled = path.extname(__filename) === '.js'
|
||||||
|
const thisFileName = path.basename(__filename)
|
||||||
|
|
||||||
|
const loadRoutes = async (dirPath, prefix = '/') => {
|
||||||
|
readdirSync(dirPath, {
|
||||||
|
withFileTypes: true,
|
||||||
|
}).forEach(async (f) => {
|
||||||
|
if (f.isFile()) {
|
||||||
|
if (f.name == thisFileName) return
|
||||||
|
const isRouteMod = f.name.endsWith(
|
||||||
|
`.routes.${isCompiled ? 'js' : 'ts'}`
|
||||||
|
)
|
||||||
|
if (isRouteMod) {
|
||||||
|
const route = f.name.replace(
|
||||||
|
`.routes.${isCompiled ? 'js' : 'ts'}`,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
const modRoute = path.join(prefix, route)
|
||||||
|
console.log('🛰️ Loaded', modRoute)
|
||||||
|
|
||||||
|
const mod = await import(path.join(baseDir, f.name))
|
||||||
|
|
||||||
|
router.use(modRoute, mod.default)
|
||||||
|
}
|
||||||
|
} else if (f.isDirectory()) {
|
||||||
|
await loadRoutes(path.resolve(dirPath, f.name), prefix + f.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseDir = path.dirname(__filename)
|
||||||
|
baseDir = path.resolve(baseDir)
|
||||||
|
|
||||||
|
loadRoutes(baseDir)
|
||||||
|
|
||||||
|
module.exports = router
|
|
@ -0,0 +1,20 @@
|
||||||
|
const { Router } = require('express')
|
||||||
|
const { getCurrentUser, updateCurrentUser, getAllUsers, getUserById, updateUserById, disableUserById } = require('../controllers/user.controller')
|
||||||
|
const { verifyUser, verifyAdmin } = require('../middlewares/auth.middleware')
|
||||||
|
|
||||||
|
|
||||||
|
const route = new Router()
|
||||||
|
|
||||||
|
route.get('/me', verifyUser, getCurrentUser)
|
||||||
|
|
||||||
|
route.patch('/me', verifyUser, updateCurrentUser)
|
||||||
|
|
||||||
|
route.get("/", verifyUser, getAllUsers)
|
||||||
|
|
||||||
|
route.get("/:usrid", verifyUser, getUserById)
|
||||||
|
|
||||||
|
route.patch("/:usrid", verifyUser, verifyAdmin, updateUserById)
|
||||||
|
|
||||||
|
route.delete("/:usrid", verifyUser, verifyAdmin, disableUserById)
|
||||||
|
|
||||||
|
module.exports = route
|
|
@ -0,0 +1,10 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const { MONGODB_URI } = require('../configs')
|
||||||
|
|
||||||
|
module.exports = async () => {
|
||||||
|
const mongo_url = process.env.MONGODB_URI || MONGODB_URI;
|
||||||
|
await mongoose.connect(
|
||||||
|
mongo_url,
|
||||||
|
() => console.log('Connected to MongoDB 🍀')
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
const schema = Joi.object({
|
||||||
|
title: Joi.string()
|
||||||
|
.alphanum()
|
||||||
|
.min(3)
|
||||||
|
.max(30)
|
||||||
|
.required(),
|
||||||
|
|
||||||
|
content: Joi.string()
|
||||||
|
.min(50)
|
||||||
|
.max(500)
|
||||||
|
.required()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function validateBlog(user) {
|
||||||
|
return schema.validateAsync({
|
||||||
|
title: user.title,
|
||||||
|
content: user.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateBlog
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
const Joi = require('joi');
|
||||||
|
|
||||||
|
const schema = Joi.object({
|
||||||
|
username: Joi.string().alphanum().min(3).max(30).required(),
|
||||||
|
password: Joi.string().pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/).required(),
|
||||||
|
email: Joi.string().email({ minDomainSegments: 2} ).required(),
|
||||||
|
name: {
|
||||||
|
first: Joi.string().min(3).max(30),
|
||||||
|
last: Joi.string().min(3).max(30)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function validateUser(user) {
|
||||||
|
return schema.validateAsync({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password,
|
||||||
|
email: user.email,
|
||||||
|
name: { first: user.name.first, last: user.name.last }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validateUser
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue