How to accept Bitcoin and USD payments using Stripe? - javascript

REFERENCE:
https://stripe.com/docs/sources/bitcoin
QUESTION:
I am trying to integrate Bitcoin payments to my code using Stripe.
From my understanding of the docs, I should create a Source object by creating a new form and on submit feed the data (in this case email and amount) to :
stripe.createSource({
type: 'bitcoin',
amount: 1000,
currency: 'usd',
owner: {
email: 'jenny.rosen#example.com',
},
}).then(function(result) {
// handle result.error or result.source
});
Then pass the result to the server to charge the Source object. Only issue: I don't see how to pass the result to the server. Should I create a new handler ?
var handler = StripeCheckout.configure({
key: 'key',
locale: 'auto',
name: 'website',
description: 'Secure Payment',
token: function(token) {
$('#stripeToken').val(token.id);
$("#stripeEmail").val(token.email);
$('form').submit();
}
});
I am lost.
Here is the code I currently have that works perfectly for USD payments.
MY CODE:
clientside (payment.ejs)
<% include partials/header %>
<div class="background">
<div class="message">
<div class="paymentBlock">
<h1 class="title">TITLE</h1>
<form class="paymentForm" action="/payment/charge" method="POST">
<input id="inputAmount" class="amountInput" name="amount" type="number/>
<input type="hidden" id="stripeToken" name="stripeToken" />
<input type="hidden" id="stripeEmail" name="stripeEmail"/>
<button type="submit" class="btn btn-success" id="paymentButton" >Submit Payment</button>
</form>
</div>
</div>
</div>
<script src="https://checkout.stripe.com/checkout.js"></script>
<script>
var handler = StripeCheckout.configure({
key: 'key',
locale: 'auto',
name: 'website',
description: 'Secure Payment',
token: function(token) {
$('#stripeToken').val(token.id);
$("#stripeEmail").val(token.email);
$('form').submit();
}
});
$('#paymentButton').on('click', function(e) {
e.preventDefault();
$('#error_explanation').html('');
var amount = $('#inputAmount').val();
amount = amount.replace(/\$/g, '').replace(/\,/g, '');
amount = parseFloat(amount);
if (isNaN(amount)) {
$('#error_explanation').html('<p>Please enter a valid amount in USD ($).</p>');
}
else if (amount < 1.00) {
$('#error_explanation').html('<p>Payment amount must be at least $1.</p>');
}
else {
amount = amount * 100;
handler.open({
amount: Math.round(amount)
})
}
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
</script>
<% include partials/indexScripts %>
serverside (payment.js)
router.post("/", (req, res) => {
var amount = req.body.amount;
var object;
var ARef = admin.database().ref("ref");
var ARefList;
amount = amount * 100;
var object = {
amount: amount,
email: email,
inversedTimeStamp: now
}
stripe.customers.create({
email: req.body.stripeEmail,
source: req.body.stripeToken
})
.then(customer =>
stripe.charges.create({
amount: amount,
description: "desc",
currency: "usd",
customer: customer.id
})
)
.then(charge =>
ARef.transaction(function(dbAmount){
if (!dbAmount) {
dbAmount = 0;
}
dbAmount = dbAmount + amount/100;
return dbAmount;
})
)
.then( () =>
ARef.push(object)
)
.then ( () =>
ARefList.push(object)
)
.then( () =>
res.render("received",{amount:amount/100})
);
});

Thank you to "pksk321" from the freenode IRC #stripe for helping me with this !
Apparently, all that was needed was to add bitcoin: true to the handler, like so :
clientside
var handler = StripeCheckout.configure({
key: 'key',
locale: 'auto',
name: 'website',
description: 'Secure Payment',
bitcoin: true,
token: function(token) {
$('#stripeToken').val(token.id);
$("#stripeEmail").val(token.email);
$('form').submit();
}
});

Related

Trying to update the body of a blog post using Sequelize and Handlebars.js

Im creating an online blog posting webpage that uses Sequelize and Handlebars to display the pages. The problem I am having is that I'm trying to give the user an option to update the contents of a post they have made, and I am able to change the title of the post and save it, but no matter what I do, changing the body of the post does not save as well.
Heres the code for the Post Model (/models/Post.js)
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/connection');
// Post Model
class Post extends Model {
static upvote(body, models) {
return models.Vote.create({
user_id: body.user_id,
post_id: body.post_id,
}).then(() => {
return Post.findOne({
where: {
id: body.post_id,
},
attributes: [
'id',
'title',
'created_at',
// use raw MySQL aggregate function query to get a count of how many votes the post has and return it under the name `vote_count`
[
sequelize.literal(
'(SELECT COUNT(*) FROM vote WHERE post.id = vote.post_id)'
),
'vote_count',
],
]
})
})
}
}
Post.init(
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
post_text: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [4]
}
},
user_id: {
type: DataTypes.INTEGER,
references: {
model: 'user',
key: 'id'
}
}
},
{
sequelize,
freezeTableName: true,
underscored: true,
modelName: 'post'
}
);
module.exports = Post;
This is the handlebars template (views/edit-post.handlebars)
<article>
← Back to dashboard
<h2>
Edit Post
</h2>
<form class="edit-post-form">
<div>
<input name="post-title" type="text" value="{{post.title}}" />
</div>
<div>
<textarea name="post-text">{{post.post_text}}</textarea>
</div>
<div>
{{post.vote_count}} {{format_plural "point" post.vote_count}} by you on {{format_date post.created_at}}
|
{{post.comments.length}} {{format_plural "comment" post.comments.length}}
</div>
<button type="submit">Save post</button>
<button type="button" class="delete-post-btn">Delete post</button>
</form>
</article>
<form class="comment-form">
<div>
<textarea name="comment-body"></textarea>
</div>
<div>
<button type="submit">add comment</button>
</div>
</form>
{{> comments post.comments}}
<script src="/javascript/edit-post.js"></script>
<script src="/javascript/delete-post.js"></script>
<script src="/javascript/comment.js"></script>
Here's the Javascript file (/public/javascript/edit-post.js)
async function editFormHandler(event) {
event.preventDefault();
const id = window.location.toString().split('/')[
window.location.toString().split('/').length - 1
];
const title = document.querySelector('input[name="post-title"]').value;
const post_text = document.querySelector('textarea[name="post-text"]').value;
const response = await fetch(`/api/posts/${id}`, {
method: 'PUT',
body: JSON.stringify({
title,
post_text
}),
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
document.location.replace('/dashboard/');
} else {
alert(response.statusText);
}
}
document
.querySelector('.edit-post-form')
.addEventListener('submit', editFormHandler);
And the route function where the routes are located (controllers/dashboard-routes.js)
const router = require('express').Router();
const sequelize = require('../config/connection');
const { Post, User, Comment } = require('../models');
const withAuth = require('../utils/auth');
router.get('/', withAuth, (req, res) => {
Post.findAll({
where: {
// use the ID from the session
user_id: req.session.user_id,
},
attributes: [
'id',
'title',
'post_text',
'created_at',
[
sequelize.literal(
'(SELECT COUNT(*) FROM vote WHERE post.id = vote.post_id)'
),
'vote_count',
],
],
include: [
{
model: Comment,
attributes: ['id', 'comment_text', 'post_id', 'user_id', 'created_at'],
include: {
model: User,
attributes: ['username'],
},
},
{
model: User,
attributes: ['username'],
},
],
})
.then((dbPostData) => {
// serialize data before passing to template
const posts = dbPostData.map((post) => post.get({ plain: true }));
res.render('dashboard', { posts, loggedIn: true });
})
.catch((err) => {
console.log(err);
res.status(500).json(err);
});
});
//GET api/posts/edit/i
router.get('/edit/:id', withAuth, (req, res) => {
Post.findOne({
where: {
id: req.params.id,
},
attributes: [
'id',
'title',
'post_text',
'user_id',
'created_at',
[
`(SELECT COUNT(*) FROM vote WHERE post.id = vote.post_id)`,
'vote_count',
],
],
include: [
{
model: Comment,
attributes: ['id', 'comment_text', 'post_id', 'user_id', 'created_at'],
include: {
model: User,
attributes: ['username'],
},
},
{
model: User,
attributes: ['username'],
},
],
})
.then((dbPostData) => {
if (dbPostData) {
const post = dbPostData.get({ plain: true });
res.render('edit-post', {
post,
loggedIn: true,
});
} else {
res.status(404).end();
}
})
.catch((err) => {
console.log(err);
res.status(500).json(err);
});
});
module.exports = router;
Im pretty sure this is all of the related code but the rest is on: https://github.com/Zacharycampanelli/hi_tech_blog
I've tried changing the object the body is stored in from to and neither seem to be giving me any luck

Error: MercadoPago.js - Could not find HTML element for provided id: MPHiddenInputPaymentMethod

I have an Nuxt desktop app here, am i am facing this problem with MERCADO PAGO API.
This is part of the Mercado documentation : https://www.mercadopago.com.br/developers/pt/guides/online-payments/checkout-api/v2/testing
The problem is:
I make use of the index.vue that makes use of the default form from the documentation itself:
<template>
<div >
<form id="form-checkout" >
<input type="text" name="cardNumber" id="form-checkout__cardNumber" />
<input type="text" name="cardExpirationMonth" id="form-checkout__cardExpirationMonth" />
<input type="text" name="cardExpirationYear" id="form-checkout__cardExpirationYear" />
<input type="text" name="cardholderName" id="form-checkout__cardholderName"/>
<input type="email" name="cardholderEmail" id="form-checkout__cardholderEmail"/>
<input type="text" name="securityCode" id="form-checkout__securityCode" />
<select name="issuer" id="form-checkout__issuer"></select>
<select name="identificationType" id="form-checkout__identificationType"></select>
<input type="text" name="identificationNumber" id="form-checkout__identificationNumber"/>
<select name="installments" id="form-checkout__installments"></select>
<button type="submit" id="form-checkout__submit">Pagar</button>
<progress value="0" class="progress-bar">Carregando...</progress>
</form>
</div>
</template>
nuxt.config:
export default{
head:{
...
script: [
{ src: 'https://sdk.mercadopago.com/js/v2' },
{src: "/js/index.js", },
}
}
and the "/js/index.js file in static folder:
//i know the YOU_PUBLIC_KEY must be from the Mercado Pago account, i have one already
const mp = new MercadoPago('YOUR_PUBLIC_KEY', {
locale: 'pt-BR',
})
const cardForm = mp.cardForm({
amount: '100.5',
autoMount: true,
processingMode: 'aggregator',
form: {
id: 'form-checkout',
cardholderName: {
id: 'form-checkout__cardholderName',
placeholder: 'Cardholder name',
},
cardholderEmail: {
id: 'form-checkout__cardholderEmail',
placeholder: 'Email',
},
cardNumber: {
id: 'form-checkout__cardNumber',
placeholder: 'Card number',
},
cardExpirationMonth: {
id: 'form-checkout__cardExpirationMonth',
placeholder: 'MM'
},
cardExpirationYear: {
id: 'form-checkout__cardExpirationYear',
placeholder: 'YYYY'
},
securityCode: {
id: 'form-checkout__securityCode',
placeholder: 'CVV',
},
installments: {
id: 'form-checkout__installments',
placeholder: 'Total installments'
},
identificationType: {
id: 'form-checkout__identificationType',
placeholder: 'Document type'
},
identificationNumber: {
id: 'form-checkout__identificationNumber',
placeholder: 'Document number'
},
issuer: {
id: 'form-checkout__issuer',
placeholder: 'Issuer'
}
},
callbacks: {
onFormMounted: error => {
if (error) return console.warn('Form Mounted handling error: ', error)
console.log('Form mounted')
},
onFormUnmounted: error => {
if (error) return console.warn('Form Unmounted handling error: ', error)
console.log('Form unmounted')
},
onIdentificationTypesReceived: (error, identificationTypes) => {
if (error) return console.warn('identificationTypes handling error: ', error)
console.log('Identification types available: ', identificationTypes)
},
onPaymentMethodsReceived: (error, paymentMethods) => {
if (error) return console.warn('paymentMethods handling error: ', error)
console.log('Payment Methods available: ', paymentMethods)
},
onIssuersReceived: (error, issuers) => {
if (error) return console.warn('issuers handling error: ', error)
console.log('Issuers available: ', issuers)
},
onInstallmentsReceived: (error, installments) => {
if (error) return console.warn('installments handling error: ', error)
console.log('Installments available: ', installments)
},
onCardTokenReceived: (error, token) => {
if (error) return console.warn('Token handling error: ', error)
console.log('Token available: ', token)
},
onSubmit: (event) => {
event.preventDefault();
const cardData = cardForm.getCardFormData();
console.log('CardForm data available: ', cardData)
},
onFetching: (resource) => {
console.log('Fetching resource: ', resource)
// Animate progress bar
const progressBar = document.querySelector('.progress-bar')
progressBar.removeAttribute('value')
return () => {
progressBar.setAttribute('value', '0')
}
},
}
})
Anyone can help me with this? And is facing more problems with the MERCADO PAGO's API?
Thanks for the atention!
Use iframe to render custom vanilla HTML/CSS/JS.
I'm using vue/quasar2 and my workaround was using an Iframe to render a custom page which can use this lib, you can see the directory structure here.
I created a page to and use an iframe tag to render the custom page:
<template>
<q-page class="flex flex-center">
<iframe width="100%" height="545vh" style="border: none;" :src='`static_site/index.html?obj=${JSON.stringify(getQueryParameters())}`'/>
</q-page>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'PageIndex',
setup () {
function getQueryParameters () {
return {
name: "name",
email: "name#gmail.com",
valor: "20"
}
}
return {
getQueryParameters,
}
}
})
</script>
I'm using the query parameters ( obj ) in the iframe src to pass down information from vue to the lib. In the callbacks section of the cardForm function, I used the URLSearchParams object to catch the information I sended, you can see it here.
OBS: I just found this workaround yesterday and haven't tested in production yet, but in dev it's working fine, will test soon in production and update this answer, hope it's useful to you.

