Skip to content

Commit 5967fef

Browse files
authored
Merge pull request #1642 from hackmdio/release/2.3.0
Release 2.3.0
2 parents 3e75445 + 30e8353 commit 5967fef

21 files changed

+981
-569
lines changed

.sequelizerc.example

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ const path = require('path')
22
const config = require('./lib/config')
33

44
module.exports = {
5-
config: path.resolve('config.json'),
5+
config: path.resolve('config.js'),
66
'migrations-path': path.resolve('lib', 'migrations'),
77
'models-path': path.resolve('lib', 'models'),
8-
url: process.env['CMD_DB_URL'] || config.dbURL
8+
url: config.dbURL
99
}

config.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const config = require('./lib/config')
2+
3+
module.exports = config.db

lib/config/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ switch (config.imageUploadType) {
189189
'image/png',
190190
'image/jpg',
191191
'image/gif',
192-
'image/svg+xml'
192+
'image/svg+xml',
193+
'image/bmp',
194+
'image/tiff'
193195
]
194196
}
195197

lib/imageRouter/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
'use strict'
22

33
const fs = require('fs')
4+
const path = require('path')
45
const Router = require('express').Router
56
const formidable = require('formidable')
67

8+
const readChunk = require('read-chunk')
9+
const imageType = require('image-type')
10+
const mime = require('mime-types')
11+
712
const config = require('../config')
813
const logger = require('../logger')
914
const response = require('../response')
1015

1116
const imageRouter = module.exports = Router()
1217

