Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 170 additions & 197 deletions README.md

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions demo-using-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// load the mysql library
var mysql = require('promise-mysql');

// create a connection to our Cloud9 server
var connection = mysql.createPool({
host : 'localhost',
user : 'ziad_saab', // CHANGE THIS :)
password : '',
database: 'reddit',
connectionLimit: 10
});

// load our API and pass it the connection
var RedditAPI = require('./reddit');

var myReddit = new RedditAPI(connection);

// We call this function to create a new user to test our API
// The function will return the newly created user's ID in the callback
myReddit.createUser({
username: 'PM_ME_CUTES',
password: 'abc123'
})
.then(newUserId => {
// Now that we have a user ID, we can use it to create a new post
// Each post should be associated with a user ID
console.log('New user created! ID=' + newUserId);

return myReddit.createPost({
title: 'Hello Reddit! This is my first post',
url: 'http://www.digg.com',
userId: newUserId
});
})
.then(newPostId => {
// If we reach that part of the code, then we have a new post. We can print the ID
console.log('New post created! ID=' + newPostId);
})
.catch(error => {
console.log(error.stack);
});
38 changes: 0 additions & 38 deletions index.js

This file was deleted.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"license": "ISC",
"dependencies": {
"bcrypt": "^0.8.6",
"mysql": "^2.10.2"
"bcrypt-as-promised": "^1.1.0",
"promise-mysql": "^3.0.1"
}
}
100 changes: 100 additions & 0 deletions reddit-crawl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
var request = require('request-promise');
var mysql = require('promise-mysql');
var RedditAPI = require('./reddit');

function getSubreddits() {
return request(/* fill in the URL, it's always the same */)
.then(response => {
// Parse response as JSON and store in variable called result
var response; // continue this line

// Use .map to return a list of subreddit names (strings) only
return response.data.children.map(/* write a function */)
});
}

function getPostsForSubreddit(subredditName) {
return request(/* fill in the URL, it will be based on subredditName */)
.then(
response => {
// Parse the response as JSON and store in variable called result
var response; // continue this line


return response.data.children
.filter(/* write a function */) // Use .filter to remove self-posts
.map(/* write a function */) // Use .map to return title/url/user objects only

}
);
}

function crawl() {
// create a connection to the DB
var connection = mysql.createPool({
host : 'localhost',
user : 'root',
password : '',
database: 'reddit',
connectionLimit: 10
});

// create a RedditAPI object. we will use it to insert new data
var myReddit = new RedditAPI(connection);

// This object will be used as a dictionary from usernames to user IDs
var users = {};

/*
Crawling will go as follows:

1. Get a list of popular subreddits
2. Loop thru each subreddit and:
a. Use the `createSubreddit` function to create it in your database
b. When the creation succeeds, you will get the new subreddit's ID
c. Call getPostsForSubreddit with the subreddit's name
d. Loop thru each post and:
i. Create the user associated with the post if it doesn't exist
2. Create the post using the subreddit Id, userId, title and url
*/

// Get a list of subreddits
getSubreddits()
.then(subredditNames => {
subredditNames.forEach(subredditName => {
var subId;
myReddit.createSubreddit({name: subredditName})
.then(subredditId => {
subId = subredditId;
return getPostsForSubreddit(subredditName)
})
.then(posts => {
posts.forEach(post => {
var userIdPromise;
if (users[post.user]) {
userIdPromise = Promise.resolve(users[post.user]);
}
else {
userIdPromise = myReddit.createUser({
username: post.user,
password: 'abc123'
})
.catch(function(err) {
return users[post.user];
})
}

userIdPromise.then(userId => {
users[post.user] = userId;
return myReddit.createPost({
subredditId: subId,
userId: userId,
title: post.title,
url: post.url
});
});
});
});
});
});
}
166 changes: 59 additions & 107 deletions reddit.js
Original file line number Diff line number Diff line change
@@ -1,114 +1,66 @@
var bcrypt = require('bcrypt');
var bcrypt = require('bcrypt-as-promised');
var HASH_ROUNDS = 10;

