Masterchoc

Untitled

Jan 27th, 2018
449
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. var _            = require('lodash');
  3. var sha512       = require('js-sha512');
  4. var md5          = require('md5');
  5. var jwt          = require('jsonwebtoken');
  6. var moment       = require('moment');
  7. var jwtBlacklist = require('express-jwt-blacklist');
  8. var PhoneNumber  = require( 'awesome-phonenumber');
  9.  
  10. module.exports = function(app, Sequelize, db)
  11. {
  12.     let Groups = require('../groups/index')(app, Sequelize);
  13.  
  14.     return controller = {
  15.         index: (req, res) =>
  16.         {
  17.             let limit = req.query.limit || 10;
  18.             let page  = req.query.page  || 1;
  19.  
  20.             return app.api.models.users.findAndCountAll(
  21.             {
  22.                 attributes: [
  23.                     'id',
  24.                     'firstname',
  25.                     'lastname',
  26.                     'email',
  27.                     'role',
  28.                     'createdAt',
  29.                     'updatedAt'
  30.                 ],
  31.                 limit: limit,
  32.                 offset: (page - 1) * limit,
  33.                 order: 'createdAt DESC, updatedAt DESC',
  34.             })
  35.             .then(data =>
  36.             {
  37.                 var users = [];
  38.                 data.rows.forEach(u =>
  39.                 {
  40.                     var user = {
  41.                         id: u.id,
  42.                         firstname: u.firstname,
  43.                         lastname: u.lastname,
  44.                         email: u.email,
  45.                         email_md5 : md5(u.email),
  46.                         role: u.role,
  47.                         status: 'offline',
  48.                         createdAt: u.createdAt,
  49.                         updatedAt: u.updatedAt,
  50.                         isFriend: false,
  51.                         friendship_status: null
  52.                     };
  53.  
  54.                     var clients = app.socketManager.getClients();
  55.                     clients.forEach(c => {
  56.                         if(c.id == u.id)
  57.                             user.status = 'online';
  58.                     });
  59.                    
  60.                     if(user.id !== req.session.user.id)
  61.                         users.push(user);
  62.                 });
  63.                
  64.                 return controller.getFriends(req.session.user.id)
  65.                 .then(friends =>
  66.                 {
  67.                     users.forEach((usr, i) => {
  68.                         friends.forEach(friend => {
  69.                             if (friend.friend_id == usr.id || friend.friendship_author == usr.id) {
  70.                                 users[i].isFriend = true;
  71.                                 users[i].friendship_status = friend.friendship_status;
  72.                             }
  73.  
  74.                         });
  75.                     });
  76.  
  77.                     app.success(res, {
  78.                         count   : data.count,
  79.                         rows    : users,
  80.                         nbPages : Math.ceil(data.count / limit)
  81.                     });
  82.                 })
  83.                 .catch(error => {
  84.                     app.error(res, error);
  85.                 })
  86.             })
  87.             .catch(error => app.error(res, error))
  88.         },
  89.         getGroups: (req, res) => {
  90.             if(typeof req.session.user !== 'undefined') {
  91.                 return Groups.getUserGroups(req.session.user.id)
  92.                 .then(groups => {
  93.                     app.success(res, groups);
  94.                 })
  95.                 .catch(err => {
  96.                     app.error(res, error);
  97.                 })
  98.             }
  99.         },
  100.         search: (req, res) =>
  101.         {
  102.             if(typeof req.session.user !== 'undefined') {
  103.                 return app.api.models.users.findAll({
  104.                     attributes: [
  105.                         'id',
  106.                         'firstname',
  107.                         'lastname',
  108.                         'email',
  109.                         'role'
  110.                     ],
  111.                     where: { $or: [
  112.                         {firstname: {$like: '%' + req.body.q + '%'}},
  113.                         {lastname: {$like: '%' + req.body.q + '%'}}
  114.                     ]},
  115.                     order: 'createdAt DESC, updatedAt DESC'
  116.                 })
  117.                 .then(data => {
  118.                     var users = [];
  119.                     data.forEach((u) => {
  120.                         var user = {
  121.                             id: u.id,
  122.                             firstname: u.firstname,
  123.                             lastname: u.lastname,
  124.                             fullname: [u.firstname, u.lastname].join(' '),
  125.                             email: u.email,
  126.                             email_md5: md5(u.email),
  127.                             role: u.role,
  128.                             status: 'offline',
  129.                         };
  130.  
  131.                         var clients = app.socketManager.getClients();
  132.                         clients.forEach((c) => {
  133.                             if (c.id == u.id)
  134.                                 user.status = 'online';
  135.                         });
  136.  
  137.                         users.push(user)
  138.                     });
  139.  
  140.                     app.success(res, users);
  141.                 })
  142.                 .catch(error => app.error(res, error))
  143.             }
  144.         },
  145.         delete: (req, res) =>
  146.         {
  147.             if(typeof req.session.user !== 'undefined') {
  148.                 return app.api.models.users.destroy({
  149.                     where: {
  150.                         id: req.params.id
  151.                     }
  152.                 })
  153.                 .then(user => {
  154.                     if (user > 0) {
  155.                         app.success(res, user, 'Deleted');
  156.                         app.logger.warn('The user "%s" has unregister', req.params.id);
  157.                     }
  158.                     else
  159.                         app.error(res, {statusCode: 404, message: 'Not found'});
  160.                 })
  161.                 .catch(error => app.error(res, error))
  162.             }
  163.         },
  164.         logout: function(req, res)
  165.         {
  166.             if(typeof req.session.user !== 'undefined')
  167.             {
  168.                 var client = app.socketManager.getClientSocket(req.session.user.id);
  169.                 req.session.user.status = 'offline';
  170.                 if(client)
  171.                     client.broadcast.emit('onUserStatus', req.session.user);
  172.  
  173.                 let now = new Date();
  174.                 now.setHours(now.getHours() + 1);
  175.  
  176.                 return app.api.models.users.update({
  177.                     lastSeen: now
  178.                 }, {
  179.                     where: {
  180.                         id: req.session.user.id
  181.                     }
  182.                 })
  183.                 .then(updated => {
  184.                     //Todo: revoke jwt token
  185.                     app.logger.info('%s %s has disconnected' , req.session.user.firstname, req.session.user.lastname);
  186.                     delete req.session.user;
  187.                     app.success(res, null, 'Disconnected');
  188.                 })
  189.                 .catch(error => {
  190.                     console.log(error);
  191.                     app.error(res, error);
  192.                 });
  193.             }
  194.             else
  195.                 app.error(res, {statusCode: 404, message: 'No session to logout'});
  196.         },
  197.         email: function(req, res)
  198.         {
  199.  
  200.         },
  201.         isAuth: function(req, res)
  202.         {
  203.             if(typeof req.session.user !== 'undefined')
  204.                 app.success(res, null, 'Authentified');
  205.             else
  206.                 app.error(res, {statusCode: 401, message: 'Not authentified'});
  207.         },
  208.         auth: function(req, res)
  209.         {
  210.             return app.api.models.users.findAndCountAll(
  211.             {
  212.                 attributes: [
  213.                     'id',
  214.                     'firstname',
  215.                     'lastname',
  216.                     'biography',
  217.                     'email',
  218.                     'activated',
  219.                     'role'
  220.                 ],
  221.                 include: [
  222.                     {model : app.api.models.walls}
  223.                 ],
  224.                 where: {
  225.                     email    : req.body.email || '',
  226.                     password : sha512(req.body.password || '')
  227.                 }
  228.             })
  229.             .then(function(data)
  230.             {
  231.  
  232.                 if(data.rows.length == 0)
  233.                     app.error(res, {statusCode: 401, message: 'Invalid credentials'});
  234.                 else
  235.                 {
  236.                     data = data.rows[0];
  237.                     var user = {
  238.                         id: data.get('id'),
  239.                         firstname: data.get('firstname'),
  240.                         lastname: data.get('lastname'),
  241.                         biography: data.get('biography'),
  242.                         email: data.get('email'),
  243.                         email_md5 : md5(data.get('email')),
  244.                         role: data.get('role'),
  245.                         activated: data.get('activated'),
  246.                         wallId: data.get('walls')[0].get('id')
  247.                     };
  248.  
  249.                     if(user.activated)
  250.                     {
  251.                         user.email_md5 = md5(user.email);
  252.                         delete user.activated;
  253.                         delete user.email;
  254.  
  255.                         user.token = jwt.sign(user, app.config.apis.jwt.key, {
  256.                             expiresIn: 7 * 24 * 60 * 60
  257.                         });
  258.  
  259.                         req.session.user = data;
  260.                         app.logger.info(
  261.                             '%s %s has just connected',
  262.                             req.session.user.firstname,
  263.                             req.session.user.lastname
  264.                         );
  265.                         app.success(res, user, 'Authentified');
  266.                     }
  267.                     else
  268.                         app.error(res, {
  269.                             statusCode: 401,
  270.                             message: 'Your account is not activated. Please check your emails.'
  271.                     });
  272.                 }
  273.             })
  274.             .catch(function(error)
  275.             { app.error(res, error) });
  276.         },
  277.         recover: function(req, res)
  278.         {
  279.  
  280.         },
  281.         view: function(req, res)
  282.         {
  283.             let usersFields = ['id', 'firstname', 'lastname', 'email'];
  284.             return app.api.models.users.findAndCountAll({
  285.                 where: {
  286.                     id: req.params.id
  287.                 },
  288.                 include: [
  289.                     {
  290.                         model : app.api.models.posts,
  291.                         limit : 10,
  292.                         order : [['createdAt', 'DESC']]
  293.                     },
  294.                     {
  295.                         model : app.api.models.groups,
  296.                         limit : 10
  297.                     },
  298.                     {
  299.                         model : app.api.models.walls,
  300.                         limit : 1,
  301.                         include: [
  302.                             {
  303.                                 model: app.api.models.posts,
  304.                                 limit: 10,
  305.                                 order : [['createdAt', 'DESC']],
  306.                                 include: [
  307.                                     { model : app.api.models.users, attributes: usersFields},
  308.                                     { model : app.api.models.medias},
  309.                                 ]
  310.                             }
  311.                         ]
  312.                     }
  313.                 ]
  314.             })
  315.             .then(function(data)
  316.             {
  317.                 if(data)
  318.                 {
  319.                     var u = data.rows[0];
  320.                     var wall = typeof u.walls[0] !== 'undefined' ? u.walls[0] : null;
  321.                     var user =
  322.                     {
  323.                         id          : u.id,
  324.                         isFriend    : false,
  325.                         friendship_status : null,
  326.                         firstname   : u.firstname,
  327.                         lastname    : u.lastname,
  328.                         fullname    : [u.firstname, u.lastname].join(' '),
  329.                         biography   : u.biography,
  330.                         birthdate   : u.birthdate,
  331.                         phone       : u.phone,
  332.                         city        : u.city,
  333.                         country     : u.country,
  334.                         postal_code : u.postal_code,
  335.                         road_number : u.road_number,
  336.                         road_name   : u.road_name,
  337.                         createdAt   : u.createdAt,
  338.                         updatedAt   : u.updatedAt,
  339.                         lastSeen    : u.lastSeen,
  340.                         activated   : u.activated,
  341.                         passwordAt  : u.passwordAt,
  342.                         email       : u.email,
  343.                         email_md5   : md5(u.email),
  344.                         role        : u.role,
  345.                         status      : 'offline',
  346.                         posts       : u.posts,
  347.                         wall        : wall,
  348.                         groups      : u.groups,
  349.                     };
  350.  
  351.                     var clients = app.socketManager.getClients();
  352.                     clients.forEach((c) => {
  353.                         if(c.id == u.id)
  354.                             user.status = 'online';
  355.                     });
  356.  
  357.                     return controller.getFriends(u.id)
  358.                     .then(friends =>
  359.                     {
  360.                         user.friends = friends;
  361.                         user.friends.forEach(friend => {
  362.                             if(friend.friend_id == req.session.user.id) {
  363.                                 user.isFriend = true;
  364.                                 user.friendship_status = friend.friendship_status
  365.                             }
  366.                         });
  367.                         app.success(res, user);
  368.                     })
  369.                     .catch(error => {
  370.                         app.error(res, error);
  371.                     })
  372.                 }
  373.                else
  374.                     app.error(res, {statusCode: 404, message: 'User not found'});
  375.             })
  376.             .catch(function(error)
  377.             { app.error(res, error) })
  378.         },
  379.         getFriends: userId => {
  380.             return new Promise((resolve, reject) => {
  381.                 if(typeof db !== 'undefined') {
  382.                     db.query
  383.                     (`
  384.                     SELECT
  385.                         m.id         as friend_id,
  386.                         m.firstname  as friend_firstname,
  387.                         m.lastname   as friend_lastname,
  388.                         md5(m.email) as friend_email_md5,
  389.                        
  390.                         x.id         as friendship_id,
  391.                         x.author     as friendship_author,
  392.                         x.createdAt  as friendship_createdAt,
  393.                         x.updatedAt  as friendship_updatedAt,
  394.                         x.status     as friendship_status
  395.                    
  396.                     FROM
  397.                         users m
  398.                     JOIN
  399.                     (
  400.                         (
  401.                             SELECT
  402.                                 f.id,
  403.                                 f.friendId as user_id,
  404.                                 f.status,
  405.                                 f.author,
  406.                                 f.createdAt,
  407.                                 f.updatedAt
  408.                             FROM
  409.                                 friends f
  410.                             WHERE
  411.                                 f.userId = ?
  412.                         )
  413.                         UNION
  414.                         (
  415.                             SELECT
  416.                                 t.id,
  417.                                 t.userId as user_id,
  418.                                 t.status,
  419.                                 t.author,
  420.                                 t.createdAt,
  421.                                 t.updatedAt
  422.                             FROM
  423.                                 friends t
  424.                             WHERE
  425.                                 t.friendId = ?
  426.                         )
  427.                     ) x
  428.                     ON
  429.                         x.user_id = m.id`,
  430.                         {
  431.                             replacements: [userId, userId]
  432.                         }
  433.                     )
  434.                     .then(friendships => resolve(_.uniqBy(friendships, 'friend_id')[0]))
  435.                     .catch(error => reject(error))
  436.                 }
  437.                 else
  438.                     reject('No db instance')
  439.             });
  440.         },
  441.         setActivationKey: function(email, key, cb) {
  442.             return app.api.models.users.update({activationKey: key}, {where: {email: email}}).then(data =>
  443.             {
  444.                 if(typeof cb == 'function')
  445.                     cb(data);
  446.             }).catch(error => { console.log(error) });
  447.         },
  448.         setResetKey: function(email, key, cb) {
  449.             return app.api.models.users.update({resetKey: key}, {where: {email: email}}).then(data =>
  450.             {
  451.                 if(typeof cb == 'function')
  452.                     cb(data);
  453.             }).catch(error => { console.log(error) });
  454.         },
  455.         generateToken: function(cb) {
  456.             require('crypto').randomBytes(48, function(err, buffer) {
  457.                 if(!err && typeof cb == 'function')
  458.                     cb(buffer.toString('hex'));
  459.                 else
  460.                     cb(false);
  461.             });
  462.         },
  463.         activation: function(req, res) {
  464.             app.success(res, {}, 'Ok');
  465.         },
  466.         request: function(req, res)
  467.         {
  468.             return app.api.models.users.findOne({
  469.                 where: {email: req.body.email}
  470.             }).then(exists =>
  471.             {
  472.                 if(exists !== null)
  473.                 {
  474.                     return controller.generateToken(token =>
  475.                     {
  476.                         let template =  null, action = {};
  477.                         if(req.params.type == 'activation') {
  478.                             template = 'register';
  479.                             action.url = `${app.config.application.hostname}/#/activation/${exists.email}/${token}`;
  480.                             controller.setActivationKey(exists.email, token);
  481.                         }
  482.                         else if(req.params.type == 'password') {
  483.                             template = 'password';
  484.                             action.url = `${app.config.application.hostname}/#/passwordReset/${exists.email}/${token}`;
  485.                             controller.setResetKey(exists.email, token);
  486.                         }
  487.  
  488.                         if(template)
  489.                         {
  490.                             app.mailer.send(template, {
  491.                                 from: 'no-reply@network.crafting.fr',
  492.                                 to: exists.email,
  493.                                 action: action
  494.                             });
  495.  
  496.                             app.success(res, {}, 'Email sent');
  497.                         }
  498.                     });
  499.                 }
  500.                 else
  501.                     app.error(res, {statusCode: 409, message: 'This user doesn\'t exists'});
  502.             });
  503.         },
  504.         validateEmail(email) {
  505.             let sQtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
  506.             let sDtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
  507.             let sAtom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
  508.             let sQuotedPair = '\\x5c[\\x00-\\x7f]';
  509.             let sDomainLiteral = '\\x5b(' + sDtext + '|' + sQuotedPair + ')*\\x5d';
  510.             let sQuotedString = '\\x22(' + sQtext + '|' + sQuotedPair + ')*\\x22';
  511.             let sDomain_ref = sAtom;
  512.             let sSubDomain = '(' + sDomain_ref + '|' + sDomainLiteral + ')';
  513.             let sWord = '(' + sAtom + '|' + sQuotedString + ')';
  514.             let sDomain = sSubDomain + '(\\x2e' + sSubDomain + ')*';
  515.             let sLocalPart = sWord + '(\\x2e' + sWord + ')*';
  516.             let sAddrSpec = sLocalPart + '\\x40' + sDomain; // complete RFC822 email address spec
  517.             let sValidEmail = '^' + sAddrSpec + '$'; // as whole string
  518.  
  519.             var reValidEmail = new RegExp(sValidEmail);
  520.  
  521.             return reValidEmail.test(email);
  522.         },
  523.         save: function(req, res)
  524.         {
  525.             if(!req.body.id)
  526.                 app.error(res, {statusCode: 409, message: 'This user doesn\'t exists'});
  527.             else {
  528.                 return app.api.models.users.findOne({
  529.                     where: {id: req.body.id}
  530.                 })
  531.                 .then(exists =>
  532.                 {
  533.                     if(req.session.user.role == 'user' && req.body.id != req.session.user.id)
  534.                         app.error(res, {statusCode: 401, message: 'You dont have the permission to edit this data'});
  535.                     else
  536.                     {
  537.                         if(exists !== null)
  538.                         {
  539.                             let user = {
  540.                                 updatedAt: new Date()
  541.                             };
  542.                             if(req.body.firstname)
  543.                                 user.firstname = req.body.firstname;
  544.                             if(req.body.lastname)
  545.                                 user.lastname = req.body.lastname;
  546.                             if(req.body.biography)
  547.                                 user.biography = req.body.biography;
  548.                             if(req.body.birthdate)
  549.                                 user.birthdate = moment(req.body.birthdate).format('YYYY-MM-DD');
  550.                             if(req.body.email && controller.validateEmail(req.body.email))
  551.                                 user.email = req.body.email;
  552.                             if(req.body.activated)
  553.                                 user.activated = req.body.activated === true ? 1 : 0;
  554.                             if(req.body.role)
  555.                                 user.role = req.body.role;
  556.                             if(typeof req.body.phone !== 'undefined')
  557.                             {
  558.                                 if(req.body.phone.toString().length > 0) {
  559.                                     let pn = new PhoneNumber(req.body.phone, 'FR');
  560.                                     if(pn.a.valid !== true) {
  561.                                         app.error(res, {statusCode: 406, message: 'The phone number is not valid'});
  562.                                         return false;
  563.                                     }
  564.                                     else if(pn.a.type !== 'mobile') {
  565.                                         app.error(res, {statusCode: 406, message: 'Only mobile numbers are allowed'});
  566.                                         return false;
  567.                                     }
  568.                                     else
  569.                                         user.phone = req.body.phone;
  570.                                 }
  571.                                 else
  572.                                     user.phone = null
  573.                             }
  574.                             if(typeof req.body.city !== 'undefined')
  575.                                 user.city = req.body.city;
  576.                             if(typeof req.body.country !== 'undefined')
  577.                                 user.country = req.body.country;
  578.                             if(typeof req.body.postal_code !== 'undefined')
  579.                                 user.postal_code = req.body.postal_code;
  580.                             if(typeof req.body.road_number !== 'undefined')
  581.                                 user.road_number = req.body.road_number;
  582.                             if(typeof req.body.road_name !== 'undefined')
  583.                                 user.road_name = req.body.road_name;
  584.  
  585.                             if(typeof req.body.actual_password !== 'undefined'
  586.                                 && typeof req.body.new_password !== 'undefined'
  587.                                 && typeof req.body.confirm_password !== 'undefined') {
  588.  
  589.                                     if(sha512(req.body.actual_password) == exists.password)
  590.                                     {
  591.                                         if(req.body.confirm_password == req.body.new_password) {
  592.                                             user.password = sha512(req.body.new_password);
  593.                                             user.passwordAt = new Date();
  594.                                         }
  595.                                         else
  596.                                         {
  597.                                             app.error(res, {
  598.                                                 statusCode: 406,
  599.                                                 message: 'The new password doesn\'t match the confirmation'
  600.                                             });
  601.                                             return false;
  602.                                         }
  603.                                     }
  604.                                     else {
  605.                                         app.error(res, {
  606.                                             statusCode: 406,
  607.                                             message: 'The actual password is wrong'
  608.                                         });
  609.                                         return false;
  610.                                     }
  611.                             }
  612.  
  613.                             return app.api.models.users.update(user, {where: {id: exists.id}}).then(function(data)
  614.                             {
  615.                                 return app.api.models.users.findAndCountAll({where: {id: exists.id}}).then(function(payload) {
  616.                                     let u = payload.rows[0];
  617.                                     app.success(res, {
  618.                                         id: u.get('id'),
  619.                                         firstname: u.get('firstname'),
  620.                                         lastname: u.get('lastname'),
  621.                                         biography: u.get('biography'),
  622.                                         birthdate: u.get('birthdate'),
  623.                                         phone: u.get('phone'),
  624.                                         city: u.get('city'),
  625.                                         country: u.get('country'),
  626.                                         postal_code: u.get('postal_code'),
  627.                                         road_number: u.get('road_number'),
  628.                                         road_name: u.get('road_name'),
  629.                                         email: u.get('email'),
  630.                                         email_md5: md5(u.get('email')),
  631.                                         activated: u.get('activated'),
  632.                                         ban: u.get('ban'),
  633.                                         ban_start: u.get('ban_start'),
  634.                                         ban_end: u.get('ban_end'),
  635.                                         role: u.get('role'),
  636.                                         passwordAt: u.get('passwordAt'),
  637.                                         activatedAt: u.get('activatedAt'),
  638.                                         resetAt: u.get('resetAt'),
  639.                                         createdAt: u.get('createdAt'),
  640.                                         updatedAt: u.get('updatedAt')
  641.                                     });
  642.                                 })
  643.                             })
  644.                             .catch(error => { app.error(res, error); });
  645.                         }
  646.                         else
  647.                             app.error(res, {statusCode: 409, message: 'This user doesn\'t exists'});
  648.                     }
  649.                 });
  650.             }
  651.         },
  652.         create: function(req, res)
  653.         {
  654.             return app.api.models.users.findOne({
  655.                 where: {email: req.body.email}
  656.             })
  657.             .then(function(exists)
  658.             {
  659.                 if(exists === null)
  660.                 {
  661.                     req.body.password  = sha512(req.body.password);
  662.                     req.body.activated = 1;
  663.                     req.body.role      = 'user';
  664.  
  665.                     return app.api.models.users.create(req.body)
  666.                     .then(user =>
  667.                     {
  668.                         var u =
  669.                         {
  670.                             id        : user.id,
  671.                             firstname : user.firstname,
  672.                             lastname  : user.lastname,
  673.                             birthdate : '0000-00-00 00:00:00',
  674.                             email_md5 : md5(user.email),
  675.                             role      : user.role,
  676.                             status : 'offline'
  677.                         };
  678.  
  679.                         return app.api.models.walls.create({
  680.                             privacy : 'public',
  681.                             userId  : user.id
  682.                         })
  683.                         .then(wall =>
  684.                         {
  685.                             app.events.emit('mail:send', {
  686.                                 template: 'register',
  687.                                 to: user.email
  688.                             });
  689.  
  690.                             app.success(res, u, 'Created');
  691.                         })
  692.                         .catch(error => console.log(error))
  693.                     })
  694.                     .catch(error => app.error(res, error));
  695.                 }
  696.                 else
  697.                     app.error(res, {
  698.                         statusCode: 409,
  699.                         message: 'Email already used'
  700.                     });
  701.             });
  702.         }
  703.     };
  704. };
RAW Paste Data