diff options
Diffstat (limited to 'controllers')
| -rw-r--r-- | controllers/albumsController.js | 133 | ||||
| -rw-r--r-- | controllers/authController.js | 89 | ||||
| -rw-r--r-- | controllers/tokenController.js | 63 | ||||
| -rw-r--r-- | controllers/uploadController.js | 309 |
4 files changed, 360 insertions, 234 deletions
diff --git a/controllers/albumsController.js b/controllers/albumsController.js index 8b5b5dd..79e3ab8 100644 --- a/controllers/albumsController.js +++ b/controllers/albumsController.js @@ -5,93 +5,116 @@ let albumsController = {} albumsController.list = function(req, res, next){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - let fields = ['id', 'name'] + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) - if(req.params.sidebar === undefined) - fields.push('timestamp') - - db.table('albums').select(fields).where('enabled', 1).then((albums) => { + let fields = ['id', 'name'] + + if(req.params.sidebar === undefined) + fields.push('timestamp') - if(req.params.sidebar !== undefined) - return res.json({ success: true, albums }) + db.table('albums').select(fields).where({enabled: 1, userid: user[0].id}).then((albums) => { + + if(req.params.sidebar !== undefined) + return res.json({ success: true, albums }) - let ids = [] - for(let album of albums){ - album.date = new Date(album.timestamp * 1000) - album.date = album.date.getFullYear() + '-' + (album.date.getMonth() + 1) + '-' + album.date.getDate() + ' ' + (album.date.getHours() < 10 ? '0' : '') + album.date.getHours() + ':' + (album.date.getMinutes() < 10 ? '0' : '') + album.date.getMinutes() + ':' + (album.date.getSeconds() < 10 ? '0' : '') + album.date.getSeconds() + let ids = [] + for(let album of albums){ + album.date = new Date(album.timestamp * 1000) + album.date = album.date.getFullYear() + '-' + (album.date.getMonth() + 1) + '-' + album.date.getDate() + ' ' + (album.date.getHours() < 10 ? '0' : '') + album.date.getHours() + ':' + (album.date.getMinutes() < 10 ? '0' : '') + album.date.getMinutes() + ':' + (album.date.getSeconds() < 10 ? '0' : '') + album.date.getSeconds() - ids.push(album.id) - } + ids.push(album.id) + } - db.table('files').whereIn('albumid', ids).select('albumid').then((files) => { + db.table('files').whereIn('albumid', ids).select('albumid').then((files) => { - let albumsCount = {} - - for(let id of ids) albumsCount[id] = 0 - for(let file of files) albumsCount[file.albumid] += 1 - for(let album of albums) album.files = albumsCount[album.id] + let albumsCount = {} + + for(let id of ids) albumsCount[id] = 0 + for(let file of files) albumsCount[file.albumid] += 1 + for(let album of albums) album.files = albumsCount[album.id] - return res.json({ success: true, albums }) + return res.json({ success: true, albums }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + } albumsController.create = function(req, res, next){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - let name = req.body.name - if(name === undefined || name === '') - return res.json({ success: false, description: 'No album name specified' }) + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) - db.table('albums').where('name', name).where('enabled', 1).then((album) => { - if(album.length !== 0) return res.json({ success: false, description: 'There\'s already an album with that name' }) + let name = req.body.name + if(name === undefined || name === '') + return res.json({ success: false, description: 'No album name specified' }) - db.table('albums').insert({ - name: name, + db.table('albums').where({ + name: name, enabled: 1, - timestamp: Math.floor(Date.now() / 1000) - }).then(() => { - return res.json({ success: true }) - }) + userid: user[0].id + }).then((album) => { + if(album.length !== 0) return res.json({ success: false, description: 'There\'s already an album with that name' }) + + db.table('albums').insert({ + name: name, + enabled: 1, + userid: user[0].id, + timestamp: Math.floor(Date.now() / 1000) + }).then(() => { + return res.json({ success: true }) + }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + + } albumsController.delete = function(req, res, next){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - let id = req.body.id - if(id === undefined || id === '') - return res.json({ success: false, description: 'No album specified' }) + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) - db.table('albums').where('id', id).update({ enabled: 0 }).then(() => { - return res.json({ success: true }) + let id = req.body.id + if(id === undefined || id === '') + return res.json({ success: false, description: 'No album specified' }) + + db.table('albums').where({id: id, userid: user[0].id}).update({ enabled: 0 }).then(() => { + return res.json({ success: true }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) } albumsController.rename = function(req, res, next){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - let id = req.body.id - if(id === undefined || id === '') - return res.json({ success: false, description: 'No album specified' }) + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) - let name = req.body.name - if(name === undefined || name === '') - return res.json({ success: false, description: 'No name specified' }) + let id = req.body.id + if(id === undefined || id === '') + return res.json({ success: false, description: 'No album specified' }) - db.table('albums').where('name', name).then((results) => { - if(results.length !== 0) - return res.json({ success: false, description: 'Name already in use' }) + let name = req.body.name + if(name === undefined || name === '') + return res.json({ success: false, description: 'No name specified' }) - db.table('albums').where('id', id).update({ name: name }).then(() => { - return res.json({ success: true }) + db.table('albums').where({name: name, userid: user[0].id}).then((results) => { + if(results.length !== 0) return res.json({ success: false, description: 'Name already in use' }) + + db.table('albums').where({id: id, userid: user[0].id}).update({ name: name }).then(() => { + return res.json({ success: true }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) diff --git a/controllers/authController.js b/controllers/authController.js new file mode 100644 index 0000000..2773abe --- /dev/null +++ b/controllers/authController.js @@ -0,0 +1,89 @@ +const config = require('../config.js') +const db = require('knex')(config.database) +const bcrypt = require('bcrypt') +const saltRounds = 10 +const randomstring = require('randomstring') + +let authController = {} + +authController.verify = function(req, res, next){ + + let username = req.body.username + let password = req.body.password + + if(username === undefined) return res.json({ success: false, description: 'No username provided' }) + if(password === undefined) return res.json({ success: false, description: 'No password provided' }) + + db.table('users').where('username', username).then((user) => { + if(user.length === 0) return res.json({ success: false, description: 'Username doesn\'t exist' }) + + bcrypt.compare(password, user[0].password, function(err, result) { + if(result === false) return res.json({ success: false, description: 'Wrong password' }) + return res.json({ success: true, token: user[0].token }) + }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + +} + +authController.register = function(req, res, next){ + + if(config.enableUserAccounts === false) + return res.json({ success: false, description: 'Register is disabled at the moment' }) + + let username = req.body.username + let password = req.body.password + + if(username === undefined) return res.json({ success: false, description: 'No username provided' }) + if(password === undefined) return res.json({ success: false, description: 'No password provided' }) + + if(username.length < 4 || username.length > 32) + return res.json({ success: false, description: 'Username must have 6-32 characters' }) + if(password.length < 6 || password.length > 64) + return res.json({ success: false, description: 'Password must have 6-64 characters' }) + + db.table('users').where('username', username).then((user) => { + if(user.length !== 0) return res.json({ success: false, description: 'Username already exists' }) + + bcrypt.hash(password, saltRounds, function(err, hash) { + if(err) return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }) + + let token = randomstring.generate(64) + + db.table('users').insert({ + username: username, + password: hash, + token: token + }).then(() => { + return res.json({ success: true, token: token}) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + }) + + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + +} + +authController.changePassword = function(req, res, next){ + + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) + + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) + + let password = req.body.password + if(password === undefined) return res.json({ success: false, description: 'No password provided' }) + if(password.length < 6 || password.length > 64) + return res.json({ success: false, description: 'Password must have 6-64 characters' }) + + bcrypt.hash(password, saltRounds, function(err, hash) { + if(err) return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' }) + + db.table('users').where('id', user[0].id).update({password: hash}).then(() => { + return res.json({ success: true}) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + +} + +module.exports = authController
\ No newline at end of file diff --git a/controllers/tokenController.js b/controllers/tokenController.js index ad2b469..31b0b3d 100644 --- a/controllers/tokenController.js +++ b/controllers/tokenController.js @@ -1,60 +1,47 @@ const config = require('../config.js') const db = require('knex')(config.database) +const randomstring = require('randomstring') let tokenController = {} tokenController.verify = function(req, res, next){ - let type = req.body.type - let token = req.body.token - - if(type === undefined) return res.json({ success: false, description: 'No type provided.' }) - if(token === undefined) return res.json({ success: false, description: 'No token provided.' }) - if(type !== 'client' && type !== 'admin') return res.json({ success: false, description: 'Wrong type provided.' }) - if(type === 'client'){ - if(token !== config.clientToken) return res.json({ success: false, description: 'Token mismatch.' }) - return res.json({ success: true }) - } + if(req.body.token === undefined) return res.json({ success: false, description: 'No token provided' }) + let token = req.body.token - if(type === 'admin'){ - if(token !== config.adminToken) return res.json({ success: false, description: 'Token mismatch.' }) + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.json({ success: false, description: 'Token mismatch' }) return res.json({ success: true }) - } - - return res.json({ success: false, description: '(╯°□°)╯︵ ┻━┻' }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + } tokenController.list = function(req, res, next){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) - return res.json({ - clientToken: config.clientToken, - adminToken: config.adminToken - }) -} + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) -tokenController.change = function(req, res, next){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.json({ success: false, description: 'Token mismatch' }) + return res.json({ success: true, token: token }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) - let type = req.body.type - let token = req.body.token +} - if(type === undefined) return res.json({ success: false, description: 'No type provided.' }) - if(token === undefined) return res.json({ success: false, description: 'No token provided.' }) - if(type !== 'client' && type !== 'admin') return res.json({ success: false, description: 'Wrong type provided.' }) +tokenController.change = function(req, res, next){ - db.table('tokens').where('name', type).update({ value: token, timestamp: Math.floor(Date.now() / 1000) }) - .then(() => { + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - if(type === 'client') - config.clientToken = token - else if(type === 'admin') - config.adminToken = token - - res.json({ success: true }) + let newtoken = randomstring.generate(64) + + db.table('users').where('token', token).update({ + token: newtoken, + timestamp: Math.floor(Date.now() / 1000) + }).then(() => { + res.json({ success: true, token: newtoken }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + } module.exports = tokenController
\ No newline at end of file diff --git a/controllers/uploadController.js b/controllers/uploadController.js index 8e60cca..f9abf50 100644 --- a/controllers/uploadController.js +++ b/controllers/uploadController.js @@ -25,81 +25,92 @@ const upload = multer({ uploadsController.upload = function(req, res, next){ + // Get the token + let token = req.headers.token + + // If we're running in private and there's no token, error if(config.private === true) - if(req.headers.auth !== config.clientToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - let album = req.params.albumid - - if(album !== undefined) - if(req.headers.adminauth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + // If there is no token then just leave it blank so the query fails + if(token === undefined) token = '' - upload(req, res, function (err) { - if (err) { - console.error(err) - return res.json({ - success: false, - description: err - }) + db.table('users').where('token', token).then((user) => { + let userid + if(user.length > 0) + userid = user[0].id + + // Check if user is trying to upload to an album + let album = undefined + if(userid !== undefined){ + album = req.headers.albumid + if(album === undefined) + album = req.params.albumid } - if(req.files.length === 0) return res.json({ success: false, description: 'no-files' }) - - let files = [] - let existingFiles = [] - let iteration = 1 + upload(req, res, function (err) { + if (err) { + console.error(err) + return res.json({ + success: false, + description: err + }) + } - req.files.forEach(function(file) { + if(req.files.length === 0) return res.json({ success: false, description: 'no-files' }) - // Check if the file exists by checking hash and size - let hash = crypto.createHash('md5') - let stream = fs.createReadStream('./' + config.uploads.folder + '/' + file.filename) + let files = [] + let existingFiles = [] + let iteration = 1 - stream.on('data', function (data) { - hash.update(data, 'utf8') - }) + req.files.forEach(function(file) { - stream.on('end', function () { - let fileHash = hash.digest('hex') // 34f7a3113803f8ed3b8fd7ce5656ebec - - db.table('files').where({ - hash: fileHash, - size: file.size - }).then((dbfile) => { - - if(dbfile.length !== 0){ - uploadsController.deleteFile(file.filename).then(() => {}).catch((e) => console.error(e)) - existingFiles.push(dbfile[0]) - }else{ - files.push({ - name: file.filename, - original: file.originalname, - type: file.mimetype, - size: file.size, - hash: fileHash, - ip: req.ip, - albumid: album, - timestamp: Math.floor(Date.now() / 1000) - }) - } + // Check if the file exists by checking hash and size + let hash = crypto.createHash('md5') + let stream = fs.createReadStream('./' + config.uploads.folder + '/' + file.filename) - if(iteration === req.files.length) - return uploadsController.processFilesForDisplay(req, res, files, existingFiles) - iteration++ + stream.on('data', function (data) { + hash.update(data, 'utf8') }) - }) - - }) + stream.on('end', function () { + let fileHash = hash.digest('hex') // 34f7a3113803f8ed3b8fd7ce5656ebec + + db.table('files').where({ + hash: fileHash, + size: file.size + }).then((dbfile) => { + + if(dbfile.length !== 0){ + uploadsController.deleteFile(file.filename).then(() => {}).catch((e) => console.error(e)) + existingFiles.push(dbfile[0]) + }else{ + files.push({ + name: file.filename, + original: file.originalname, + type: file.mimetype, + size: file.size, + hash: fileHash, + ip: req.ip, + albumid: album, + userid: userid, + timestamp: Math.floor(Date.now() / 1000) + }) + } - }) + if(iteration === req.files.length) + return uploadsController.processFilesForDisplay(req, res, files, existingFiles) + iteration++ + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + }) + }) + }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) } uploadsController.processFilesForDisplay = function(req, res, files, existingFiles){ - let basedomain = req.get('host') for(let domain of config.domains) if(domain.host === req.get('host')) @@ -139,28 +150,38 @@ uploadsController.processFilesForDisplay = function(req, res, files, existingFil uploadsController.delete = function(req, res){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) let id = req.body.id if(id === undefined || id === '') return res.json({ success: false, description: 'No file specified' }) - db.table('files').where('id', id).then((file) => { + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) - uploadsController.deleteFile(file[0].name).then(() => { - db.table('files').where('id', id).del().then(() =>{ - return res.json({ success: true }) - }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) - }).catch((e) => { - console.log(e.toString()) - db.table('files').where('id', id).del().then(() =>{ - return res.json({ success: true }) - }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + db.table('files') + .where('id', id) + .where(function(){ + if(user[0].username !== 'root') + this.where('userid', user[0].id) }) + .then((file) => { + + uploadsController.deleteFile(file[0].name).then(() => { + db.table('files').where('id', id).del().then(() =>{ + return res.json({ success: true }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + }).catch((e) => { + console.log(e.toString()) + db.table('files').where('id', id).del().then(() =>{ + return res.json({ success: true }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) - + } uploadsController.deleteFile = function(file){ @@ -179,86 +200,92 @@ uploadsController.deleteFile = function(file){ uploadsController.list = function(req, res){ - if(req.headers.auth !== config.adminToken) - return res.status(401).json({ success: false, description: 'not-authorized'}) + let token = req.headers.token + if(token === undefined) return res.status(401).json({ success: false, description: 'No token provided' }) - let offset = req.params.page - if(offset === undefined) offset = 0 + db.table('users').where('token', token).then((user) => { + if(user.length === 0) return res.status(401).json({ success: false, description: 'Invalid token'}) - db.table('files') - .where(function(){ - if(req.params.id === undefined) - this.where('id', '<>', '') - else - this.where('albumid', req.params.id) - }) - .orderBy('id', 'DESC') - .limit(25) - .offset(25 * offset) - .then((files) => { - db.table('albums').then((albums) => { - - let basedomain = req.get('host') - for(let domain of config.domains) - if(domain.host === req.get('host')) - if(domain.hasOwnProperty('resolve')) - basedomain = domain.resolve - - for(let file of files){ - file.file = basedomain + '/' + file.name - file.date = new Date(file.timestamp * 1000) - file.date = file.date.getFullYear() + '-' + (file.date.getMonth() + 1) + '-' + file.date.getDate() + ' ' + (file.date.getHours() < 10 ? '0' : '') + file.date.getHours() + ':' + (file.date.getMinutes() < 10 ? '0' : '') + file.date.getMinutes() + ':' + (file.date.getSeconds() < 10 ? '0' : '') + file.date.getSeconds() - - file.album = '' - - if(file.albumid !== undefined) - for(let album of albums) - if(file.albumid === album.id) - file.album = album.name - - if(config.uploads.generateThumbnails === true){ - - let extensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png'] - for(let ext of extensions){ - if(path.extname(file.name) === ext){ - - file.thumb = basedomain + '/thumbs/' + file.name.slice(0, -4) + '.png' - - let thumbname = path.join(__dirname, '..', 'uploads', 'thumbs') + '/' + file.name.slice(0, -4) + '.png' - fs.access(thumbname, function(err) { - if (err && err.code === 'ENOENT') { - // File doesnt exist - - let size = { - width: 200, - height: 200 - } + let offset = req.params.page + if(offset === undefined) offset = 0 - gm('./' + config.uploads.folder + '/' + file.name) - .resize(size.width, size.height + '>') - .gravity('Center') - .extent(size.width, size.height) - .background('transparent') - .write(thumbname, function (error) { - if (error) console.log('Error - ', error) - }) - } - }) + db.table('files') + .where(function(){ + if(req.params.id === undefined) + this.where('id', '<>', '') + else + this.where('albumid', req.params.id) + }) + .where(function(){ + if(user[0].username !== 'root') + this.where('userid', user[0].id) + }) + .orderBy('id', 'DESC') + .limit(25) + .offset(25 * offset) + .then((files) => { + db.table('albums').then((albums) => { + + let basedomain = req.get('host') + for(let domain of config.domains) + if(domain.host === req.get('host')) + if(domain.hasOwnProperty('resolve')) + basedomain = domain.resolve + + for(let file of files){ + file.file = basedomain + '/' + file.name + file.date = new Date(file.timestamp * 1000) + file.date = file.date.getFullYear() + '-' + (file.date.getMonth() + 1) + '-' + file.date.getDate() + ' ' + (file.date.getHours() < 10 ? '0' : '') + file.date.getHours() + ':' + (file.date.getMinutes() < 10 ? '0' : '') + file.date.getMinutes() + ':' + (file.date.getSeconds() < 10 ? '0' : '') + file.date.getSeconds() + + file.album = '' + + if(file.albumid !== undefined) + for(let album of albums) + if(file.albumid === album.id) + file.album = album.name + + if(config.uploads.generateThumbnails === true){ + + let extensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png'] + for(let ext of extensions){ + if(path.extname(file.name) === ext){ + + file.thumb = basedomain + '/thumbs/' + file.name.slice(0, -4) + '.png' + + let thumbname = path.join(__dirname, '..', 'uploads', 'thumbs') + '/' + file.name.slice(0, -4) + '.png' + fs.access(thumbname, function(err) { + if (err && err.code === 'ENOENT') { + // File doesnt exist + + let size = { + width: 200, + height: 200 + } + + gm('./' + config.uploads.folder + '/' + file.name) + .resize(size.width, size.height + '>') + .gravity('Center') + .extent(size.width, size.height) + .background('transparent') + .write(thumbname, function (error) { + if (error) console.log('Error - ', error) + }) + } + }) + } } } - } - - } - - return res.json({ - success: true, - files - }) + return res.json({ + success: true, + files + }) + }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) - }).catch(function(error) { console.log(error); res.json({success: false, description: 'error'}) }) + + }) } module.exports = uploadsController
\ No newline at end of file |