coreHandler.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. 'use strict';
  2. // nodejs modules
  3. const path = require('path'),
  4. fs = require('fs'),
  5. os = require('os'),
  6. events = require('events');
  7. // npm modules
  8. const config = require('config'),
  9. request = require('request'),
  10. waterfall = require('async/waterfall'),
  11. bcrypt = require('bcrypt'),
  12. passport = require('passport');
  13. // custom modules
  14. const globals = require('./globals'),
  15. stations = require('./stations');
  16. var eventEmitter = new events.EventEmitter();
  17. const edmStation = new stations.Station("edm", {
  18. "genres": ["edm"],
  19. playlist: [
  20. 'gCYcHz2k5x0'
  21. ],
  22. currentSongIndex: 0,
  23. paused: false,
  24. displayName: "EDM",
  25. description: "EDM Music"
  26. });
  27. const chillStation = new stations.Station("chill", {
  28. "genres": ["chill"],
  29. playlist: [
  30. 'gCYcHz2k5x0'
  31. ],
  32. currentSongIndex: 0,
  33. paused: false,
  34. displayName: "Chill",
  35. description: "Chill Music"
  36. });
  37. stations.addStation(edmStation);
  38. stations.addStation(chillStation);
  39. module.exports = {
  40. // module functions
  41. on: (name, cb) => eventEmitter.on(name, cb),
  42. emit: (name, data) => eventEmitter.emit(name, data),
  43. // core route handlers
  44. '/users/register': (user, username, email, password, recaptcha, cb) => {
  45. waterfall([
  46. // verify the request with google recaptcha
  47. (next) => {
  48. request({
  49. url: 'https://www.google.com/recaptcha/api/siteverify',
  50. method: 'POST',
  51. form: {
  52. 'secret': config.get("apis.recaptcha.secret"),
  53. 'response': recaptcha
  54. }
  55. }, next);
  56. },
  57. // check if the response from Google recaptcha is successful
  58. // if it is, we check if a user with the requested username already exists
  59. (response, body, next) => {
  60. let json = JSON.parse(body);
  61. console.log(json);
  62. if (json.success !== true) return next('Response from recaptcha was not successful');
  63. globals.db.models.user.findOne({ 'username': username }, next);
  64. },
  65. // if the user already exists, respond with that
  66. // otherwise check if a user with the requested email already exists
  67. (user, next) => {
  68. if (user) return next(true, { status: 'failure', message: 'A user with that username already exists' });
  69. globals.db.models.user.findOne({ 'email.address': email }, next);
  70. },
  71. // if the user already exists, respond with that
  72. // otherwise, generate a salt to use with hashing the new users password
  73. (user, next) => {
  74. if (user) return next(true, { status: 'failure', message: 'A user with that email already exists' });
  75. bcrypt.genSalt(10, next);
  76. },
  77. // hash the password
  78. (salt, next) => {
  79. bcrypt.hash(password, salt, next)
  80. },
  81. // save the new user to the database
  82. (hash, next) => {
  83. globals.db.models.user.create({
  84. username: username,
  85. email: {
  86. address: email,
  87. verificationToken: globals.utils.generateRandomString(64)
  88. },
  89. services: {
  90. password: {
  91. password: hash
  92. }
  93. }
  94. }, next);
  95. },
  96. // respond with the new user
  97. (newUser, next) => {
  98. next(null, { status: 'success', user: newUser })
  99. }
  100. ], (err, payload) => {
  101. // log this error somewhere
  102. if (err && err !== true) {
  103. console.error(err);
  104. return cb({ status: 'error', message: 'An error occurred while registering for an account' });
  105. }
  106. // respond with the payload that was passed to us earlier
  107. cb(payload);
  108. });
  109. },
  110. '/users/login': (user, identifier, password, cb) => {
  111. waterfall([
  112. // check if a user with the requested identifier exists
  113. (next) => globals.db.models.user.findOne({
  114. $or: [{ 'username': identifier }, { 'email.address': identifier }]
  115. }, next),
  116. // if the user doesn't exist, respond with a failure
  117. // otherwise compare the requested password and the actual users password
  118. (user, next) => {
  119. if (!user) return next(true, { status: 'failure', message: 'User not found' });
  120. bcrypt.compare(password, user.services.password.password, next);
  121. },
  122. // if the user exists, and the passwords match, respond with a success
  123. (result, next) => {
  124. // TODO: Authenticate the user with Passport here I think?
  125. // TODO: We need to figure out how other login methods will work
  126. next(null, {
  127. status: result ? 'success': 'failure',
  128. message: result ? 'Logged in' : 'User not found'
  129. });
  130. }
  131. ], (err, payload) => {
  132. // log this error somewhere
  133. if (err && err !== true) {
  134. console.error(err);
  135. return cb({ status: 'error', message: 'An error occurred while logging in' });
  136. }
  137. // respond with the payload that was passed to us earlier
  138. cb(payload);
  139. });
  140. },
  141. '/u/:username': (username, cb) => {
  142. globals.db.models.user.find({ username }, (err, account) => {
  143. if (err) throw err;
  144. account = account[0];
  145. cb({status: 'success', data: {
  146. username: account.username,
  147. createdAt: account.createdAt,
  148. statistics: account.statistics
  149. }});
  150. });
  151. },
  152. '/users/logout': (req, cb) => {
  153. if (!req.user || !req.user.logged_in) return cb({ status: 'failure', message: `You're not currently logged in` });
  154. req.logout();
  155. cb({ status: 'success', message: `You've been successfully logged out` });
  156. },
  157. '/stations': cb => {
  158. cb(stations.getStations().map(station => {
  159. return {
  160. id: station.id,
  161. playlist: station.playlist,
  162. displayName: station.displayName,
  163. description: station.description,
  164. currentSongIndex: station.currentSongIndex,
  165. users: station.users
  166. }
  167. }));
  168. },
  169. '/stations/join/:id': (id, cb) => {
  170. let station = stations.getStation(id);
  171. if (!station) return cb({ status: 'error', message: `Station with id '${id}' does not exist` });
  172. // session.station_id = id;
  173. station.users++;
  174. cb({ status: 'success', users: station.users });
  175. },
  176. // leaves the users current station
  177. // returns the count of users that are still in that station
  178. '/stations/leave': cb => {
  179. // let station = stations.getStation(session.station_id);
  180. let station = stations.getStation("edm"); // will be removed
  181. if (!station) return cb({ status: 'failure', message: `Not currently in a station, or station doesn't exist` });
  182. // session.station_id = "";
  183. // station.users--;
  184. cb({ status: 'success', users: station.users });
  185. },
  186. '/youtube/getVideo/:query': (query, cb) => {
  187. const params = [
  188. 'part=snippet',
  189. `q=${encodeURIComponent(query)}`,
  190. `key=${config.get('apis.youtube.key')}`,
  191. 'type=video',
  192. 'maxResults=15'
  193. ].join('&');
  194. request(`https://www.googleapis.com/youtube/v3/search?${params}`, (err, res, body) => {
  195. if (err) {
  196. console.error(err);
  197. return cb({ status: 'error', message: 'Failed to search youtube with the requested query' });
  198. }
  199. cb({ status: 'success', data: JSON.parse(body) });
  200. });
  201. },
  202. '/stations/add/:song': (station, song, cb) => {
  203. // if (!session.logged_in) return cb({ status: 'failure', message: 'You must be logged in to add a song' });
  204. const params = [
  205. 'part=snippet,contentDetails,statistics,status',
  206. `id=${encodeURIComponent(song.id)}`,
  207. `key=${config.get('apis.youtube.key')}`
  208. ].join('&');
  209. request(`https://www.googleapis.com/youtube/v3/videos?${params}`, (err, res, body) => {
  210. // TODO: Get data from Wikipedia and Spotify
  211. if (err) {
  212. console.error(err);
  213. return cb({ status: 'error', message: 'Failed to find song from youtube' });
  214. }
  215. const newSong = new globals.db.models.song({
  216. id: json.items[0].id,
  217. title: json.items[0].snippet.title,
  218. duration: globals.utils.convertTime(json.items[0].contentDetails.duration),
  219. thumbnail: json.items[0].snippet.thumbnails.high.url
  220. });
  221. // save the song to the database
  222. newSong.save(err => {
  223. if (err) {
  224. console.error(err);
  225. return cb({ status: 'error', message: 'Failed to save song from youtube to the database' });
  226. }
  227. stations.getStation(station).playlist.push(newSong);
  228. cb({ status: 'success', data: stations.getStation(station.playlist) });
  229. });
  230. });
  231. },
  232. '/songs': cb => {
  233. globals.db.models.song.find({}, (err, songs) => {
  234. if (err) throw err;
  235. cb(songs);
  236. });
  237. },
  238. '/songs/:song/update': (song, cb) => {
  239. globals.db.models.song.findOneAndUpdate({ id: song.id }, song, { upsert: true }, (err, updatedSong) => {
  240. if (err) throw err;
  241. cb(updatedSong);
  242. });
  243. },
  244. '/songs/:song/remove': (song, cb) => {
  245. globals.db.models.song.find({ id: song.id }).remove().exec();
  246. }
  247. };