18+
function checkImageValid (filepath) {
19+
const buffer = readChunk.sync(filepath, 0, 12)
20+
/** @type {{ ext: string, mime: string } | null} */
21+
const mimetypeFromBuf = imageType(buffer)
22+
const mimeTypeFromExt = mime.lookup(path.extname(filepath))
23+
24+
return mimetypeFromBuf && config.allowedUploadMimeTypes.includes(mimetypeFromBuf.mime) &&
25+
mimeTypeFromExt && config.allowedUploadMimeTypes.includes(mimeTypeFromExt)
26+
}
27+
1328
// upload image
1429
imageRouter.post('/uploadimage', function (req, res) {
1530
var form = new formidable.IncomingForm()
@@ -24,6 +39,10 @@ imageRouter.post('/uploadimage', function (req, res) {
2439
logger.info('SERVER received uploadimage: ' + JSON.stringify(files.image))
2540
}
2641

42+
if (!checkImageValid(files.image.path)) {
43+
return response.errorForbidden(req, res)
44+
}
45+
2746
const uploadProvider = require('./' + config.imageUploadType)
2847
uploadProvider.uploadImage(files.image.path, function (err, url) {
2948
// remove temporary upload file, and ignore any error

lib/note/index.js

+102-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
const config = require('../config')
44
const logger = require('../logger')
5-
const { Note, User } = require('../models')
5+
const { Note, User, Revision } = require('../models')
66

77
const { newCheckViewPermission, errorForbidden, responseCodiMD, errorNotFound, errorInternalError } = require('../response')
8-
const { updateHistory } = require('../history')
8+
const { updateHistory, historyDelete } = require('../history')
99
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision, actionPandoc } = require('./noteActions')
10+
const realtime = require('../realtime/realtime')
1011

1112
async function getNoteById (noteId, { includeUser } = { includeUser: false }) {
1213
const id = await Note.parseNoteIdAsync(noteId)
@@ -232,7 +233,106 @@ function listMyNotes (req, res) {
232233
}
233234
}
234235

236+
const deleteNote = async (req, res) => {
237+
if (req.isAuthenticated()) {
238+
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
239+
try {
240+
const destroyed = await Note.destroy({
241+
where: {
242+
id: noteId,
243+
ownerId: req.user.id
244+
}
245+
})
246+
if (!destroyed) {
247+
logger.error('Delete note failed: Make sure the noteId and ownerId are correct.')
248+
return errorNotFound(req, res)
249+
}
250+
251+
historyDelete(req, res)
252+
253+
if (realtime.isNoteExistsInPool(noteId)) {
254+
const note = realtime.getNoteFromNotePool(noteId)
255+
realtime.disconnectSocketOnNote(note)
256+
}
257+
258+
res.send({
259+
status: 'ok'
260+
})
261+
} catch (err) {
262+
logger.error('Delete note failed: Internal Error.')
263+
return errorInternalError(req, res)
264+
}
265+
} else {
266+
return errorForbidden(req, res)
267+
}
268+
}
269+
270+
const updateNote = async (req, res) => {
271+
if (req.isAuthenticated()) {
272+
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
273+
try {
274+
const note = await Note.findOne({
275+
where: {
276+
id: noteId
277+
}
278+
})
279+
if (!note) {
280+
logger.error('Update note failed: Can\'t find the note.')
281+
return errorNotFound(req, res)
282+
}
283+
284+
if (realtime.isNoteExistsInPool(noteId)) {
285+
logger.error('Update note failed: There are online users opening this note.')
286+
return res.status('403').json({ status: 'error', message: 'Update API can only be used when no users is online' })
287+
}
288+
289+
const now = Date.now()
290+
const content = req.body.content
291+
const updated = await note.update({
292+
title: Note.parseNoteTitle(content),
293+
content: content,
294+
lastchangeAt: now,
295+
authorship: [
296+
[
297+
req.user.id,
298+
0,
299+
content.length,
300+
now,
301+
now
302+
]
303+
]
304+
})
305+
306+
if (!updated) {
307+
logger.error('Update note failed: Write note content error.')
308+
return errorInternalError(req, res)
309+
}
310+
311+
updateHistory(req.user.id, note.id, content)
312+
313+
Revision.saveNoteRevision(note, (err, revision) => {
314+
if (err) {
315+
logger.error(err)
316+
return errorInternalError(req, res)
317+
}
318+
if (!revision) return errorNotFound(req, res)
319+
res.send({
320+
status: 'ok'
321+
})
322+
})
323+
} catch (err) {
324+
logger.error(err)
325+
logger.error('Update note failed: Internal Error.')
326+
return errorInternalError(req, res)
327+
}
328+
} else {
329+
return errorForbidden(req, res)
330+
}
331+
}
332+
235333
exports.showNote = showNote
236334
exports.showPublishNote = showPublishNote
237335
exports.noteActions = noteActions
238336
exports.listMyNotes = listMyNotes
337+
exports.deleteNote = deleteNote
338+
exports.updateNote = updateNote

lib/routes.js

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const historyController = require('./history')
1212
const userController = require('./user')
1313
const noteController = require('./note')
1414
const response = require('./response')
15+
const bodyParser = require('body-parser')
1516
const appRouter = Router()
1617

1718
// register route
@@ -72,6 +73,10 @@ appRouter.get('/p/:shortid', response.showPublishSlide)
7273
appRouter.get('/p/:shortid/:action', response.publishSlideActions)
7374
// gey my note list
7475
appRouter.get('/api/notes/myNotes', noteController.listMyNotes)
76+
// delete note by id
77+
appRouter.delete('/api/notes/:noteId', noteController.deleteNote)
78+
// update note content by id
79+
appRouter.put('/api/notes/:noteId', bodyParser.json(), noteController.updateNote)
7580
// get note by id
7681
appRouter.get('/:noteId', wrap(noteController.showNote))
7782
// note actions

lib/utils.js

+2-17
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,14 @@
22
const fs = require('fs')
33
const path = require('path')
44
const bodyParser = require('body-parser')
5+
const mime = require('mime-types')
56

67
exports.isSQLite = function isSQLite (sequelize) {
78
return sequelize.options.dialect === 'sqlite'
89
}
910

1011
exports.getImageMimeType = function getImageMimeType (imagePath) {
11-
const fileExtension = /[^.]+$/.exec(imagePath)
12-
13-
switch (fileExtension[0]) {
14-
case 'bmp':
15-
return 'image/bmp'
16-
case 'gif':
17-
return 'image/gif'
18-
case 'jpg':
19-
case 'jpeg':
20-
return 'image/jpeg'
21-
case 'png':
22-
return 'image/png'
23-
case 'tiff':
24-
return 'image/tiff'
25-
default:
26-
return undefined
27-
}
12+
return mime.lookup(path.extname(imagePath))
2813
}
2914

3015
exports.isRevealTheme = function isRevealTheme (theme) {

0 commit comments

Comments
 (0)