Skip to content

Commit

Permalink
added get articles by author and categories
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucky-victory committed Apr 7, 2022
1 parent e93d46d commit d9ccb95
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 129 deletions.
18 changes: 16 additions & 2 deletions controllers/article.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@

const Articles=require('../models/articles');
const Authors=require("../models/authors");
const Comments=require("../models/comments");
const Replies=require("../models/replies");
const asyncHandler=require('express-async-handler');
const { StringToArray, generateSlug, NullOrUndefined, isEmpty } = require('../helpers/utils');
const { StringToArray, generateSlug, NullOrUndefined, isEmpty,arrayBinder} = require('../helpers/utils');
const { decode } = require('html-entities');

// get a single article
Expand All @@ -25,9 +27,21 @@ body=decode(body);
tags= StringToArray(tags);
const {fullname,twitter,linkedIn,bio,profileImage,username}=await Authors.findOne({"id":authorId});

// query comments table with article id
let comments= await Comments.query(`SELECT id,text,postId,userId,createdAt FROM ArticlesSchema.Comments WHERE postId='${id}' ORDER BY createdAt DESC`);
// get comment ids to query replies table;
const commentsId=comments.map((comment)=>comment.id);

let replies=await Replies.query(`SELECT id,text,commentId,userId,createdAt FROM ArticlesSchema.Replies WHERE commentId IN ("${commentsId.join('","')}") ORDER BY createdAt DESC`);
// combine comments with replies based on their related id
comments=arrayBinder(comments,replies,{
innerProp:"commentId",outerProp:"id",innerTitle:"replies"
});
// combine Articles with Comments based on their related id

const newViewsCount=parseInt(views)+1 || 1;
await Articles.update([{id,'views':newViewsCount}]);
res.status(200).json({title,content,slug,views,publishedAt,modifiedAt,tags,heroImage,id,category,body,author:{fullname,twitter,linkedIn,profileImage,bio,username,readTime}});
res.status(200).json({title,content,slug,views,publishedAt,modifiedAt,tags,heroImage,id,category,body,author:{fullname,twitter,linkedIn,profileImage,bio,username,readTime,comments}});

}
catch(err){
Expand Down
43 changes: 1 addition & 42 deletions controllers/articles.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,47 +69,6 @@ if(!articles.length){
});


// Get all article tags
const getTags= asyncHandler(async(req,res)=>{
try{

let {limit,page}=req.query;
limit=parseInt(limit) ||20;
page=parseInt(page) ||1;
let offset=(limit * (page - 1)) || 0;
// select article tags from Articles table, this returns an array of objects with tags props
let allTags=await Articles.find({getAttributes:["tags"],limit,offset});

// filter out null tags, and flatten the object into an array of strings
allTags=[...allTags.reduce((accum,t)=>{t.tags !=null ? accum.push(t.tags):accum
return accum;
},[])]
res.status(200).json({message:"Tags retrieved",status:200,"tags":allTags})
}
catch(error){

const status=error.status ||500;
res.status(status).json({message:"an error occurred",error,status})
}
});

// Get all article categories
const getCategories=asyncHandler(async(req,res)=>{
try{

let {limit,page}=req.query;
limit=parseInt(limit) ||20;
page=parseInt(page) ||1;
let offset=(limit * (page - 1)) || 0;
let categories=await Articles.find({getAttributes:["category"],limit,offset});
categories=[...categories.reduce((accum,c)=>{c.category !=null ? accum.push(c.category):accum; return accum},[])]
res.status(200).json({message:"Categories retrieved",status:200,categories})
}
catch(error){
const status=error.status ||500;
res.status(status).json({message:"an error occurred",error,status})
}
});

// Add new article
const createNewArticle=asyncHandler( async(req,res)=>{
Expand Down Expand Up @@ -148,4 +107,4 @@ const totalWords= String(NotNullOrUndefined(title) + NotNullOrUndefined(body)) |

}
});
module.exports={getTags,getArticles: getPublishedArticles,createNewArticle,getCategories}
module.exports={getPublishedArticles,createNewArticle}
87 changes: 19 additions & 68 deletions controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
"use strict";
const Authors=require("../models/authors");
const randomWords=require("random-words");
const bcrypt=require("bcrypt");
const jwt=require("jsonwebtoken");
const {randomHexId}=require("../helpers/utils");
const { generateUsername}=require("../helpers/utils");
const asyncHandler=require("express-async-handler");
const nodemailer = require("nodemailer");