Stripe Subscription JS issues

I use the following code to create a stripe customer and create a subscription for that customer
stripe.subscriptions.create({
customer: customer_id,
items: [
{
plan: plan_id
},
],
trial_period_days: 7,
expand: ['latest_invoice.payment_intent'],
}, function (err, subscription) {
stripe.customers.create({
email: email,
source: token,
metadata: {
userid: userId
}
}, function (err, customer) {
I want users to subscription to subscribe to a 1 week free trial, Monthly plan (they pay after 7 days)
I am running into the following issues
Stripe does not validate the credit card number on prod. You can enter any credit card number and the payment goes through
Can someone please help?
Here's the client code
submit(event) {
event.preventDefault();
if ( this.props.planid) {
setTimeout(() => {
this.setState({loading: true});
}, 0);
var planId = this.props.planid;
this.props.stripe.createToken({name: "Name"}).then( token => {
axios.post("/charge/process/" + planId, {
token: token.id
}).then(response => {
if (response.data.status === "ok") {
this.setState({complete: true, loading: false})
} else if (response.data.error) {
this.setState({error: response.data.error, loading: false});
}
}).catch(error => {
this.setState({error: "There was an error.", loading: false});
});
})
} else {
this.setState({error: "Invalid plan id", loading: false});
}
}
render() {
if (this.state.loading) return (<Spinner />)
if (this.state.complete) return (<p align="center">Thanks for purchasing! You can now access the book summaries.</p>);
var error = "";
if (this.state.error) {
error = (<p>{this.state.error}</p>);
}
return (
<div className="CheckoutForm" style={{width: "400px", margin: "0 auto", "textAlign": "center"}}>
{error}
<fieldset>
<legend className="card-only">Pay with card</legend>
<div className="container">
<div id="example4-card" className="StripeElement StripeElement--empty">
<div style={{width: "300px", margin: "0 auto"}}>
<CardElement/>
<br/>
<br/>
<button onClick={this.submit.bind(this)} className="btn btn-primary purchasebtn">Purchase</button>
</div>
</div>
</div>
</fieldset>
</div>
);
}

How to charge a stripe card in meteor

Having a warm time trying to charge a card in Meteor. The error I get is: Exception while invoking method 'chargeCard' Error: Match error: Expected string, got object. I do get the modal where I typed in the email and card number but after pressing the pay button, in terminal I get the error message.
How to call the charge function properly? I cant find any tutorial that matches closely the way I implement it.
The setup is very basic. I also have jquery installed.
Template:
<template name="hello">
<form id="myForm">
<input type="text" id="amount" name="amount"/>
<input type="hidden" id="stripeToken" name="stripeToken"/>
<input type="hidden" id="stripeEmail" name="stripeEmail"/>
</form>
<hr>
<button id="customButton">Pay</button>
</template>
js:
if (Meteor.isClient) {
Template.hello.helpers({
});
Template.hello.events({
'click button': function (e) {
e.preventDefault();
var handler = StripeCheckout.configure({
key: 'pk_test_rand',
token: function(token) {
$("#stripeToken").val(token.id);
$("#stripeEmail").val(token.email);
$("#myForm").submit();
Meteor.call('chargeCard', token); // this seem not right?
}
});
// Showing the pop up Stripe dialog
var amount = $("#amount").val() *100;
// Open Checkout with further options
handler.open({
name: 'Demo Site',
description: '2 widgets ($20.00)',
amount: amount
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
}
});
Meteor.startup(function(){
$.getScript('https://checkout.stripe.com/checkout.js', function(){
// script has loaded
});
});
}
if (Meteor.isServer) {
Meteor.methods({
'chargeCard': function(stripeToken) {
check(stripeToken, String);
var Stripe = StripeAPI('sk_test_rand');
Stripe.charges.create({
source: stripeToken,
amount: 5000, // this is equivalent to $50
currency: 'usd'
}, function(err, charge) {
console.log(err, charge);
});
}
});
}
It seems you're passing the whole token object:
Meteor.call('chargeCard', token);
But your chargeCard() method expects a string:
check(stripeToken, String);
So you need to either pass only the token id:
Meteor.call('chargeCard', token.id);
or change your chargeCard() method to expect and use the whole token object:
Meteor.methods({
'chargeCard': function(stripeToken) {
check(stripeToken, Object);
var Stripe = StripeAPI('sk_test_rand');
Stripe.charges.create({
source: stripeToken.id,
...

Delete URL field in Discover Meteor Book

I'm trying to delete the "URL" part in the Discover Meteor Book so that it is more of a chat room. I deleted and changed the post_submit.js so that the error statement return is true. When I goto the "Submit Post" and only submit in the "Text" field and no "URL", it won't submit.
Do I need to change anything in the post_item.js or posts.js in the lib/collections?
Most of this issue seems to be in the Chapter 7 part when the method is introduced.
post_submit.js
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('postInsert', post, function(error, result) {
// display the error to the user and abort
if (error)
return true;
// show this result but route anyway
if (result.postExists)
return true;
Router.go('postsList', {_id: result._id});
});
}
});
post_item.js
Template.postItem.helpers({
ownPost: function() {
return this.userId === Meteor.userId();
},
domain: function() {
var a = document.createElement('a');
a.href = this.url;
return a.hostname;
}
});
Does this.url do anyting?
posts.js
Posts = new Mongo.Collection('posts');
Meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String,
url: String
});
var postWithSameLink = Posts.findOne({url: postAttributes.url});
if (postWithSameLink) {
return {
postExists: true,
_id: postWithSameLink._id
}
}
var user = Meteor.user();
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
var postId = Posts.insert(post);
return {
_id: postId
};
}
});
url is in here too..
post_submit.html
<template name="postSubmit">
<form class="main form page">
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="" placeholder="Name your post" class="form-control"/>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
</template>
You should remove all references to the url if you don't want it.
Check out the project: git clone https://github.com/DiscoverMeteor/Microscope.git
Look for all files containing "url": grep -i -r url *
Results:
client/templates/posts/post_item.html: <h3>{{title}}<span>{{domain}}</span></h3>
client/templates/posts/post_submit.js: url: $(e.target).find('[name=url]').val(),
client/templates/posts/post_submit.js: if (errors.title || errors.url)
client/templates/posts/post_submit.html: <div class="form-group {{errorClass 'url'}}">
client/templates/posts/post_submit.html: <label class="control-label" for="url">URL</label>
client/templates/posts/post_submit.html: <input name="url" id="url" type="text" value="" placeholder="Your URL" class="form-control"/>
client/templates/posts/post_submit.html: <span class="help-block">{{errorMessage 'url'}}</span>
client/templates/posts/post_edit.js: url: $(e.target).find('[name=url]').val(),
client/templates/posts/post_edit.js: if (errors.title || errors.url)
client/templates/posts/post_edit.html: <div class="form-group {{errorClass 'url'}}">
client/templates/posts/post_edit.html: <label class="control-label" for="url">URL</label>
client/templates/posts/post_edit.html: <input name="url" id="url" type="text" value="{{url}}" placeholder="Your URL" class="form-control"/>
client/templates/posts/post_edit.html: <span class="help-block">{{errorMessage 'url'}}</span>
client/templates/posts/post_item.js: a.href = this.url;
lib/collections/posts.js: return (_.without(fieldNames, 'url', 'title').length > 0);
lib/collections/posts.js: return errors.title || errors.url;
lib/collections/posts.js: if (!post.url)
lib/collections/posts.js: errors.url = "Please fill in a URL";
lib/collections/posts.js: url: String
lib/collections/posts.js: if (errors.title || errors.url)
lib/collections/posts.js: throw new Meteor.Error('invalid-post', "You must set a title and URL for your post");
lib/collections/posts.js: var postWithSameLink = Posts.findOne({url: postAttributes.url});
server/fixtures.js: url: 'http://sachagreif.com/introducing-telescope/',
server/fixtures.js: url: 'http://meteor.com',
server/fixtures.js: url: 'http://themeteorbook.com',
server/fixtures.js: url: 'http://google.com/?q=test-' + i,
Those are the files the need to be modified.
post_item.html:
<template name="postItem">
<div class="post">
upvote
<div class="post-content">
<h3>{{title}}</h3>
<p>
{{pluralize votes "Vote"}},
submitted by {{author}},
{{pluralize commentsCount "comment"}}
{{#if ownPost}}Edit{{/if}}
</p>
</div>
Discuss
</div>
</template>
post_submit.js:
Template.postSubmit.onCreated(function() {
Session.set('postSubmitErrors', {});
});
Template.postSubmit.helpers({
errorMessage: function(field) {
return Session.get('postSubmitErrors')[field];
},
errorClass: function (field) {
return !!Session.get('postSubmitErrors')[field] ? 'has-error' : '';
}
});
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
title: $(e.target).find('[name=title]').val()
};
var errors = validatePost(post);
if (errors.title)
return Session.set('postSubmitErrors', errors);
Meteor.call('postInsert', post, function(error, result) {
// display the error to the user and abort
if (error)
return throwError(error.reason);
Router.go('postPage', {_id: result._id});
});
}
});
post_submit.html:
<template name="postSubmit">
<form class="main form page">
<div class="form-group {{errorClass 'title'}}">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="" placeholder="Name your post" class="form-control"/>
<span class="help-block">{{errorMessage 'title'}}</span>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
</template>
post_edit.js:
Template.postEdit.onCreated(function() {
Session.set('postEditErrors', {});
});
Template.postEdit.helpers({
errorMessage: function(field) {
return Session.get('postEditErrors')[field];
},
errorClass: function (field) {
return !!Session.get('postEditErrors')[field] ? 'has-error' : '';
}
});
Template.postEdit.events({
'submit form': function(e) {
e.preventDefault();
var currentPostId = this._id;
var postProperties = {
title: $(e.target).find('[name=title]').val()
}
var errors = validatePost(postProperties);
if (errors.title)
return Session.set('postEditErrors', errors);
Posts.update(currentPostId, {$set: postProperties}, function(error) {
if (error) {
// display the error to the user
throwError(error.reason);
} else {
Router.go('postPage', {_id: currentPostId});
}
});
},
'click .delete': function(e) {
e.preventDefault();
if (confirm("Delete this post?")) {
var currentPostId = this._id;
Posts.remove(currentPostId);
Router.go('home');
}
}
});
post_edit.html:
<template name="postEdit">
<form class="main form page">
<div class="form-group {{errorClass 'title'}}">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="{{title}}" placeholder="Name your post" class="form-control"/>
<span class="help-block">{{errorMessage 'title'}}</span>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary submit"/>
<hr/>
<a class="btn btn-danger delete" href="#">Delete post</a>
</form>
</template>
post_item.js:
Template.postItem.helpers({
ownPost: function() {
return this.userId == Meteor.userId();
},
upvotedClass: function() {
var userId = Meteor.userId();
if (userId && !_.include(this.upvoters, userId)) {
return 'btn-primary upvotable';
} else {
return 'disabled';
}
}
});
Template.postItem.events({
'click .upvotable': function(e) {
e.preventDefault();
Meteor.call('upvote', this._id);
}
});
posts.js:
Posts = new Mongo.Collection('posts');
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); },
});
Posts.deny({
update: function(userId, post, fieldNames) {
// may only edit the following fields:
return (_.without(fieldNames, 'title').length > 0);
}
});
Posts.deny({
update: function(userId, post, fieldNames, modifier) {
var errors = validatePost(modifier.$set);
return errors.title;
}
});
validatePost = function (post) {
var errors = {};
if (!post.title)
errors.title = "Please fill in a headline";
return errors;
}
Meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String
});
var errors = validatePost(postAttributes);
if (errors.title)
throw new Meteor.Error('invalid-post', "You must set a title for your post");
var user = Meteor.user();
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date(),
commentsCount: 0,
upvoters: [],
votes: 0
});
var postId = Posts.insert(post);
return {
_id: postId
};
},
upvote: function(postId) {
check(this.userId, String);
check(postId, String);
var affected = Posts.update({
_id: postId,
upvoters: {$ne: this.userId}
}, {
$addToSet: {upvoters: this.userId},
$inc: {votes: 1}
});
if (! affected)
throw new Meteor.Error('invalid', "You weren't able to upvote that post");
}
});
fixtures.js:
// Fixture data
if (Posts.find().count() === 0) {
var now = new Date().getTime();
// create two users
var tomId = Meteor.users.insert({
profile: { name: 'Tom Coleman' }
});
var tom = Meteor.users.findOne(tomId);
var sachaId = Meteor.users.insert({
profile: { name: 'Sacha Greif' }
});
var sacha = Meteor.users.findOne(sachaId);
var telescopeId = Posts.insert({
title: 'Introducing Telescope',
userId: sacha._id,
author: sacha.profile.name,
submitted: new Date(now - 7 * 3600 * 1000),
commentsCount: 2,
upvoters: [], votes: 0
});
Comments.insert({
postId: telescopeId,
userId: tom._id,
author: tom.profile.name,
submitted: new Date(now - 5 * 3600 * 1000),
body: 'Interesting project Sacha, can I get involved?'
});
Comments.insert({
postId: telescopeId,
userId: sacha._id,
author: sacha.profile.name,
submitted: new Date(now - 3 * 3600 * 1000),
body: 'You sure can Tom!'
});
Posts.insert({
title: 'Meteor',
userId: tom._id,
author: tom.profile.name,
submitted: new Date(now - 10 * 3600 * 1000),
commentsCount: 0,
upvoters: [], votes: 0
});
Posts.insert({
title: 'The Meteor Book',
userId: tom._id,
author: tom.profile.name,
submitted: new Date(now - 12 * 3600 * 1000),
commentsCount: 0,
upvoters: [], votes: 0
});
for (var i = 0; i < 10; i++) {
Posts.insert({
title: 'Test post #' + i,
author: sacha.profile.name,
userId: sacha._id,
submitted: new Date(now - i * 3600 * 1000 + 1),
commentsCount: 0,
upvoters: [], votes: 0
});
}
}

Categories

Resources