index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. 'use strict'
  2. const express = require('express')
  3. const fileUpload = require('express-fileupload')
  4. var cors = require('cors')
  5. const md5 = require('md5')
  6. const fs = require('fs')
  7. //добавляю к консольному выводу дату и время
  8. function console_log(fmt, ...aparams){
  9. fmt = (new Date()).toJSON().substr(0, 19)+' '+fmt
  10. console.log(fmt, ...aparams)
  11. }
  12. // создание экземпляра http-сервера
  13. const app = express()
  14. // метод .use задает команды, которые будут выполнены до разбора GET/POST команд
  15. // декодирует параметры запроса
  16. app.use( express.urlencoded() )
  17. app.use( express.json() )
  18. app.use(fileUpload())
  19. app.use('/up/images', cors(), express.static(__dirname +'/images') )
  20. app.use('/swagger', cors(), express.static(__dirname +'/swagger') )
  21. // логгирую все входящие запросы
  22. app.use((req, res, next)=>{
  23. console_log('[express] %s %s request from %s, body: %s', req.method, req.path, req.ip, JSON.stringify(req.body))
  24. next()
  25. })
  26. const registeredUsers = []
  27. const movies = [
  28. {movieId: 1, category:'Фентези', name: 'Дюна',
  29. description: 'Атрейдесы прибывают на планету, где им никто не рад. Фантастический эпос Дени Вильнёва с шестью «Оскарами»',
  30. age: "12", images: ['duna_wallpaper.jpg'], poster: 'duna.webp', tags: [],
  31. filters: ['new','inTrend','forMe']},
  32. {movieId: 2, category:'Фентези', name: 'Легенда о Зелёном Рыцаре',
  33. description: 'Наследник короля принимает вызов таинственного рыцаря. Захватывающее фэнтези по мотивам средневековой поэмы',
  34. age: "18", images: ['green_wp.jpg'], poster: 'green.webp', tags: [],
  35. filters: ['new','inTrend','forMe']},
  36. {movieId: 3, category:'Мелодрама', name: 'Главный герой', age: "16",
  37. images: ['main_hero_wp.jpg'], poster: 'maincharacter.webp', tags: [],
  38. filters: ['new','inTrend','forMe'], description: 'Парень по имени Парень счастлив. Он живет в лучшем в мире городе Городе, работает на лучшей в мире работе в Банке и дружит с охранником по имени Приятель. И его совершенно не волнует, что Банк грабят по нескольку раз на дню, а улицы Города напоминают зону военных действий. Единственное, чего Парню не хватает для полного счастья — идеальной девушки, к которой у него имеется точный список требований. И вот однажды он видит на улице красотку, точь-в-точь как в его мечтах. Эта встреча изменит не только нашего главного героя, но и перевернёт весь известный ему мир.'},
  39. {movieId: 4, category:'Исторический', name: 'Петр I: Последний царь и первый император',
  40. age: "12", images: ['piter_1_wp.webp'], poster: 'Petr1.webp', tags: [],
  41. filters: ['new','inTrend','forMe'], description: 'Фигура императора Петра Великого, как и эпоха его становления и правления, до сих пор будоражит умы людей во всем мире. Создатели отвечают на вопросы, как занять престол, когда ты — четырнадцатый ребенок в семье; как отвоевать выход к морю, когда в стране нет профессиональной армии и флота; как за несколько десятилетий вывести в мировые лидеры страну, с которой раньше никто не считался и многие другие.'},
  42. {movieId: 5, category:'Приключения', name: 'Либерея: Охотники за сокровищами',
  43. age: "12", images: ['liber_wp.jpg'], poster: 'Liberia.webp', tags: [],
  44. filters: ['new','inTrend','forMe'], description: 'При строительстве столичного метро рабочие обнаруживают драгоценный оклад, который доказывает — легендарная Библиотека Ивана Грозного существует! Но находка оказывается забыта на долгие годы, и уже в наше время попадает в руки ни о чем не подозревающего Ильи. Теперь его жизнь в опасности, ведь за старинным артефактом начинают охоту могущественные силы! Парень вынужден объединиться со странным незнакомцем, который утверждает, что оклад — это ключ к обнаружению Библиотеки. Помочь им в поисках и разгадать древние шифры берется красотка-филолог Арина. Теперь, чтобы обрести новые ключи-подсказки и приблизиться к разгадке, трио авантюристов нужно побывать в затерянных и опасных местах, разбросанных по всей России: от Вологды до Нарьян-Мара, на суше, под водой и даже в тайных подземельях Кремля.'},
  45. {movieId: 6, category:'Исторический', name: 'Грозный папа', age: '6',
  46. images: ['papa2.jpg'], poster: 'formidableDad.webp', tags: [],
  47. filters: ['new','inTrend','forMe'], description: 'Поссорившись с сыном, царь Иван Грозный случайно ранит его – как на знаменитой картине Репина. Жизнь царевича на волоске. Чтобы все исправить, Грозный хочет отправиться в прошлое с помощью волшебного гримуара. Однако что-то пошло не так, и Грозный попадает в наше время, где знакомится с семьей Осиповых. Никита Осипов – неудачливый археолог и такой же неудачливый отец. Он давно потерял контакт с детьми – Ромкой и Полей. Но теперь они вместе отправляются в путешествие, чтобы помочь Грозному отыскать гримуар и спасти царевича.'},
  48. {movieId: 7, category:'Мелодрама', name: 'Сердце пармы', age: '16',
  49. images: ['parma_wp.jpg'], poster: 'Heart.webp', tags: [],
  50. filters: ['new','inTrend','forMe'], description: 'Русский князь Михаил и юная Тиче — дети разных народов, разных миров и разных богов. Любовь молодого воителя и ведьмы-ламии кажется невозможной, но преодолевает все запреты, запуская маховик рока. Отныне только от Михаила зависит будущее родной пармы, древних суровых земель, напоенных чудодейственной мощью кровавых языческих богов. Здесь сталкиваются герои и призраки, князья и шаманы, вогулы и московиты. Здесь расстаться с жизнью — не так страшно, как выбрать между долгом, верностью братству и любовью к единственной женщине на свете.'},
  51. {movieId: 8, category:'Мультики', name: 'Большое путешествие. Специальная доставка',
  52. age: '6', images: ['big_wp.jpg'], poster: 'bigAdventure.webp', tags: [],
  53. filters: ['new','inTrend','forMe'], description: 'Прошло время с тех пор, как заяц Оскар и медведь Мик-Мик в компании своих друзей вернули домой маленького панду. С тех пор жили они спокойно и размеренно. Мик-Мик заботился о своих пчелах, а Оскар организовал в лесу американские горки. И вот однажды к берегу Мик-Мика прибивает корзину с малышом гризли. Кто-то снова перепутал адреса, а разбираться с этим придется Мик-Мику и Оскару. В компании друзей они отправляются в новое путешествие — теперь, чтобы вернуть домой малыша гризли.'},
  54. {movieId: 9, category:'Мелодрама', name: 'Шрамы Парижа', age: '18',
  55. images: ['paris_wp.webp'], poster: 'scars.webp', tags: [],
  56. filters: ['new','inTrend','forMe'], description: 'В ноябре 2015 года Париж пережил самые страшные теракты в своей истории. Жертвами тщательно спланированных актов насилия стали почти 400 человек. Но на этом преступники не собирались останавливаться. Чтобы предотвратить будущие угрозы, двум агентам придется провести одно из самых крупных расследований в истории Старого Света и помешать преступникам нанести новый удар. Теперь в опасности не только Франция, но и вся Европа.'},
  57. {movieId: 10, category:'Ужасы', name: 'Паранормальные явления. Дом призраков',
  58. age: '16', images: ['house_wp.webp'], poster: 'Paranormal.webp', tags: [],
  59. filters: ['new','inTrend','forMe'], description: 'Когда-то Шон был популярным видеоблогером, сделавшим имя на экстремальных роликах, в которых он бросал вызов собственным страхам, но однажды вляпался в скандал и потерял всех спонсоров. Записав видео с извинениями и снова получив финансирование, парень возвращается с новым леденящим душу проектом. Шон собирается провести ночной стрим из дома с привидениями, где более 100 лет назад повесилась одинокая женщина, а после неоднократно фиксировалась паранормальная активность.'}
  60. ]
  61. const chats = [
  62. {chatId: '1', movieId: 1, name: 'Всё о дюне'},
  63. {chatId: '2', movieId: 2, name: 'Кто такой зелёный рыцарь?'},
  64. {chatId: '3', movieId: 4, name: 'Петр первый: великий император или разрушитель руси'}
  65. ]
  66. const chatMessages = []
  67. function findUserByEmail(email) {
  68. for (let i = 0; i < registeredUsers.length; i++) {
  69. if (registeredUsers[i].email == email)
  70. return registeredUsers[i]
  71. }
  72. return null
  73. }
  74. function findUserByToken(token) {
  75. for (let i = 0; i < registeredUsers.length; i++) {
  76. if (registeredUsers[i].token == token)
  77. return registeredUsers[i]
  78. }
  79. return null
  80. }
  81. app.get('/', (req, res) => {
  82. res.sendFile(
  83. __dirname + '/swagger/index.html')
  84. })
  85. app.options('/auth/register', cors())
  86. app.post('/auth/register', cors(), (req,res)=>{
  87. try {
  88. if(req.body.email==undefined)
  89. throw new Error('Not found "email" param')
  90. if(req.body.password==undefined)
  91. throw new Error('Not found "password" param')
  92. if(req.body.firstName==undefined)
  93. throw new Error('Not found "firstName" param')
  94. if(req.body.lastName==undefined)
  95. throw new Error('Not found "lastName" param')
  96. const re = new RegExp(`^[a-z0-9]+@[a-z0-9]+\.[a-z]{1,3}$`)
  97. if (!re.test(req.body.email))
  98. throw new Error('Param "email" don`t match template')
  99. let user = findUserByEmail(req.body.email)
  100. if (user == null) {
  101. let user = null
  102. let token = null
  103. do {
  104. token = Math.ceil(Math.random() * 999998)
  105. user = findUserByToken(token)
  106. } while (user != null);
  107. registeredUsers.push({
  108. email: req.body.email,
  109. password: req.body.password,
  110. firstName: req.body.firstName,
  111. lastName: req.body.lastName,
  112. token: token,
  113. avatar: ''
  114. })
  115. }
  116. res.status(201)
  117. } catch (error) {
  118. res.statusMessage = error.message
  119. res.status(400)
  120. }
  121. res.end()
  122. })
  123. app.options('/auth/login', cors())
  124. app.post('/auth/login', cors(), (req,res)=>{
  125. try {
  126. if(req.body.email==undefined)
  127. throw new Error('Not found "email" param')
  128. if(req.body.password==undefined)
  129. throw new Error('Not found "password" param')
  130. let user = findUserByEmail(req.body.email)
  131. if (user == null) res.status(404)
  132. else {
  133. if (user.password != req.body.password)
  134. throw new Error('Wrong password')
  135. res.json({token: user.token})
  136. }
  137. } catch (error) {
  138. res.statusMessage = error.message
  139. res.status(400)
  140. }
  141. res.end()
  142. })
  143. function checkAuth(req){
  144. if(req.headers.authorization==undefined)
  145. throw new Error('No Authorization header')
  146. let parts = req.headers.authorization.split(' ')
  147. if (parts.length == 2) {
  148. if (parts[0] == 'Bearer') {
  149. let user = findUserByToken(parts[1])
  150. if (user == null)
  151. throw new Error('User not found')
  152. return user
  153. } else
  154. throw new Error('Unsupported Authorization method')
  155. } else
  156. throw new Error('Bad Authorization content')
  157. }
  158. app.options('/movies', cors())
  159. app.get('/movies', cors(), (req,res)=>{
  160. try {
  161. if (typeof req.query.filter == 'undefined')
  162. throw new Error('Filter is required parameter')
  163. // checkAuth(req)
  164. let filtered = movies
  165. .filter(m => m.filters.includes(req.query.filter))
  166. let mapped = filtered.map(m => {
  167. return {
  168. movieId: m.movieId,
  169. name: m.name,
  170. description: m.description,
  171. age: m.age,
  172. images: m.images,
  173. poster: m.poster,
  174. tags: m.tags,
  175. category: m.category
  176. }
  177. })
  178. res.json(mapped)
  179. } catch (error) {
  180. res.statusMessage = error.message
  181. res.status(400)
  182. }
  183. res.end()
  184. })
  185. function userModel (user) {
  186. const fileName = md5(user.email)+'.jpg'
  187. const userObj = {
  188. userId: user.token,
  189. firstName: user.firstName,
  190. lastName: user.lastName,
  191. email: user.email,
  192. avatar: user.avatar
  193. }
  194. if (fs.existsSync(__dirname + '/images/'+ fileName))
  195. userObj.avatar = fileName
  196. return [userObj]
  197. }
  198. app.options('/user', cors())
  199. app.get('/user', cors(), (req,res)=>{
  200. try {
  201. let user = checkAuth(req)
  202. res.json(userModel(user))
  203. } catch (error) {
  204. res.statusMessage = error.message
  205. res.status(401)
  206. }
  207. res.end()
  208. })
  209. app.options('/user/chats', cors())
  210. app.get('/user/chats', cors(), (req,res)=>{
  211. try {
  212. checkAuth(req)
  213. res.json(chats)
  214. } catch (error) {
  215. res.statusMessage = error.message
  216. res.status(401)
  217. }
  218. res.end()
  219. })
  220. function getChatMessage(message, user) {
  221. const fileName = md5(user.email)+'.jpg'
  222. return {
  223. chatId: message.chatId,
  224. messageId: message.messageId,
  225. creationDateTime: message.creationDateTime,
  226. firstName: user.firstName,
  227. lastName: user.lastName,
  228. avatar: fileName,
  229. text: message.text
  230. }
  231. }
  232. app.options('/chats/:movieId', cors())
  233. /**
  234. * Список чатов фильма (неавторизованный доступ)
  235. */
  236. app.get('/chats/:movieId', cors(), (req,res)=>{
  237. try {
  238. let result = []
  239. for (let i = 0; i < chats.length; i++) {
  240. if (chats[i].movieId == req.params.movieId) {
  241. result.push(chats[i])
  242. }
  243. }
  244. res.json(result)
  245. } catch (error) {
  246. res.statusMessage = error.message
  247. res.status(401)
  248. }
  249. res.end()
  250. })
  251. function findMovieById (movieId) {
  252. for (let i = 0; i < movies.length; i++) {
  253. if(movies[i].movieId == movieId)
  254. return movies[i]
  255. }
  256. return null
  257. }
  258. /**
  259. * Создание чата для фильма
  260. */
  261. app.post('/chats/:movieId', cors(), (req,res)=>{
  262. try {
  263. // только авторизованный может добавить чат
  264. checkAuth(req)
  265. const chatName = req.body.name.trim()
  266. if (chatName == '')
  267. throw new Error('Empty chat name')
  268. const movie = findMovieById(req.params.movieId)
  269. if(movie == null)
  270. throw new Error('Movie Id not found')
  271. let chat = null
  272. let maxChatId = 0
  273. for (let i = 0; i < chats.length; i++) {
  274. maxChatId = Math.max(maxChatId, chats[i].chatId)
  275. if(chats[i].name == chatName && chats[i].movieId == req.params.movieId) {
  276. chat = chats[i]
  277. }
  278. }
  279. if (chat == null) {
  280. chat = {
  281. chatId: (maxChatId+1).toString(),
  282. movieId: req.params.movieId,
  283. name: chatName
  284. }
  285. chats.push(chat)
  286. }
  287. res.json(chat)
  288. } catch (error) {
  289. res.statusMessage = error.message
  290. res.status(400)
  291. }
  292. res.end()
  293. })
  294. /**
  295. * Список сообщений чата
  296. */
  297. app.options('/chats/:chatId/messages', cors())
  298. app.get('/chats/:chatId/messages', cors(), (req,res)=>{
  299. try {
  300. checkAuth(req)
  301. let messages = []
  302. // console_log('try get chat messages for chatId: %s', req.params.chatId)
  303. for (let i = 0; i < chatMessages.length; i++) {
  304. if(chatMessages[i].chatId == req.params.chatId) {
  305. let user = findUserByToken(chatMessages[i].userId)
  306. if(user != null) {
  307. let chatMessage = getChatMessage(chatMessages[i], user)
  308. messages.push(chatMessage)
  309. }
  310. }
  311. }
  312. res.json(messages)
  313. } catch (error) {
  314. res.statusMessage = error.message
  315. res.status(401)
  316. }
  317. res.end()
  318. })
  319. function dateToMysql(xDate) {
  320. return xDate.getFullYear().toString(10)
  321. + '-' + (xDate.getMonth()+1).toString(10).padStart(2,'0')
  322. + '-' + xDate.getDate().toString(10).padStart(2,'0')
  323. + ' ' + xDate.getHours().toString(10).padStart(2,'0')
  324. + ':' + xDate.getMinutes().toString(10).padStart(2,'0')
  325. }
  326. app.post('/chats/:chatId/messages', cors(), (req,res)=>{
  327. try {
  328. let user = checkAuth(req)
  329. if (req.body.text.trim() == '')
  330. throw new Error('Empty text')
  331. let newMessage = {
  332. chatId: req.params.chatId,
  333. messageId: chatMessages.length + 1,
  334. creationDateTime: dateToMysql(new Date()),
  335. userId: user.token,
  336. text: req.body.text
  337. }
  338. chatMessages.push(newMessage)
  339. res.json(getChatMessage(newMessage, user))
  340. } catch (error) {
  341. res.statusMessage = error.message
  342. res.status(401)
  343. }
  344. res.end()
  345. })
  346. app.options('/user/avatar', cors())
  347. app.post('/user/avatar', cors(), (req, res) => {
  348. try {
  349. // console.log(req.files)
  350. if(req.body.token==undefined)
  351. throw new Error('Not found "token" param in body')
  352. const user = findUserByToken(req.body.token)
  353. if(!user) throw new Error('User not found')
  354. const { file } = req.files
  355. if (!file) throw new Error('No file in request')
  356. const fileName = md5(user.email)+'.jpg'
  357. // console_log('try save avatar: %s', fileName)
  358. file.mv(__dirname + '/images/' + fileName)
  359. res.json(userModel(user))
  360. } catch (error) {
  361. res.statusMessage = error.message
  362. res.status(400)
  363. }
  364. res.end()
  365. })
  366. // запуск сервера на порту 8080
  367. app.listen(3019, '0.0.0.0', ()=>{
  368. console_log('HTTP сервер успешно запущен на порту 3019')
  369. }).on('error', (err)=>{
  370. console_log('ошибка запуска HTTP сервера: %s', err)
  371. })