diff --git a/app.js b/app.js index 731a87c..8b709ef 100644 --- a/app.js +++ b/app.js @@ -14,7 +14,6 @@ const compression = require('compression') const { UserModel } = require('./model/users') const { BlogModel } = require('./model/blog') -const { ImageModel } = require('./model/images') const { callbacks } = require('./utils/callbacks') const app = express() @@ -66,11 +65,9 @@ app.get(`/${config.admin_route}`, (req, res) => { const users = await UserModel.find().sort({ isAdmin: -1 }).exec() const blog = await BlogModel.find() - const images = await ImageModel.find().sort({ date: -1 }) return res.render('admin', { users: (users) || [], blog: (blog) || [], - images: (images) || [], version: config.VERSION }) })(req, res) diff --git a/model/images.js b/model/images.js deleted file mode 100644 index 90a40a8..0000000 --- a/model/images.js +++ /dev/null @@ -1,24 +0,0 @@ -const mongoose = require('mongoose') - -const ImageSchema = new mongoose.Schema({ - filename: { - type: String, - required: true - }, - description: { - type: String, - required: true - }, - path: { - type: String, - required: true - }, - date: { - type: Date, - required: true, - default: Date.now - } -}) - -const ImageModel = mongoose.model('image', ImageSchema) -module.exports = { ImageSchema, ImageModel } diff --git a/public/css/admin.css b/public/css/admin.css index ff2c3f0..03646b9 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -70,18 +70,4 @@ section { float: right; } -#delete-button-images{ - /* self style */ - background-color: red; - color: var(--light-gray); - border: none; - border-radius: 3px; - font-weight: 600; - - /* self align */ - height: 25px; - width: 75px; - float: right; -} - /* ----------CSS for Delete Buttons ends here----------------- */ diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 35e1467..8915df6 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -1,4 +1,4 @@ -/* global blogTableSelections, usersTableSelections, imagesTableSelections, alert, XMLHttpRequest */ +/* global blogTableSelections, usersTableSelections, alert, XMLHttpRequest */ // uncheck all checkboxes on load and remove all data from selectionsArray(s) and hide Delete-Buttons document.addEventListener('DOMContentLoaded', () => { @@ -90,7 +90,6 @@ const deleteButtonVisibility = (tableId, buttonId) => { const disableAllDeleteButtons = () => { deleteButtonVisibility('blog-table', 'delete-button-blog') deleteButtonVisibility('blog-table', 'delete-button-users') - deleteButtonVisibility('images-table', 'delete-button-images') } const deleteHandler = async (route) => { // eslint-disable-line @@ -105,10 +104,6 @@ const deleteHandler = async (route) => { // eslint-disable-line selections = blogTableSelections itemDesc = 'article' break - case 'image': - selections = imagesTableSelections - itemDesc = 'image' - break default: console.log('deleteHandler: invalid selection') return diff --git a/public/js/editDb.js b/public/js/editDb.js index 887c75a..b44b0ea 100644 --- a/public/js/editDb.js +++ b/public/js/editDb.js @@ -1,4 +1,4 @@ -/* global alert, XMLHttpRequest, FormData, IS_NEW, OBJ_ID */ +/* global alert, XMLHttpRequest, IS_NEW, OBJ_ID */ const editUser = (event) => { // eslint-disable-line event.preventDefault() @@ -137,27 +137,3 @@ const deleteBlog = () => { // eslint-disable-line XHR.send() } - -const editImage = (event) => { // eslint-disable-line - event.preventDefault() - const XHR = new XMLHttpRequest() - - // Success - XHR.onreadystatechange = () => { - if (XHR.readyState === 4) { - if (XHR.status !== 200) { - alert(JSON.parse(XHR.responseText).error) - return - } - - alert('Image saved succesfully') - window.location.reload() - } - } - - XHR.open('POST', '/api/image') - XHR.withCredentials = true - - const fd = new FormData(document.getElementById('edit-images-form')) - XHR.send(fd) -} diff --git a/routes/api.js b/routes/api.js index 8c7c8ab..4e2579f 100644 --- a/routes/api.js +++ b/routes/api.js @@ -6,12 +6,10 @@ const usersRoute = require('./users') const blogRoute = require('./blog') const contactRoute = require('./contact') const editRoute = require('./edit') -const imagesRoute = require('./images') router.use('/users', passport.authenticate('loggedIn', { session: false }), usersRoute) router.use('/edit', passport.authenticate('loggedIn', { session: false }), editRoute) router.use('/blog', blogRoute) router.use('/contact', contactRoute) -router.use('/image', imagesRoute) module.exports = router diff --git a/routes/edit.js b/routes/edit.js index d9d3b7f..b51d0e1 100644 --- a/routes/edit.js +++ b/routes/edit.js @@ -42,9 +42,4 @@ router.get('/user/:id', async (req, res, next) => { return res.render('edit-user', { data: userExists, isNew: false }) }) -// Create new image -router.get('/image', (req, res, next) => { - return res.render('edit-image', { data: null, isNew: true }) -}) - module.exports = router diff --git a/routes/images.js b/routes/images.js deleted file mode 100644 index a9b4431..0000000 --- a/routes/images.js +++ /dev/null @@ -1,115 +0,0 @@ -const fs = require('fs') -const { resolve } = require('path') -const express = require('express') -const multer = require('multer') -const mongoose = require('mongoose') -const passport = require('passport') -const { ImageModel } = require('../model/images') - -const router = express.Router() - -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - // Create directory if it doesnt exist. - if (!fs.existsSync('uploads/')) { - fs.mkdirSync('uploads/', { recursive: true }) - } - cb(null, 'uploads/') - }, - filename: (req, file, cb) => { - // If its not a .jpeg, then it will be caught later. - const suffix = (file.mimetype === 'image/png') ? '.png' : '.jpeg' - const prefix = Date.now() + '-' + Math.round(Math.random() * 1E9) - cb(null, prefix + suffix) - } -}) - -const fileFilter = (req, file, cb) => { - if (file.fieldname !== 'File' || file.encoding !== '7bit') { - req.uploadFileError = 'Bad File Encoding' - cb(null, false) - return - } - - if (file.mimetype !== 'image/png' && file.mimetype !== 'image/jpeg') { - req.uploadFileError = 'bad file type: use .png or .jpeg instead' - cb(null, false) - return - } - - cb(null, true) -} - -// Max size: 25MB -const upload = multer({ storage, fileFilter, limits: { fileSize: 26214400 } }) - -// Return all images. -router.get('/', passport.authenticate('loggedIn', { session: false }), async (req, res) => { - const images = await ImageModel.find().sort({ date: -1 }) - if (!images) return res.status(404).send({ error: 'No posts found' }) - res.status(200).send(images) -}) - -// Return image by its id. -router.get('/:id', async (req, res) => { - if (!mongoose.Types.ObjectId.isValid(req.params.id)) { - return res.status(400).send({ error: 'Invalid ID' }) - } - - const imageExists = await ImageModel.findById(req.params.id) - if (!imageExists) { - return res.status(404).send({ error: 'ID does not exist' }) - } - - // May exist in DB but not exist in filesystem. - if (!fs.existsSync(imageExists.path)) { - await ImageModel.findByIdAndDelete(req.params.id) - return res.status(404).send({ error: 'image does not exist' }) - } - - res.status(200).sendFile(resolve(imageExists.path)) -}) - -// Create a new image. -router.post('/', passport.authenticate('loggedIn', { session: false }), upload.single('File'), (req, res) => { - if (typeof req.uploadFileError === 'string') { - return res.status(500).send({ error: req.uploadFileError }) - } - - if (!req.file || !req.body || !req.body.Description) { - return res.status(500).send({ error: 'expected image and description' }) - } - - const image = new ImageModel({ - filename: req.file.filename, - description: req.body.Description, - path: req.file.path - }) - - image.save((err) => { - if (err) return res.status(500).send({ error: 'Internal Server Error' }) - }) - - return res.status(200).send(image) -}) - -// Delete an existing image by its ID. -router.delete('/:id', passport.authenticate('loggedIn', { session: false }), async (req, res) => { - if (!mongoose.Types.ObjectId.isValid(req.params.id)) { - return res.status(400).send({ error: 'Invalid ID' }) - } - - const imageExists = await ImageModel.findByIdAndDelete(req.params.id) - if (!imageExists) { - return res.status(404).send({ error: 'ID does not exist' }) - } - - // Delete file (from disk) if it exists. - if (fs.existsSync(imageExists.path)) { - fs.unlinkSync(resolve(imageExists.path)) - } - - return res.status(200).send(imageExists) -}) - -module.exports = router diff --git a/test/images.spec.js b/test/images.spec.js deleted file mode 100644 index 98de1e8..0000000 --- a/test/images.spec.js +++ /dev/null @@ -1,198 +0,0 @@ -/* global describe, it, before */ - -const fs = require('fs') -const { resolve } = require('path') -const chai = require('chai') -const { app } = require('../app') -const chaiHttp = require('chai-http') -const TestHelper = require('./test-helper') -const expect = chai.expect -const UserModel = require('../model/users').UserModel -const ImageModel = require('../model/images').ImageModel -const auth = require('../auth/auth') -const ENV = require('../utils/config').ENV - -chai.use(chaiHttp) -let authenticatedAgent = null -let agent = null - -describe('Images Route', () => { - before(async () => { - expect(ENV === 'development').to.equal(true) - const user = await UserModel.findOne() - authenticatedAgent = new TestHelper(chai, app, auth.tokenizeUser(user)) - agent = new TestHelper(chai, app) - }) - - // IMAGES-GET route. - describe('Images: GET ROUTE', () => { - it('Should require authentication', async () => { - const unauthed = await agent.get('/api/image/') - const authed = await authenticatedAgent.get('/api/image/') - expect(unauthed.statusCode).to.equal(401) - expect(authed.statusCode).to.equal(200) - }) - - it('Should return all images in database', async () => { - const images = await authenticatedAgent.get('/api/image/') - const dbImages = await ImageModel.find().sort({ date: -1 }).exec() - expect(images.text).to.equal(JSON.stringify(dbImages)) - }) - }) - - // IMAGES-GET-ID route. - describe('Images: GET ID ROUTE', () => { - it('Should not require authentication', async () => { - const id = await ImageModel.findOne() - const resp = await agent.get(`/api/image/${id.id}`) - expect(resp.status).to.equal(200) - }) - - it('Should return an error when ID is malformed', async () => { - const resp = await agent.get('/api/image/:641a2cbbe8f88f$3bd1a25f8') - const resp1 = await agent.get('/api/image/123') - expect(resp.status).to.equal(400) - expect(resp1.status).to.equal(400) - }) - - it('Should return an error when ID is valid but doesnt exist', async () => { - const resp = await agent.get('/api/image/64b3c0cf45b4dc3d407b7416') - const resp1 = await agent.get('/api/image/64b3c0cf45b4dc3d407b7417') - expect(resp.status).to.equal(404) - expect(resp1.status).to.equal(404) - }) - - it('Should return an error when image doesnt exist on filesystem', async () => { - const image = await ImageModel.findOne({ path: 'test/assets/doesnt-exist.jpeg' }) - expect(image).to.not.be.null // eslint-disable-line - const resp = await agent.get(`/api/image/${image.id}`) - expect(resp.status).to.equal(404) - }) - - it('Should return image file when image is registered and exists', async () => { - const image = await ImageModel.findOne({ path: 'test/assets/1x1.png' }) - const resp = await agent.get(`/api/image/${image.id}`) - - // Returns image file. - expect(resp.status).to.equal(200) - expect(resp.type).to.equal('image/png') - - // Returns uncompressed image file. - const data = fs.readFileSync(resolve('test/assets/1x1.png')) - expect(data.equals(resp.body)).to.equal(true) - }) - }) - - // IMAGES-POST route. - describe('Images: POST ROUTE', () => { - it('Should return an error if user is not authenticated', async () => { - const resp = await agent.post('/api/image/') - expect(resp.status).to.equal(401) - }) - - it('Should return an error when encoding is not multiform', async () => { - const user = await UserModel.findOne() - const resp = await chai.request(app) - .post('/api/image/') - .set('Cookie', `jwt=${auth.tokenizeUser(user)}`) - .set('Content-Type', 'application/json') - .send({ description: 'desc', file: 'no file' }) - - expect(resp.status).to.equal(500) - }) - - it('Should return an error if description is missing', async () => { - const user = await UserModel.findOne() - const data = fs.readFileSync(resolve('test/assets/1x1.png')) - const resp = await chai.request(app).post('/api/image/') - .set('Cookie', `jwt=${auth.tokenizeUser(user)}`) - .attach('File', data, '1x1.png') - - expect(resp.status).to.equal(500) - }) - - it('Should return an error if image is missing', async () => { - const user = await UserModel.findOne() - const resp = await chai.request(app).post('/api/image/') - .set('Cookie', `jwt=${auth.tokenizeUser(user)}`) - .field('Description', 'my failed image upload') - - expect(resp.status).to.equal(500) - }) - - it('Should return an error if image is not jpeg or png', async () => { - const user = await UserModel.findOne() - const data = fs.readFileSync(resolve('test/assets/1x1.gif')) - const resp = await chai.request(app).post('/api/image/') - .set('Cookie', `jwt=${auth.tokenizeUser(user)}`) - .field('Description', 'my failed image upload') - .attach('File', data, '1x1.gif') - - expect(resp.status).to.equal(500) - }) - - it('Should return saved image model', async () => { - const user = await UserModel.findOne() - const data = fs.readFileSync(resolve('test/assets/1x1.png')) - const resp = await chai.request(app).post('/api/image/') - .set('Cookie', `jwt=${auth.tokenizeUser(user)}`) - .field('Description', 'my image upload') - .attach('File', data, '1x1.png') - - expect(resp.status).to.equal(200) - const respData = JSON.parse(resp.text) - const dbData = await ImageModel.findById(respData._id) - expect(dbData === null).to.equal(false) - }) - - it('Should save image to filesystem and database', async () => { - const user = await UserModel.findOne() - const data = fs.readFileSync(resolve('test/assets/1x1.png')) - const resp = await chai.request(app).post('/api/image/') - .set('Cookie', `jwt=${auth.tokenizeUser(user)}`) - .field('Description', 'my image upload') - .attach('File', data, '1x1.png') - - expect(resp.status).to.equal(200) - const path = JSON.parse(resp.text).path - expect(fs.existsSync(path)).to.be.true // eslint-disable-line - }) - }) - - // IMAGES-DELETE route. - describe('Images: DELETE ROUTE', () => { - it('Should require authentication', async () => { - const resp = await agent.post('/api/image/') - expect(resp.status).to.equal(401) - }) - - it('Should return an error if ID is malformed', async () => { - const resp = await authenticatedAgent.delete('/api/image/:641a2cbbe8f88f$3bd1a25f8') - const resp1 = await authenticatedAgent.delete('/api/image/123') - expect(resp.status).to.equal(400) - expect(resp1.status).to.equal(400) - }) - - it('Should return an error if ID is valid but does not exist', async () => { - const resp = await authenticatedAgent.delete('/api/image/64b3c0cf45b4dc3d407b7416') - const resp1 = await authenticatedAgent.delete('/api/image/64b3c0cf45b4dc3d407b7417') - expect(resp.status).to.equal(404) - expect(resp1.status).to.equal(404) - }) - - it('Should delete image from database', async () => { - const image = await ImageModel.findOne({ path: { $ne: 'test/assets/1x1.png' } }) - const resp = await authenticatedAgent.delete(`/api/image/${image.id}`) - expect(resp.status).to.equal(200) - const isDeleted = await ImageModel.findById(image.id) - expect(isDeleted).to.be.null // eslint-disable-line - }) - - it('Should delete image from filesystem if it exists', async () => { - const image = await ImageModel.findOne({ path: { $ne: 'test/assets/1x1.png' } }) - const resp = await authenticatedAgent.delete(`/api/image/${image.id}`) - expect(resp.status).to.equal(200) - expect(fs.existsSync(image.path)).to.be.false // eslint-disable-line - }) - }) -}) diff --git a/utils/seed.js b/utils/seed.js index a36e47f..94d19b8 100644 --- a/utils/seed.js +++ b/utils/seed.js @@ -4,7 +4,6 @@ const mongoose = require('mongoose') const { hashPassword } = require('../auth/auth') const { UserModel } = require('../model/users') const { BlogModel } = require('../model/blog') -const { ImageModel } = require('../model/images') const seed = async () => { if (config.ENV !== 'development') { @@ -22,7 +21,6 @@ const seed = async () => { try { await UserModel.collection.drop() await BlogModel.collection.drop() - await ImageModel.collection.drop() console.log('Reset database succesfully') } catch (error) { console.log('ERROR while resetting database: ', error) @@ -64,32 +62,11 @@ const seed = async () => { author: ['admin2'] }) - const imageA = new ImageModel({ - filename: '1689501903131-459608087.png', - description: 'lorem ipsum dolor', - path: 'test/assets/1x1.png' - }) - - const imageB = new ImageModel({ - filename: '1689502000136-727547610.png', - description: 'lorem ipsum dolor', - path: 'test/assets/1x1.png' - }) - - const imageC = new ImageModel({ - filename: '1689502000136-727547610.png', - description: 'lorem ipsum dolor', - path: 'test/assets/doesnt-exist.jpeg' - }) - await userAdmin.save() await userB.save() await blogA.save() await blogA.save() await blogB.save() - await imageA.save() - await imageB.save() - await imageC.save() console.log('Finished seeding') mongoose.connection.close() diff --git a/views/admin.ejs b/views/admin.ejs index 23c1723..fe810e3 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -21,7 +21,6 @@ @@ -54,21 +53,6 @@ <%- include('./components/users-table.ejs', { data: users }) %> -
- - -

Images

-
-
- <%- include('./components/images-table.ejs', { data: images }) %> -
-
<%- include('./components/footer.ejs') %> diff --git a/views/components/images-table.ejs b/views/components/images-table.ejs deleted file mode 100644 index 890dd91..0000000 --- a/views/components/images-table.ejs +++ /dev/null @@ -1,44 +0,0 @@ -
- - - - - - - - - <% data.forEach((entry) => { %> - - - - - - - <% }) %> - - - - - - - - - - -
- - PathDescriptionPreview
- - <%= `/api/image/${entry.id}` %><%= entry.description %> - -
-
diff --git a/views/edit-image.ejs b/views/edit-image.ejs deleted file mode 100644 index a5acfe3..0000000 --- a/views/edit-image.ejs +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - Edit Image - - - - - - - - - <%- include('./components/navbar.ejs') %> -
-

EDIT IMAGE

-
- - - - - - <% if (!locals.isNew) { %> - - <% } %> -
-
- -