Angular/Node/Mongoose - Rendering Post, Comment, Replies - javascript

I am creating an Application where people can ask questions. Answer- and reply to answers. I can render Posts and answers no problem, but having trouble getting replies to answers.Can someone please give me tips on the best approach to do this?
html page
<div class="container-fluid" id='post_container' ng-repeat='post in vm.posts' style='border: solid'>
<div class="row">
<div class = 'container'>
<div class='single_post'>
<h3>Topic: {{post.topic}}</h3>
<h5>Posted By: {{post.owner}} </h5>
<p>Description: {{post.description}}</p>
<form class="form-horizontal">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Answer</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" ng-model='vm.newAnswer[$index]'></textarea>
<button class='btn btn-primary' ng-click = 'vm.Answer(post._id, $index)' style='float:right'>Submit</button>
</div>
</form>
</div>
<em>Answers</em>
<!-- Make API Call to get answers into the container change varialbe -->
<div class='answer_container' ng-repeat = 'answer in post.answers track by $index'>
<h5><strong>{{answer._owner}}</strong>: <em> {{answer.answer}} ID {{answer._id}}</em></h5>
<div class='replies' style='margin-left: 20px'>
-find a way to get comments here
<input class='Reply' ng-model='vm.newComment'>
<input type='submit' ng-click = 'vm.Reply(answer._id)'>
</div>
angular controller
function getPosts() {
PostsFactory.getPosts()
.then(function(data) {
console.log(data)
console.log('getting POSTS')
vm.posts = data
console.log(map(vm.posts, getAnswers));
})
.catch(function(){
console.log('in the single psot controller and could not get posts')
}
Factory
function getPosts() {
var deferred = $q.defer()
$http.get('/getAnswers')
.success(function(data) {
deferred.resolve(data)
})
.error(function() {
console.log('could not get posts')
})
return deferred.promise
}
Models/Routes/Controller
var Post = new mongoose.Schema({
category: String,
topic: String,
description: String,
points: Number,
owner: String,
answers: [{type: ObjectId, ref: 'Answer'}],
date_created: Date
});
Answer
var Answer = new mongoose.Schema({
answer: String,
_post: {type: ObjectId, ref:'Post'},
_owner: String,
points: Number,
comments: [{type: ObjectId, ref: 'Comment'}],
date_created: Date
})
Comments
var Comment = new mongoose.Schema({
comment: String,
_answer: {type: ObjectId, ref: 'Answer'},
_owner: String,
points: Number,
date_created: Date
})
Controller
posts.show = function(req, res) {
Post.find()
.populate('answers')
.exec(function(err, result) {
if(err) {
console.log('error finding post')
} else {
res.json(result)
}
})
}

Mongoose supports "multi-level" population, but you need to supply that information to .populate():
posts.show = function(req,res) {
Post.find()
.populate({
"path": "answers",
"populate": {
"path": "comments",
"model": "Comment"
}
})
.exec(function(err,result) {
if (err) {
console.log(err);
} else {
res.json(result)
}
});
}
So the most that happens with a "single path" is that particular "path" is populated. But by "nesting" the "populate" arguments, then further calls are made for the other referenced items named.

Related

How do I convert a videos file uploaded with multer to a string so that I can display it on the frontend

I am trying to upload the video file to the frontend page, but it is not working. below is the code. Please let me know what's wrong because I keep getting the error cast to string failed for value at path tutorial.
this is the model schema
//this is the model schema
var mongoose = require("mongoose");
var courseSchema = new mongoose.Schema({
name: String,
course: String,
createdAt: {
type: Date,
default: Date.now
},
tutorial: String,
price: String,
description: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
username: String
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Course", courseSchema);
this is the post route
//this is the route
router.post("/", upload.single("tutorial"), function(req, res, next){
//get data from the form and add it to the course array
var name = req.body.name;
var course = req.body.course;
var tutorial = req.file;
var price = req.body.price;
var description = req.body.description;
var author = {
id: req.user._id,
username: req.user.username
};
var newCourse = {name: name, course: course, tutorial: tutorial, price:price, description: description, author: author}
//Create a new course and save to the database
Course.create(newCourse, function(err, newlyCreated){
if(err){
console.log(err)
} else {
//redirect it page back to the courses page
res.redirect("courses/" + req.body.course);
}
});
});
this is the front end part.
//this is the frontend
<div class="row container">
<!-- blog grid -->
<% courses.forEach(function(course){ %>
<div class="col-lg-4 col-md-6">
<div class="card">
<div class="card-header p-0 position-relative">
<a href="/courses/<%- course._id %>">
<video class="card-img-bottom" src="${req.file.path}" alt="course" controls></video>
<span class="post-icon" aria-hidden="true">$<%- course.price %></span>
</a>
</div>
<div class="card-body">
<h6 class="text-colors let-spa mb-3"></h6>
<h5 class="blog-title card-title font-weight-bold text-bl"><%- course.name %></h5>
<div class="row mt-5">
<div class="col-3 testi-img-res px-2">
<img src="/images/te1.jpg" alt=" " class="img-fluid rounded-circle" />
</div>
<div class="col-9 w3_testi_grid mt-xl-2 mt-lg-0 mt-md-2 mt-4">
<h5 class="text-colors mb-1"><%- course.author.username %></h5>
<p>Good day Programmers</p>
</div>
</div>
</div>
</div>
</div>
<% }); %>
<!-- //blog grid -->
</div>
It just keep returning this
Error: Course validation failed: tutorial: Cast to string failed for value "{
fieldname: 'tutorial',
originalname: 'name of the file',
encoding: '7bit',
mimetype: 'video/mp4',
destination: 'public/course_uploads',
filename: '1626194886684.mp4',
path: 'public\\course_uploads\\1626194886684.mp4',
size: 32510498
}" at path "tutorial"
{
stringValue: '"{\n' +
" fieldname: 'tutorial',\n" +
" originalname: '09. Navigation Bar and Menu in CSS.mp4',\n" +
" encoding: '7bit',\n" +
" mimetype: 'video/mp4',\n" +
" destination: 'public/course_uploads',\n" +
" filename: '1626194886684.mp4',\n" +
" path: 'public\\\\course_uploads\\\\1626194886684.mp4',\n" +
' size: 32510498\n' +
'}"',
messageFormat: undefined,
kind: 'string',
value: [Object],
path: 'tutorial',
reason: null
}
},
_message: 'Course validation failed'
}
create /uploads file in ur backend.
don't forget to use it's file path in ur index.js
app.use('/videos', express.static('/uploads))
I have used /video as prefix
then store ur file and name it using its fieldname and original name from multer:
${file.fieldname}${path.extname(file.originalname)}
In you database, store it as : http://localhost:5000/videos/${req.file.filename}
Using this url, u can use access ur videos in frontend.

Express.js with EJS application bug: the view throws a "Cannot read property 'toString'" error

I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.
I have Posts and Post Categories, each in its own collection.
The Categories Schema:
const mongoose = require('mongoose');
const categorySchema = new mongoose.Schema({
cat_name: {
type: String,
required: true
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Category', categorySchema);
The Posts schema:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'category'
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Post', postSchema);
There is some detail I have missed, because on updating a post, the editpost.ejs view returns a Cannot read property 'toString' of undefined error:
<form action="/dashboard/post/update/<%= post._id %>" method="POST" enctype="multipart/form-data" class="mb-0">
<div class="form-group">
<input type="text" class="form-control" name="title" value="<%= typeof form!='undefined' ? form.titleholder : post.title %>" placeholder="Title" />
</div>
<div class="form-group">
<input type="text" class="form-control" name="excerpt" value="<%= typeof form!='undefined' ? form.excerptholder : post.short_description %>" placeholder="Excerpt" />
</div>
<div class="form-group">
<textarea rows="5" class="form-control" name="body" placeholder="Full text">
<%= typeof form!='undefined' ? form.bodyholder : post.full_text %>
</textarea>
</div>
<% if (categories) { %>
<div class="form-group">
<label for="category">Choose a post category</label>
<select id="category" name="category" class="form-control">
<% categories.forEach(function(category, index) { %>
<option value="<%= category._id %>" <%=category._id.toString()==p ost.category._id.toString() ? 'selected' : ''; %>>
<%= category.cat_name %>
</option>
<% }); %>
</select>
</div>
<% } %>
<label for="postimage">Upload an image</label>
<div class="form-group">
<input type="file" name="postimage" id="postimage" size="20">
</div>
<div class="form-group d-flex mb-0">
<div class="w-50 pr-1">
<input type="submit" value="Update Post" class="btn btn-block btn-md btn-success">
</div>
<div class="w-50 pl-1">
Cancel
</div>
</div>
</form>
In the controller, the updatePost method looks like this:
exports.updatePost = (req, res, next) => {
const query = {
_id: req.params.id
}
const form = {
titleholder: req.body.title,
excerptholder: req.body.excerpt,
bodyholder: req.body.body
};
const errors = validationResult(req);
const post = {};
post._id = req.params.id;
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.category = req.body.category;
if (req.file) {
post.post_image = req.file.filename;
}
if (!errors.isEmpty()) {
req.flash('danger', errors.array());
const categories = Category.find({}, (err, categories) => {
res.render('admin/editpost', {
layout: 'admin/layout',
website_name: 'MEAN Blog',
page_heading: 'Dashboard',
page_subheading: 'Edit Post',
categories: categories,
form: form,
post: post
});
});
} else {
Post.update(query, post, function(err) {
if (err) {
console.log(err);
return;
} else {
req.flash('success', "The post was successfully updated");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
What have I missed?
I think your problem here is here:
post.category._id.toString()
When you pass category in req.body you are passing category._id as that parameter, so when you create that post object in your controller, you set post.category as req.body.category, when you return this to the view post.category is not an object with ._id property and the toString() method doesn't exist on it.
Try using post.category instead
Do nothing but handle req.file first in the post route, because you are using enctype="multipart/form-data".
Use req.file somewhere and it'll work fine! I don't know the exact reason but it works!

Mean stack can't post to mongodb doesn't seem to do anything, get 0 error

I can't seem to find what's wrong with the code. What I want to do is to be able to post via the inputted data then when clicking the button it should post (create) and save in mongoDB. Here's my code so far. Any help is appreciated!
Html (Note: Category.name works):
<section class="wrapper" *ngIf="category">
<h2>Create a new post</h2>
<p>within the category <span class="category">{{ category.name }}</span> </p>
<form>
<input type="text" name="title" placeholder="Title" required autofocus [(ngModel)]="title">
<input type="hidden" name="category" value="{{ category._id }}">
<textarea name="content" placeholder="Post content" required [(ngModel)]="content" ></textarea>
<button (click)="createPost()">Publicera</button>
</form>
</section>
Component ts file :
export class CreateComponent implements OnInit {
category: Category;
title: String = '';
content: String = '';
constructor(
private route: ActivatedRoute,
private appService: AppService
) { }
ngOnInit() {
let _id = this.route.snapshot.paramMap.get('_id');
this.appService.getCategory(_id)
.subscribe(data =>this.category=data);
}
createPost(){
let post = new Post();
post.title = this.title;
post.content = this.content;
this.appService.createPost(post);
}
}
Service file:
createPost(post: Post){
let payload = {
"title": post.title,
"content": post.content
}
this.http.post(this.apiUrl+"/post/", payload, {responseType: 'text'}).subscribe(response => {});
}
Server file (server.js)
app.post('/api/post', (req, res) => {
var today = new Date();
var newPostData = { userId: req.body.userId, category: req.body.category, postId: req.body.postId, commentId: req.body.commentId, title: req.body.title, content: req.body.content, publishedDate: today, editedDate: null };
var post = new Post(newPostData, function(err) {
});
post.save();
});
I think you have to add a callback in save function, check the document here.
var newPostData = { userId: req.body.userId, category: req.body.category, postId: req.body.postId, commentId: req.body.commentId, title: req.body.title, content: req.body.content, publishedDate: today, editedDate: null };
var post = new Post(newPostData);
post .save(function (err) {
if (err) {console.log(err) return res.send(err)};
// saved!
});

Find MongoDB document from id posted as hidden input

How can I find the document matching the ID being posted in a hidden input?
Here is my schema:
var MessageSchema = Schema({
name: {type: String, required: true},
message: {type: String, required: true},
replies: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
}, {timestamps: true});
Here is my form:
<% for(var i=0; i<messages.length; i++) { %>
<form action="/comment/create" method="post">
<label>Name: </label>
<input type="name" name="name">
<label>Comment: </label>
<input type="text" name="comment">
<input type="hidden" name="replyTo" value=<%= messages[i]['_id']%> >
<button>Reply</button>
</form>
<% } %>
And my post route is a mess so at this point I just want to know how to find the dang message.
app.post('/comment/create', function(req, res) {
console.log(req.body.replyTo);
var message = Message.find({ _id: req.body.replyTo });
console.log(message);
res.redirect('/');
})
console.log(req.body.replyTo) returns the id 59022ff22951ce73ed9bb773.
console.log(message) returns undefined.
The call to Messages.find is asynchronous, and therefore will not provide a proper value when assigning it's result to the variable message. You need to handle this either with a callback or a Promise:
As a callback
Message.find({ _id: req.body.replyTo }, (err, res) => {
console.log(res); // message
});
As a Promise
Message.find({ _id: req.body.replyTo })
.then((res) => {
console.log(res); // message
})
.catch((err) => {
// ...
});

SequelizeJS Passing Array of Values

I am trying to figure out how it is possible to pass an array as the value for the property of an instance. I currently have the dataType set to STRING in my model and have values from jQuery fields insert each form field value into an array that I parse from the body and set to the property, discoverSource. Unfortunately I receive a string violation error that says I can't use an array or object. What does this mean and how can I change the dataType of the field or route to allow me to pass the comma separated values to the field?
E.x. For discoverySource I pass values to two fields (NJ, NY). On submit, the values are combined in an array as ["NJ", "NY"] and the error displays:
Error Message:
{"name":"SequelizeValidationError","message":"string violation: discoverySource cannot be an array or an object","errors":[{"message":"discoverySource cannot be an array or an object","type":"string violation","path":"discoverySource","value":["NJ","NY"]}]}
Here is my model:
module.exports = function(sequelize, DataTypes) {
var Organization = sequelize.define('organization', {
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
autoIncrement: true,
primaryKey: true
},
organizationName: {
type: DataTypes.STRING,
field: 'organization_name'
},
admin: DataTypes.STRING,
discoverySource: {
type: DataTypes.TEXT,
field: 'discovery_source'
},
members: DataTypes.STRING
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.belongsToMany(db.User, { through: 'member', foreignKey: 'user_id' });
},
},
});
return Organization;
}
Here is the route:
var express = require('express');
var appRoutes = express.Router();
var passport = require('passport');
var localStrategy = require('passport-local').Strategy;
var models = require('../models/db-index');
appRoutes.route('/sign-up/organization')
.get(function(req, res){
models.User.find({
where: {
user_id: req.user.email
}, attributes: [ 'user_id', 'email'
]
}).then(function(user){
res.render('pages/app/sign-up-organization.hbs',{
user: req.user
});
})
})
.post(function(req, res, user){
models.Organization.create({
organizationName: req.body.organizationName,
admin: req.body.admin,
discoverySource: req.body.discoverySource
}).then(function(organization, user){
res.redirect('/app');
}).catch(function(error){
res.send(error);
console.log('Error at Post' + error);
})
});
Here is my view file:
<!DOCTYPE html>
<head>
{{> head}}
</head>
<body>
{{> navigation}}
<div class="container">
<div class="col-md-6 col-md-offset-3">
<form action="/app/sign-up/organization" method="post">
<p>{{user.email}}</p>
<input type="hidden" name="admin" value="{{user.email}}">
<input type="hidden" name="organizationId">
<label for="sign-up-organization">Company/Organization Name</label>
<input type="text" class="form-control" id="sign-up-organization" name="organizationName" value="" placeholder="Company/Organization">
Add Another Discovery Source
<div id="sign-up-organization-discovery-source">
<input type="text" id="discovery-source-field" placeholder="Discovery Source" name="discoverySource[0]">
</div>
<br />
<button type="submit">Submit</button>
</form>
Already have an account? Login here!
</div>
</div>
<script type="text/javascript">
$(function() {
var dataSourceField = $('#sign-up-organization-discovery-source');
var i = $('#sign-up-organization-discovery-source p').size();
var sourceCounter = 1;
$('#sign-up-add-discovery-source').on('click', function() {
$('<p><label for="discovery-source-field"><input type="text" id="discovery-source-field" size="20" name="discoverySource['+ sourceCounter++ +']" value="" placeholder="Discovery Source" /></label> Remove</p>').appendTo(dataSourceField);
i++;
return false;
});
$('#sign-up-organization-discovery-source').on('click', '.remove', function() {
if (i > 1) {
$(this).parent('p').remove();
i--;
}
return false;
});
});
</script>
</body>
To answer the last comment, I need to be able to make the code more readable, so I'm posting it here in a new answer.
Having thought about it a little more, it would make more sense to add it as custom 'getter' function. I'll also include the 'instanceMethods' to demonstrate how that works, as well.
var Organization = sequelize.define('organization', {
...
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.belongsToMany(db.User, { through: 'member', foreignKey: 'user_id' });
},
},
// Here's where custom getters would go
getterMethods: {
discoverySources: function() {
return this.getDataValue('discoverySource');
}
},
// here's the instance methods
instanceMethods: {
getSourcesArray: function() {
return this.getDataValue('discoverySource');
}
}
});
Both of these options add the functions to each instance created by the Model. The main difference being in how they are accessed.
organization.discoverySources; // -> ['s1', 's2', etc...]
organization.getSourcesArray(); // -> ['s1', 's2', etc...]
note the additional () required on the instanceMethod. Those are added as functions of the instance, the getterMethods get added as properties.
setterMethods work the same way to allow you to define custom setters.
Hope that clarifies things a bit.

Categories