2020-01-04 22:58:40 +00:00
|
|
|
'use strict'
|
|
|
|
|
|
|
|
const config = require('../config')
|
2020-01-04 23:38:19 +00:00
|
|
|
const logger = require('../logger')
|
2020-07-27 09:59:51 +00:00
|
|
|
const { Note, User, Revision } = require('../models')
|
2020-01-04 22:58:40 +00:00
|
|
|
|
2020-06-30 10:30:21 +00:00
|
|
|
const { newCheckViewPermission, errorForbidden, responseCodiMD, errorNotFound, errorInternalError } = require('../response')
|
2020-07-20 16:09:44 +00:00
|
|
|
const { updateHistory, historyDelete } = require('../history')
|
2019-08-25 03:02:15 +00:00
|
|
|
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision, actionPandoc } = require('./noteActions')
|
2020-07-22 16:24:58 +00:00
|
|
|
const realtime = require('../realtime/realtime')
|
2020-01-04 22:58:40 +00:00
|
|
|
|
2020-01-04 23:17:55 +00:00
|
|
|
async function getNoteById (noteId, { includeUser } = { includeUser: false }) {
|
2020-01-04 22:58:40 +00:00
|
|
|
const id = await Note.parseNoteIdAsync(noteId)
|
2020-01-04 23:17:55 +00:00
|
|
|
|
|
|
|
const includes = []
|
|
|
|
|
|
|
|
if (includeUser) {
|
|
|
|
includes.push({
|
|
|
|
model: User,
|
|
|
|
as: 'owner'
|
|
|
|
}, {
|
|
|
|
model: User,
|
|
|
|
as: 'lastchangeuser'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-01-04 22:58:40 +00:00
|
|
|
const note = await Note.findOne({
|
|
|
|
where: {
|
|
|
|
id: id
|
2020-01-04 23:17:55 +00:00
|
|
|
},
|
|
|
|
include: includes
|
2020-01-04 22:58:40 +00:00
|
|
|
})
|
|
|
|
return note
|
|
|
|
}
|
|
|
|
|
|
|
|
async function createNote (userId, noteAlias) {
|
2020-03-17 12:52:22 +00:00
|
|
|
if (!config.allowAnonymous && !userId) {
|
2020-01-04 22:58:40 +00:00
|
|
|
throw new Error('can not create note')
|
|
|
|
}
|
|
|
|
|
|
|
|
const note = await Note.create({
|
|
|
|
ownerId: userId,
|
2020-01-04 23:17:55 +00:00
|
|
|
alias: noteAlias
|
2020-01-04 22:58:40 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
if (userId) {
|
|
|
|
updateHistory(userId, note)
|
|
|
|
}
|
|
|
|
|
|
|
|
return note
|
|
|
|
}
|
|
|
|
|
|
|
|
// controller
|
|
|
|
async function showNote (req, res) {
|
|
|
|
const noteId = req.params.noteId
|
|
|
|
const userId = req.user ? req.user.id : null
|
|
|
|
|
|
|
|
let note = await getNoteById(noteId)
|
|
|
|
|
|
|
|
if (!note) {
|
|
|
|
// if allow free url enable, auto create note
|
|
|
|
if (!config.allowFreeURL || config.forbiddenNoteIDs.includes(noteId)) {
|
2020-02-26 03:20:42 +00:00
|
|
|
return errorNotFound(req, res)
|
2020-04-29 20:40:46 +00:00
|
|
|
} else if (!config.allowAnonymous && !userId) {
|
|
|
|
return errorForbidden(req, res)
|
2020-01-04 22:58:40 +00:00
|
|
|
}
|
|
|
|
note = await createNote(userId, noteId)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!newCheckViewPermission(note, req.isAuthenticated(), userId)) {
|
2020-02-26 03:13:45 +00:00
|
|
|
return errorForbidden(req, res)
|
2020-01-04 22:58:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// force to use note id
|
|
|
|
const id = Note.encodeNoteId(note.id)
|
|
|
|
if ((note.alias && noteId !== note.alias) || (!note.alias && noteId !== id)) {
|
|
|
|
return res.redirect(config.serverURL + '/' + (note.alias || id))
|
|
|
|
}
|
|
|
|
return responseCodiMD(res, note)
|
|
|
|
}
|
|
|
|
|
2020-02-07 02:55:50 +00:00
|
|
|
function canViewNote (note, isLogin, userId) {
|
|
|
|
if (note.permission === 'private') {
|
|
|
|
return note.ownerId === userId
|
|
|
|
}
|
|
|
|
if (note.permission === 'limited' || note.permission === 'protected') {
|
|
|
|
return isLogin
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-01-04 23:17:55 +00:00
|
|
|
async function showPublishNote (req, res) {
|
|
|
|
const shortid = req.params.shortid
|
|
|
|
|
|
|
|
const note = await getNoteById(shortid, {
|
|
|
|
includeUser: true
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!note) {
|
2020-02-26 03:20:42 +00:00
|
|
|
return errorNotFound(req, res)
|
2020-01-04 23:17:55 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 02:23:46 +00:00
|
|
|
if (!canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
|
2020-02-26 03:13:45 +00:00
|
|
|
return errorForbidden(req, res)
|
2020-02-26 02:23:46 +00:00
|
|
|
}
|
|
|
|
|
2020-01-04 23:17:55 +00:00
|
|
|
if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
|
|
|
|
return res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
|
|
|
|
}
|
|
|
|
|
|
|
|
await note.increment('viewcount')
|
|
|
|
|
|
|
|
const body = note.content
|
|
|
|
const extracted = Note.extractMeta(body)
|
|
|
|
const markdown = extracted.markdown
|
|
|
|
const meta = Note.parseMeta(extracted.meta)
|
|
|
|
const createTime = note.createdAt
|
|
|
|
const updateTime = note.lastchangeAt
|
|
|
|
const title = Note.generateWebTitle(meta.title || Note.decodeTitle(note.title))
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
title: title,
|
|
|
|
description: meta.description || (markdown ? Note.generateDescription(markdown) : null),
|
2020-06-27 13:26:08 +00:00
|
|
|
image: meta.image,
|
2020-01-04 23:17:55 +00:00
|
|
|
viewcount: note.viewcount,
|
|
|
|
createtime: createTime,
|
|
|
|
updatetime: updateTime,
|
|
|
|
body: body,
|
|
|
|
owner: note.owner ? note.owner.id : null,
|
|
|
|
ownerprofile: note.owner ? User.getProfile(note.owner) : null,
|
|
|
|
lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
|
|
|
|
lastchangeuserprofile: note.lastchangeuser ? User.getProfile(note.lastchangeuser) : null,
|
|
|
|
robots: meta.robots || false, // default allow robots
|
|
|
|
GA: meta.GA,
|
|
|
|
disqus: meta.disqus,
|
|
|
|
cspNonce: res.locals.nonce
|
|
|
|
}
|
|
|
|
|
|
|
|
res.set({
|
|
|
|
'Cache-Control': 'private' // only cache by client
|
|
|
|
})
|
|
|
|
|
|
|
|
res.render('pretty.ejs', data)
|
|
|
|
}
|
|
|
|
|
2020-01-04 23:38:19 +00:00
|
|
|
async function noteActions (req, res) {
|
|
|
|
const noteId = req.params.noteId
|
|
|
|
|
|
|
|
const note = await getNoteById(noteId)
|
2020-02-07 02:55:50 +00:00
|
|
|
|
2020-01-04 23:38:19 +00:00
|
|
|
if (!note) {
|
2020-02-26 03:20:42 +00:00
|
|
|
return errorNotFound(req, res)
|
2020-01-04 23:38:19 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 02:55:50 +00:00
|
|
|
if (!canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
|
2020-02-26 03:13:45 +00:00
|
|
|
return errorForbidden(req, res)
|
2020-02-07 02:55:50 +00:00
|
|
|
}
|
|
|
|
|
2020-01-04 23:38:19 +00:00
|
|
|
const action = req.params.action
|
|
|
|
switch (action) {
|
|
|
|
case 'publish':
|
|
|
|
case 'pretty': // pretty deprecated
|
|
|
|
return actionPublish(req, res, note)
|
|
|
|
case 'slide':
|
|
|
|
return actionSlide(req, res, note)
|
|
|
|
case 'download':
|
|
|
|
actionDownload(req, res, note)
|
|
|
|
break
|
|
|
|
case 'info':
|
|
|
|
actionInfo(req, res, note)
|
|
|
|
break
|
|
|
|
case 'pdf':
|
|
|
|
if (config.allowPDFExport) {
|
|
|
|
actionPDF(req, res, note)
|
|
|
|
} else {
|
|
|
|
logger.error('PDF export failed: Disabled by config. Set "allowPDFExport: true" to enable. Check the documentation for details')
|
2020-02-26 03:13:45 +00:00
|
|
|
errorForbidden(req, res)
|
2020-01-04 23:38:19 +00:00
|
|
|
}
|
|
|
|
break
|
|
|
|
case 'gist':
|
|
|
|
actionGist(req, res, note)
|
|
|
|
break
|
|
|
|
case 'revision':
|
|
|
|
actionRevision(req, res, note)
|
|
|
|
break
|
2019-08-25 03:02:15 +00:00
|
|
|
case 'pandoc':
|
|
|
|
actionPandoc(req, res, note)
|
|
|
|
break
|
2020-01-04 23:38:19 +00:00
|
|
|
default:
|
|
|
|
return res.redirect(config.serverURL + '/' + noteId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 08:08:10 +00:00
|
|
|
async function getMyNoteList (userId, callback) {
|
2020-06-30 10:30:21 +00:00
|
|
|
const myNotes = await Note.findAll({
|
|
|
|
where: {
|
2020-07-14 07:38:02 +00:00
|
|
|
ownerId: userId
|
2020-06-30 10:30:21 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
if (!myNotes) {
|
|
|
|
return callback(null, null)
|
|
|
|
}
|
|
|
|
try {
|
2020-07-14 07:38:02 +00:00
|
|
|
const myNoteList = myNotes.map(note => ({
|
|
|
|
id: Note.encodeNoteId(note.id),
|
|
|
|
text: note.title,
|
|
|
|
tags: Note.parseNoteInfo(note.content).tags,
|
|
|
|
createdAt: note.createdAt,
|
|
|
|
lastchangeAt: note.lastchangeAt,
|
|
|
|
shortId: note.shortid
|
|
|
|
}))
|
2020-06-30 10:30:21 +00:00
|
|
|
if (config.debug) {
|
2020-07-14 07:38:02 +00:00
|
|
|
logger.info('Parse myNoteList success: ' + userId)
|
2020-06-30 10:30:21 +00:00
|
|
|
}
|
|
|
|
return callback(null, myNoteList)
|
2020-07-01 03:33:41 +00:00
|
|
|
} catch (err) {
|
|
|
|
logger.error('Parse myNoteList failed')
|
|
|
|
return callback(err, null)
|
2020-06-30 10:30:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 08:08:10 +00:00
|
|
|
function listMyNotes (req, res) {
|
2020-06-30 10:30:21 +00:00
|
|
|
if (req.isAuthenticated()) {
|
|
|
|
getMyNoteList(req.user.id, (err, myNoteList) => {
|
|
|
|
if (err) return errorInternalError(req, res)
|
|
|
|
if (!myNoteList) return errorNotFound(req, res)
|
|
|
|
res.send({
|
|
|
|
myNotes: myNoteList
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
return errorForbidden(req, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-20 16:09:44 +00:00
|
|
|
const deleteNote = async (req, res) => {
|
2020-07-21 01:16:32 +00:00
|
|
|
if (req.isAuthenticated()) {
|
2020-07-20 16:09:44 +00:00
|
|
|
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
|
|
|
try {
|
|
|
|
const destroyed = await Note.destroy({
|
|
|
|
where: {
|
|
|
|
id: noteId,
|
|
|
|
ownerId: req.user.id
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if (!destroyed) {
|
|
|
|
logger.error('Delete note failed: Make sure the noteId and ownerId are correct.')
|
|
|
|
return errorNotFound(req, res)
|
|
|
|
}
|
2020-07-22 16:24:58 +00:00
|
|
|
|
2020-07-20 16:09:44 +00:00
|
|
|
historyDelete(req, res)
|
2020-07-22 16:24:58 +00:00
|
|
|
|
|
|
|
if (realtime.isNoteExistsInPool(noteId)) {
|
|
|
|
const note = realtime.getNoteFromNotePool(noteId)
|
|
|
|
realtime.disconnectSocketOnNote(note)
|
|
|
|
}
|
|
|
|
|
2020-07-20 16:09:44 +00:00
|
|
|
res.send({
|
|
|
|
status: 'ok'
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
logger.error('Delete note failed: Internal Error.')
|
|
|
|
return errorInternalError(req, res)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return errorForbidden(req, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-21 01:14:02 +00:00
|
|
|
const updateNote = async (req, res) => {
|
2021-03-09 11:31:46 +00:00
|
|
|
if (req.isAuthenticated() || config.allowAnonymousEdits) {
|
2020-07-21 01:14:02 +00:00
|
|
|
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
|
|
|
try {
|
|
|
|
const note = await Note.findOne({
|
|
|
|
where: {
|
2020-07-21 01:16:32 +00:00
|
|
|
id: noteId
|
2020-07-21 01:14:02 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
if (!note) {
|
|
|
|
logger.error('Update note failed: Can\'t find the note.')
|
|
|
|
return errorNotFound(req, res)
|
|
|
|
}
|
2020-07-21 01:16:32 +00:00
|
|
|
|
2020-07-27 09:59:51 +00:00
|
|
|
if (realtime.isNoteExistsInPool(noteId)) {
|
|
|
|
logger.error('Update note failed: There are online users opening this note.')
|
2020-07-30 10:55:36 +00:00
|
|
|
return res.status('403').json({ status: 'error', message: 'Update API can only be used when no users is online' })
|
2020-07-27 09:59:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const now = Date.now()
|
|
|
|
const content = req.body.content
|
2020-07-21 01:14:02 +00:00
|
|
|
const updated = await note.update({
|
2020-08-04 08:47:57 +00:00
|
|
|
title: Note.parseNoteTitle(content),
|
2020-07-27 09:59:51 +00:00
|
|
|
content: content,
|
2020-07-30 10:13:35 +00:00
|
|
|
lastchangeAt: now,
|
2021-03-18 07:56:19 +00:00
|
|
|
authorship: [
|
2020-07-27 09:59:51 +00:00
|
|
|
[
|
2021-03-18 07:56:19 +00:00
|
|
|
req.isAuthenticated() ? req.user.id : null,
|
2020-07-27 09:59:51 +00:00
|
|
|
0,
|
|
|
|
content.length,
|
|
|
|
now,
|
|
|
|
now
|
|
|
|
]
|
2021-03-18 07:56:19 +00:00
|
|
|
]
|
2020-07-21 01:14:02 +00:00
|
|
|
})
|
2020-07-27 09:59:51 +00:00
|
|
|
|
2020-07-21 01:14:02 +00:00
|
|
|
if (!updated) {
|
2020-07-27 09:59:51 +00:00
|
|
|
logger.error('Update note failed: Write note content error.')
|
2020-07-21 01:14:02 +00:00
|
|
|
return errorInternalError(req, res)
|
|
|
|
}
|
2020-07-27 09:59:51 +00:00
|
|
|
|
2021-03-09 11:31:46 +00:00
|
|
|
if (req.isAuthenticated()) {
|
2020-08-13 08:17:44 +00:00
|
|
|
updateHistory(req.user.id, noteId, content)
|
2021-03-09 11:31:46 +00:00
|
|
|
}
|
2020-08-04 08:47:57 +00:00
|
|
|
|
2020-07-27 09:59:51 +00:00
|
|
|
Revision.saveNoteRevision(note, (err, revision) => {
|
2020-07-30 10:13:35 +00:00
|
|
|
if (err) {
|
|
|
|
logger.error(err)
|
|
|
|
return errorInternalError(req, res)
|
|
|
|
}
|
2020-07-27 09:59:51 +00:00
|
|
|
if (!revision) return errorNotFound(req, res)
|
2020-07-30 10:13:35 +00:00
|
|
|
res.send({
|
|
|
|
status: 'ok'
|
|
|
|
})
|
2020-07-21 01:14:02 +00:00
|
|
|
})
|
|
|
|
} catch (err) {
|
2021-03-09 11:31:46 +00:00
|
|
|
logger.error(err.stack)
|
2020-07-21 01:14:02 +00:00
|
|
|
logger.error('Update note failed: Internal Error.')
|
|
|
|
return errorInternalError(req, res)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return errorForbidden(req, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 22:58:40 +00:00
|
|
|
exports.showNote = showNote
|
2020-01-04 23:17:55 +00:00
|
|
|
exports.showPublishNote = showPublishNote
|
2020-01-04 23:38:19 +00:00
|
|
|
exports.noteActions = noteActions
|
2020-06-30 10:30:21 +00:00
|
|
|
exports.listMyNotes = listMyNotes
|
2020-07-20 16:09:44 +00:00
|
|
|
exports.deleteNote = deleteNote
|
2020-07-21 01:14:02 +00:00
|
|
|
exports.updateNote = updateNote
|