module.exports = function RedditAPI(conn) {
return {
createUser: function(user, callback) {

// first we have to hash the password...
bcrypt.hash(user.password, HASH_ROUNDS, function(err, hashedPassword) {
if (err) {
callback(err);
}
else {
conn.query(
'INSERT INTO `users` (`username`,`password`, `createdAt`) VALUES (?, ?, ?)', [user.username, hashedPassword, null],
function(err, result) {
if (err) {
/*
There can be many reasons why a MySQL query could fail. While many of
them are unknown, there's a particular error about unique usernames
which we can be more explicit about!
*/
if (err.code === 'ER_DUP_ENTRY') {
callback(new Error('A user with this username already exists'));
}
else {
callback(err);
}
}
else {
/*
Here we are INSERTing data, so the only useful thing we get back
is the ID of the newly inserted row. Let's use it to find the user
and return it
*/
conn.query(
'SELECT `id`, `username`, `createdAt`, `updatedAt` FROM `users` WHERE `id` = ?', [result.insertId],
function(err, result) {
if (err) {
callback(err);
}
else {
/*
Finally! Here's what we did so far:
1. Hash the user's password
2. Insert the user in the DB
3a. If the insert fails, report the error to the caller
3b. If the insert succeeds, re-fetch the user from the DB
4. If the re-fetch succeeds, return the object to the caller
*/
callback(null, result[0]);
}
}
);
}
}
);
}
});
},
createPost: function(post, callback) {
conn.query(
'INSERT INTO `posts` (`userId`, `title`, `url`, `createdAt`) VALUES (?, ?, ?, ?)', [post.userId, post.title, post.url, null],
function(err, result) {
if (err) {
callback(err);
}
else {
/*
Post inserted successfully. Let's use the result.insertId to retrieve
the post and send it to the caller!
*/
conn.query(
'SELECT `id`,`title`,`url`,`userId`, `createdAt`, `updatedAt` FROM `posts` WHERE `id` = ?', [result.insertId],
function(err, result) {
if (err) {
callback(err);
class RedditAPI {
constructor(conn) {
this.conn = conn;
}

createUser(user) {
/*
first we have to hash the password. we will learn about hashing next week.
the goal of hashing is to store a digested version of the password from which
it is infeasible to recover the original password, but which can still be used
to assess with great confidence whether a provided password is the correct one or not
*/
return bcrypt.hash(user.password, HASH_ROUNDS)
.then(hashedPassword => {
return this.conn.query('INSERT INTO users (username,password, createdAt, updatedAt) VALUES (?, ?, NOW(), NOW())', [user.username, hashedPassword]);
})
.then(result => {
return result.insertId;
})
.catch(error => {
// Special error handling for duplicate entry
if (error.code === 'ER_DUP_ENTRY') {
throw new Error('A user with this username already exists');
}
else {
callback(null, result[0]);
throw error;
}
}
);
}
}
);
},
getAllPosts: function(options, callback) {
// In case we are called without an options parameter, shift all the parameters manually
if (!callback) {
callback = options;
options = {};
}
var limit = options.numPerPage || 25; // if options.numPerPage is "falsy" then use 25
var offset = (options.page || 0) * limit;

conn.query(`
SELECT \`id\`,\`title\`,\`url\`,\`userId\`, \`createdAt\`, \`updatedAt\`
FROM \`posts\`
ORDER BY \`createdAt\` DESC
LIMIT ? OFFSET ?
`, [limit, offset],
function(err, results) {
if (err) {
callback(err);
}
else {
callback(null, results);
}
}
);
});
}

createPost(post) {
return this.conn.query(
`
INSERT INTO posts (userId, title, url, createdAt, updatedAt)
VALUES (?, ?, ?, NOW(). NOW())`,
[post.userId, post.title, post.url]
)
.then(result => {
return result.insertId;
});
}

getAllPosts() {
/*
strings delimited with ` are an ES2015 feature called "template strings".
they are more powerful than what we are using them for here. one feature of
template strings is that you can write them on multiple lines. if you try to
skip a line in a single- or double-quoted string, you would get a syntax error.

therefore template strings make it very easy to write SQL queries that span multiple
lines without having to manually split the string line by line.
*/
return this.conn.query(
`
SELECT id, title, url, userId, createdAt, updatedAt
FROM posts
ORDER BY createdAt DESC
LIMIT 25`
);
}
}
}

module.exports = RedditAPI;
Loading