I am using Keystone JS and nunjucks. I have a feature where in the application sends an email. There is no problem in sending the email , the problem is that the data does not pass to the template. It does not adapt.
Code
var sendEmail = function (err, results) {
if (err) return callback(err);
async.each(results.admins, function (admin, done) {
new keystone.Email({ templateName: 'enquiry-notification.html', transport: 'mailgun', engine: cons.nunjucks, root: 'templates/emails' }).send({
}, {
apiKey: '',
domain: '',
title: "Test",
author: 'test',
body: 'Heeeeeeeeeeeeeeeeeeeeeeeeeeeee',
subject: subject,
html: '<b>NodeJS Email Tutorial</b>',
body: "Helloworld",
to: admin.email,
text: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaavbbb',
host: 'helloworld.com',
from: {
name: 'Test Mail',
email: inquiry.email
},
inquiry: inquiry,
brand: brand,
}, done);
}, callback);
}
Template
<h1>Hi %recipient%</h1>
<p class="text-larger">An enquiry was just submitted to %from.name%:</p>
{% if inquiry.email %}
<p class="text-larger">From
{% if inquiry.name.full %}
<strong>{{ inquiry.name.full }}</strong>
{% endif %}
{% endif %}
Because according to the doc, if you need to send locals you need to send it inside the .send() method:
new Email(/* ... */).send({
recipient: {
firstName: 'Max',
name: 'Stoiber',
}
}, {/* ... */}, function (err, result) {/* ... */});
So in your case it will be:
const sendEmail = function (err, results) {
if (err) return callback(err);
async.each(results.admins, function (admin, done) {
new keystone.Email({ templateName: 'enquiry-notification.html', transport: 'mailgun', engine: cons.nunjucks, root: 'templates/emails' })
.send({
inquiry: inquiry,
brand: brand
}, { /*....*/ }, done);
}, callback);
}
Related
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!
});
I’d like to make a request in my j=Javascript code to the Apostrophe’s server (like PUT, POST…). I did that but it does not work:
{% extends "layout.html" %}
{% block main %}
<div class="main-content">
<h3>Hello world!
{% if not data.user %}
<a class="login-link" href="/login">Login</a>
{% endif %}
</h3>
<p>This is a very bare bones Apostrophe project. Now, get to work and make a real website!</p>
</div>
{{
apos.area(data.page, 'body', {
widgets: {
'apostrophe-images': {
size: 'full'
},
'apostrophe-rich-text': {
toolbar: [ 'Styles', 'Bold', 'Italic', 'Link', 'Unlink' ],
styles: [
{ name: 'Heading', element: 'h3' },
{ name: 'Subheading', element: 'h4' },
{ name: 'Paragraph', element: 'p' }
]
}
}
})
self.apos.tasks.add(self.__meta.name, 'insert-stuff', function(apos, argv, callback) {
var req = self.apos.tasks.getReq();
return self.find(req, { cool: true }).toArray().then(function(err, pieces) {
if (err) {
return callback(err);
}
});
};
}}
{% endblock %}
When I run the script (going to localhost:3000), I receive an error in my console. I have the error :
e.stack: Template render error: (apostrophe-pages:pages/home.html) [Line 34, Column 3]
expected variable end
at Object.exports.prettifyError (C:\Windows\System32\test-project\node_modules\nunjucks\src\lib.js:34:15)
at new_cls.render (C:\Windows\System32\test-project\node_modules\nunjucks\src\environment.js:469:27)
at Object.self.renderBody (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:309:47)
at Object.self.renderForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:176:19)
at Object.self.render (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:173:34)
at Object.self.renderPageForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:666:28)
at C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:349:31
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:726:13
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:269:32
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:44:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:723:17
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:167:37
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at iterate (C:\Windows\System32\test-project\node_modules\async\lib\async.js:260:24)
at Object.async.forEachOfSeries.async.eachOfSeries (C:\Windows\System32\test-project\node_modules\async\lib\async.js:281:9)
:: 2018-08-17T10:43:23+0200: template error at /
Current user: admin
{ Template render error: (apostrophe-pages:pages/home.html) [Line 34, Column 3]
expected variable end
at Object.exports.prettifyError (C:\Windows\System32\test-project\node_modules\nunjucks\src\lib.js:34:15)
at new_cls.render (C:\Windows\System32\test-project\node_modules\nunjucks\src\environment.js:469:27)
at Object.self.renderBody (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:309:47)
at Object.self.renderForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:176:19)
at Object.self.render (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:173:34)
at Object.self.renderPageForModule (C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-templates\index.js:666:28)
at C:\Windows\System32\test-project\node_modules\apostrophe\lib\modules\apostrophe-module\index.js:349:31
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:726:13
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:269:32
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:44:16
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:723:17
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:167:37
at C:\Windows\System32\test-project\node_modules\async\lib\async.js:52:16
at iterate (C:\Windows\System32\test-project\node_modules\async\lib\async.js:260:24)
at Object.async.forEachOfSeries.async.eachOfSeries (C:\Windows\System32\test-project\node_modules\async\lib\async.js:281:9) name: ‘Template render error’ }
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) => {
// ...
});
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.
I have no idea what's going wrong in my app. I'm trying to update a user profile. If a user has already a profile, it should display the current values of the profile. I have a SimpleSchema attached to the user collection.
<template name="updateCustomerProfile">
<div class="container">
<h1>Edit User</h1>
{{#if isReady 'updateCustomerProfile'}}
{{#autoForm collection="Users" doc=getUsers id="profileForm" type="update"}}
<fieldset>
{{> afQuickField name='username'}}
{{> afObjectField name='profile'}}
</fieldset>
<button type="submit" class="btn btn-primary">Update User</button>
<a class="btn btn-link" role="button" href="{{pathFor 'adminDocuments'}}">Back</a>
{{/autoForm}}
{{else}}
Nothing
{{/if}}
</div>
</template>
I have a template helper:
Template.updateCustomerProfile.events({
getUsers: function () {
//return Users.findOne();
return Meteor.user();
}
});
I have an Autoform hook
AutoForm.addHooks(['profileForm'], {
before: {
insert: function(error, result) {
if (error) {
console.log("Insert Error:", error);
AutoForm.debug();
} else {
console.log("Insert Result:", result);
AutoForm.debug();
}
},
update: function(error) {
if (error) {
console.log("Update Error:", error);
AutoForm.debug();
} else {
console.log("Updated!");
console.log('AutoForm.debug()');
}
}
}
});
Have the following route:
customerRoutes.route('/profile/edit', {
name: "updateCustomerProfile",
subscriptions: function (params, queryParams) {
this.register('updateCustomerProfile', Meteor.subscribe('usersAllforCustomer', Meteor.userId()));
},
action: function(params, queryParams) {
BlazeLayout.render('layout_frontend', {
top: 'menu',
main: 'updateCustomerProfile',
footer: 'footer'
});
}
});
and finally the following publication:
Meteor.publish('usersAllforCustomer', function (userId) {
check(userId, String);
var user = Users.findOne({_id: userId});
if (Roles.userIsInRole(this.userId, 'customer')) {
return Users.find({_id: userId});
}
});
And here is the collection:
Users = Meteor.users;
Schema = {};
Schema.UserProfile = new SimpleSchema({
firstName: {
type: String,
optional: true
},
lastName: {
type: String,
optional: true
},
gender: {
type: String,
allowedValues: ['Male', 'Female'],
optional: true
},
organization : {
type: String,
optional: true
}
});
Schema.User = new SimpleSchema({
username: {
type: String,
optional: true
},
emails: {
type: Array,
optional: true
},
"emails.$": {
type: Object
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date,
optional: true,
denyUpdate: true,
autoValue: function() {
if (this.isInsert) {
return new Date();
}
}
},
profile: {
type: Schema.UserProfile,
optional: true
},
services: {
type: Object,
optional: true,
blackbox: true
},
roles: {
type: [String],
optional: true
}
});
Meteor.users.attachSchema(Schema.User);
I'm sure the user object is passed in the publication. I can't update the profile: getting the following error (from Autoform debug):
Update Error: Object {$set: Object}
$set: Object
profile.firstName: "test_firstname"
profile.gender: "Female"
profile.lastName: "test_lastname"
profile.organization: "test_organisation
"username: "test_username"
How to go about updating a profile, staring blind....
You need to change your before AutoForm Hooks.
AutoForm.addHooks(['profileForm'], {
before: {
insert: function(doc) {
console.log('doc: ', doc);
return doc;
},
update: function(doc) {
console.log('doc: ', doc);
return doc;
},
},
});
While the after callback has the js standard (error, result) function signature, the before callback has just one parameter, the doc to insert/update. This is why you are always logging an 'error', it is just the doc you want to insert. Also you need to either return it, or pass it to this.result to actually insert/update the object in the db.
From the docs:
var hooksObject = {
before: {
// Replace `formType` with the form `type` attribute to which this hook applies
formType: function(doc) {
// Potentially alter the doc
doc.foo = 'bar';
// Then return it or pass it to this.result()
return doc; (synchronous)
//return false; (synchronous, cancel)
//this.result(doc); (asynchronous)
//this.result(false); (asynchronous, cancel)
}
},
There are a couple small issues so I'm not sure how to tackle your problem, but here's some things to address.
Publish Method
Local variable user is never used. Were you trying to use
it?
No need to include userId as a function parameter since you have
access to this.userId
The current user's profile is published by default so you don't need to use publish/subscribe unless you want to include/exclude fields, but then I would define a Meteor.publish(null, ...) so that it overrides the default current user publication
Note: If you remove publish usersAllforCustomer function, don't forget to remove it from route updateCustomerProfile
Use Global Helper currentUser
Here's how to update your template to use currentUser instead of getUsers
<template name="updateCustomerProfile">
<div class="container">
<h1>Edit User</h1>
{{#with currentUser}}
{{#autoForm collection="Users" doc=this id="profileForm" type="update"}}
<fieldset>
{{> afQuickField name='username'}}
{{> afObjectField name='profile'}}
</fieldset>
<button type="submit" class="btn btn-primary">Update User</button>
<a class="btn btn-link" role="button" href="{{pathFor 'adminDocuments'}}">Back</a>
{{/autoForm}}
{{else}}
Nothing
{{/with}}
</div>
</template>
Hope this helps.
The meteorpad indeed solved the issue. There was a mistake in the helper. In fact, the original code was:
Template.updateCustomerProfile.events({
getUsers: function () {
return Meteor.user();
}
});
So in above snippet I was using an 'events' instead of an 'helper'. Below is the correct code:
Template.updateCustomerProfile.helpers({
getUsers: function(){
return Meteor.user();
}
});