// register a new user/author
const createUser= asyncHandler(async(req,res)=>{
try{
// generate random username
const defaultUsername=randomWords({exactly:2,join:"-"})+"-"+randomHexId();


const {profileImage="https://cdn.pixabay.com/photo/2016/08/21/16/31/emoticon-1610228__480.png",bio,twitter,linkedIn,fullname,email,password}=req.body;

let {username=defaultUsername}=req.body;
username=slugify(username,{strict:true,lower:true,remove:/[*+~.()'"!:@]/g});
let {username}=req.body;
username=generateUsername(username);

if(!fullname || !email || !password){
res.status(400).json({"message":"please provide the required fields",requiredFields:["fullname","email","password"],status:400});
res.status(400).json({message:"please provide the required fields",requiredFields:["fullname","email","password"],status:400});
return
}

// check if a user with the email already exist;
const emailExist= await Authors.findOne({email});

if(emailExist){
res.status(400).json({"message":"user already exist"});
res.status(400).json({message:"user already exist"});
return
}
// check if a user with the username already exist;
const usernameExist= await Authors.findOne({username});
if(usernameExist){
res.status(400).json({"message":`'${username}' have been taken`});
res.status(400).json({message:`'${username}' have been taken`});
return
}
const newUser= {profileImage,bio,twitter,linkedIn,username,fullname,email};
const joinedAt=new Date().toISOString();
const newUser= {profileImage,bio,twitter,linkedIn,website,joinedAt,username,fullname,email,superUser:false};

// hash the password before storing in database
try{
Expand All @@ -47,13 +44,14 @@ try{
catch{

}
newUser["superUser"]=false;


await Authors.create(newUser);
res.status(201).json({"message":"new user created",status:201});
res.status(201).json({message:"new user created",status:201});
}
catch(err){
res.status(500).json({"message":"an error occurred",status:err.status || 500,"error":err});
catch(error){
const status=error.status||500;
res.status(status).json({message:"an error occurred, couldn't create user",status,error});
}

});
Expand Down Expand Up @@ -85,62 +83,15 @@ try{
req.user=user;
next();
}
catch(err){
res.status(500).json({"message":"an error occured",status:err.status || 500,"error":err})
catch(error){
const status=error.status||500;
res.status(status).json({message:"an error occurred, couldn't login",status,error});

}
});

const deleteUser=async(req,res)=>{

}

const sendMail=asyncHandler(async(req,res)=>{

try{
main().catch(console.error)
res.json({"info":"mail sent "})
}
catch(err){
res.json({"error":err})
}
});

async function main() {
// Generate test SMTP service account from ethereal.email
// Only needed if you don't have a real mail account for testing
let testAccount = await nodemailer.createTestAccount();

// create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: testAccount.user, // generated ethereal user
pass: testAccount.pass, // generated ethereal password
},
tls:{
rejectUnauthorized:false
}
});

// send mail with defined transport object
let info = await transporter.sendMail({
from: '"Fred Foo 👻" <luckyvictory54@gmail.com>', // sender address
to: "blizwonder25@gmail.om", // list of receivers
subject: "Hello ✔", // Subject line
text: "Hello world?", // plain text body
html: "<b>Hello world?</b>", // html body
});

console.log("Message sent: %s", info.messageId);
// Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>

// Preview only available when sending through an Ethereal account
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
// Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...
}



module.exports={createUser,deleteUser,loginUser,sendMail}
module.exports={createUser,deleteUser,loginUser}
49 changes: 49 additions & 0 deletions controllers/author.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const asyncHandler=require('express-async-handler');
const {nester,NullOrUndefined,StringToArray}=require('../helpers/utils');
const {decode}=require('html-entities');
const Articles=require('../models/articles')

const getArticlesByAuthor=asyncHandler(async(req,res)=>{
try{
let {page,sort='publishedAt|desc'}=req.query;
const {author}=req.params;
const orderBy=StringToArray(sort,'|')[0];
const order=StringToArray(sort,'|')[1] ||'desc';
const limit=20;
page=parseInt(page) ||1;
let offset=(limit * (page - 1)) ||0;
const recordCountQuery=`SELECT count(a.id) as recordCount FROM ArticlesSchema.Articles as a INNER JOIN ArticlesSchema.Authors as u ON a.authorId=u.id WHERE a.published=true AND u.username='${author}'`;
const recordCountResult=await Articles.query(recordCountQuery);
const {recordCount}=recordCountResult[0];
if((recordCount - offset ) <= 0 || (offset > recordCount)){
res.status(200).json({message:"No more Articles","articles":[]});
return
}
const articlesQuery=`SELECT a.id,a.publishedAt,a.title,a.authorId,a.body,a.views,a.heroImage,a.slug,a.tags,a.category,a.content,a.readTime,a.modifiedAt,u.fullname as _fullname,u.id as _id,u.twitter as _twitter,u.linkedIn as _linkedin,u.bio as _bio,u.username as _username,u.profileImage as _profileImage FROM ArticlesSchema.Articles as a INNER JOIN ArticlesSchema.Authors as u ON a.authorId=u.id WHERE a.published=true AND u.username='${author}' ORDER BY a.${orderBy} ${order} LIMIT ${limit} OFFSET ${offset} `;

let articles=await Articles.query(articlesQuery);
// nest author info as author property
articles=nester(articles,["_fullname","_id","_bio","_twitter","_linkedin","_username","_profileImage"],{nestedTitle:"author"});

// decode html entities
articles=articles.map((article)=>{
article.title=decode(article.title);
article.body=decode(article.body);
article.tags=StringToArray(article.tags)
return article;
});
if(!articles.length){
res.status(200).json({message:"No Articles","articles":[]})
return
}
res.status(200).json({message:"Articles retrieved",status:200,articles,resultCount:recordCount});
}
catch(error){
const status=error.status ||500;
res.status(status).json({message:"an error occurred",status,error})
}
})

module.exports={
getArticlesByAuthor
}
65 changes: 65 additions & 0 deletions controllers/category.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const asyncHandler=require('express-async-handler');
const {nester,NullOrUndefined,StringToArray, NotNullOrUndefined}=require('../helpers/utils');
const {decode}=require('html-entities');
const Articles=require('../models/articles')
// Get all article categories
const getCategories=asyncHandler(async(req,res)=>{
try{

let {page}=req.query;
const limit=20;
page=parseInt(page) ||1;
let offset=(limit * (page - 1)) || 0;
let categories=await Articles.query(`SELECT DISTINCT category FROM ArticlesSchema.Articles ${limit} ${offset}`);
categories=[...categories.reduce((accum,c)=>{!NullOrUndefined(c.category) ? accum.push(c.category):accum; return accum},[])]
res.status(200).json({message:"Categories retrieved",status:200,categories})
}
catch(error){
const status=error.status ||500;
res.status(status).json({message:"an error occurred",error,status})
}
});
const getArticlesByCategory=asyncHandler(async(req,res)=>{
try{
let {page,sort='publishedAt|desc'}=req.query;
const {category}=req.params;
const orderBy=StringToArray(sort,'|')[0];
const order=StringToArray(sort,'|')[1] ||'desc';
const limit=20;
page=parseInt(page) ||1;
let offset=(limit * (page - 1)) ||0;
const recordCountQuery=`SELECT count(id) as recordCount FROM ArticlesSchema.Articles WHERE published=true ${!NullOrUndefined(category) ? ` AND category='${category}'`:''} `;
const recordCountResult=await Articles.query(recordCountQuery);
const {recordCount}=recordCountResult[0];
if((recordCount - offset ) <= 0 || (offset > recordCount)){
res.status(200).json({message:"No more Articles","articles":[]});
return
}
const articlesQuery=`SELECT a.id,a.publishedAt,a.title,a.authorId,a.body,a.views,a.heroImage,a.slug,a.tags,a.category,a.content,a.readTime,a.modifiedAt,u.fullname as _fullname,u.id as _id,u.twitter as _twitter,u.linkedIn as _linkedin,u.bio as _bio,u.username as _username,u.profileImage as _profileImage FROM ArticlesSchema.Articles as a INNER JOIN ArticlesSchema.Authors as u ON a.authorId=u.id WHERE a.published=true ${!NullOrUndefined(category) ? ` AND category='${category}'`:''} ORDER BY a.${orderBy} ${order} LIMIT ${limit} OFFSET ${offset} `;

let articles=await Articles.query(articlesQuery);
// nest author info as author property
articles=nester(articles,["_fullname","_id","_bio","_twitter","_linkedin","_username","_profileImage"],{nestedTitle:"author"});

// decode html entities
articles=articles.map((article)=>{
article.title=decode(article.title);
article.body=decode(article.body);
article.tags=StringToArray(article.tags)
return article;
});
if(!articles.length){
res.status(200).json({message:"No Articles","articles":[]})
return
}
res.status(200).json({message:"Articles retrieved",status:200,articles,resultCount:recordCount});
}
catch(error){
const status=error.status ||500;
res.status(status).json({message:"an error occurred",status,error})
}
})
module.exports={
getCategories,
getArticlesByCategory
}
Loading

0 comments on commit d9ccb95

Please sign in to comment.