mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-05 15:19:02 +02:00
Merge pull request #28367 from overleaf/ar-migrate-routermjs-to-zod
[web] migrate router.mjs to zod GitOrigin-RevId: d3fc21a11351f3e2deb5011cd1beeb86286a300b
This commit is contained in:
@@ -11,6 +11,7 @@ const Settings = require('@overleaf/settings')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const SessionManager = require('../Authentication/SessionManager')
|
||||
const { RateLimiter } = require('../../infrastructure/RateLimiter')
|
||||
const { z, zz, validateReq } = require('../../infrastructure/Validation')
|
||||
const ClsiCookieManager = require('./ClsiCookieManager')(
|
||||
Settings.apis.clsi?.backendGroupName
|
||||
)
|
||||
@@ -138,6 +139,25 @@ async function _syncTeX(req, res, direction, validatedOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAuxFilesSchema = z.object({
|
||||
params: z.object({
|
||||
Project_id: zz.objectId(),
|
||||
}),
|
||||
query: z.object({
|
||||
clsiserverid: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
const wordCountSchema = z.object({
|
||||
params: z.object({
|
||||
Project_id: zz.objectId(),
|
||||
}),
|
||||
query: z.object({
|
||||
clsiserverid: z.string().optional(),
|
||||
file: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
const _CompileController = {
|
||||
async compile(req, res) {
|
||||
res.setTimeout(COMPILE_TIMEOUT_MS)
|
||||
@@ -384,8 +404,9 @@ const _CompileController = {
|
||||
},
|
||||
|
||||
async deleteAuxFiles(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const { clsiserverid } = req.query
|
||||
const { params, query } = validateReq(req, deleteAuxFilesSchema)
|
||||
const projectId = params.Project_id
|
||||
const { clsiserverid } = query
|
||||
const userId = await CompileController._getUserIdForCompile(req)
|
||||
await CompileManager.promises.deleteAuxFiles(
|
||||
projectId,
|
||||
@@ -650,9 +671,10 @@ const _CompileController = {
|
||||
},
|
||||
|
||||
async wordCount(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const file = req.query.file || false
|
||||
const { clsiserverid } = req.query
|
||||
const { params, query } = validateReq(req, wordCountSchema)
|
||||
const projectId = params.Project_id
|
||||
const file = query.file || false
|
||||
const { clsiserverid } = query
|
||||
const userId = CompileController._getUserIdForCompile(req)
|
||||
|
||||
const body = await CompileManager.promises.wordCount(
|
||||
|
||||
@@ -45,6 +45,7 @@ import TagsHandler from '../Tags/TagsHandler.js'
|
||||
import TutorialHandler from '../Tutorial/TutorialHandler.js'
|
||||
import UserUpdater from '../User/UserUpdater.js'
|
||||
import Modules from '../../infrastructure/Modules.js'
|
||||
import { z, zz, validateReq } from '../../infrastructure/Validation.js'
|
||||
import UserGetter from '../User/UserGetter.js'
|
||||
import { isStandaloneAiAddOnPlanCode } from '../Subscription/AiHelper.js'
|
||||
import SubscriptionController from '../Subscription/SubscriptionController.js'
|
||||
@@ -55,6 +56,34 @@ const { ObjectId } = mongodb
|
||||
* @import { GetProjectsRequest, GetProjectsResponse, Project } from "./types"
|
||||
*/
|
||||
|
||||
const updateProjectAdminSettingsSchema = z.object({
|
||||
params: z.object({
|
||||
Project_id: zz.coercedObjectId(ObjectId),
|
||||
}),
|
||||
body: z.object({
|
||||
publicAccessLevel: z
|
||||
.enum(
|
||||
[PublicAccessLevels.PRIVATE, PublicAccessLevels.TOKEN_BASED],
|
||||
'unexpected access level'
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
const updateProjectSettingsSchema = z.object({
|
||||
params: z.object({
|
||||
Project_id: zz.coercedObjectId(),
|
||||
}),
|
||||
body: z.object({
|
||||
compiler: z.string().optional(),
|
||||
imageName: z.string().optional(),
|
||||
mainBibliographyDocId: zz.objectId().optional(),
|
||||
name: z.string().optional(),
|
||||
rootDocId: zz.objectId().optional(),
|
||||
spellCheckLanguage: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
const _ProjectController = {
|
||||
_isInPercentageRollout(rolloutName, objectId, percentage) {
|
||||
if (Settings.bypassPercentageRollouts === true) {
|
||||
@@ -67,38 +96,36 @@ const _ProjectController = {
|
||||
},
|
||||
|
||||
async updateProjectSettings(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const { params, body } = validateReq(req, updateProjectSettingsSchema)
|
||||
const projectId = params.Project_id
|
||||
|
||||
if (req.body.compiler != null) {
|
||||
await EditorController.promises.setCompiler(projectId, req.body.compiler)
|
||||
if (body.compiler != null) {
|
||||
await EditorController.promises.setCompiler(projectId, body.compiler)
|
||||
}
|
||||
|
||||
if (req.body.imageName != null) {
|
||||
await EditorController.promises.setImageName(
|
||||
projectId,
|
||||
req.body.imageName
|
||||
)
|
||||
if (body.imageName != null) {
|
||||
await EditorController.promises.setImageName(projectId, body.imageName)
|
||||
}
|
||||
|
||||
if (req.body.name != null) {
|
||||
await EditorController.promises.renameProject(projectId, req.body.name)
|
||||
if (body.name != null) {
|
||||
await EditorController.promises.renameProject(projectId, body.name)
|
||||
}
|
||||
|
||||
if (req.body.spellCheckLanguage != null) {
|
||||
if (body.spellCheckLanguage != null) {
|
||||
await EditorController.promises.setSpellCheckLanguage(
|
||||
projectId,
|
||||
req.body.spellCheckLanguage
|
||||
body.spellCheckLanguage
|
||||
)
|
||||
}
|
||||
|
||||
if (req.body.rootDocId != null) {
|
||||
await EditorController.promises.setRootDoc(projectId, req.body.rootDocId)
|
||||
if (body.rootDocId != null) {
|
||||
await EditorController.promises.setRootDoc(projectId, body.rootDocId)
|
||||
}
|
||||
|
||||
if (req.body.mainBibliographyDocId != null) {
|
||||
if (body.mainBibliographyDocId != null) {
|
||||
await EditorController.promises.setMainBibliographyDoc(
|
||||
projectId,
|
||||
req.body.mainBibliographyDocId
|
||||
body.mainBibliographyDocId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -106,26 +133,17 @@ const _ProjectController = {
|
||||
},
|
||||
|
||||
async updateProjectAdminSettings(req, res) {
|
||||
const projectId = req.params.Project_id
|
||||
const { params, body } = validateReq(req, updateProjectAdminSettingsSchema)
|
||||
const projectId = params.Project_id
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
if (!Features.hasFeature('link-sharing')) {
|
||||
return res.sendStatus(403) // return Forbidden if link sharing is not enabled
|
||||
}
|
||||
const publicAccessLevel = req.body.publicAccessLevel
|
||||
const publicAccessLevels = [
|
||||
PublicAccessLevels.READ_ONLY,
|
||||
PublicAccessLevels.READ_AND_WRITE,
|
||||
PublicAccessLevels.PRIVATE,
|
||||
PublicAccessLevels.TOKEN_BASED,
|
||||
]
|
||||
|
||||
if (
|
||||
req.body.publicAccessLevel != null &&
|
||||
publicAccessLevels.includes(publicAccessLevel)
|
||||
) {
|
||||
if (body.publicAccessLevel != null) {
|
||||
await EditorController.promises.setPublicAccessLevel(
|
||||
projectId,
|
||||
req.body.publicAccessLevel
|
||||
body.publicAccessLevel
|
||||
)
|
||||
|
||||
await ProjectAuditLogHandler.promises.addEntry(
|
||||
@@ -133,7 +151,7 @@ const _ProjectController = {
|
||||
'toggle-access-level',
|
||||
user._id,
|
||||
req.ip,
|
||||
{ publicAccessLevel: req.body.publicAccessLevel, status: 'OK' }
|
||||
{ publicAccessLevel: body.publicAccessLevel, status: 'OK' }
|
||||
)
|
||||
res.sendStatus(204)
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
// @ts-check
|
||||
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import LearnedWordsManager from './LearnedWordsManager.js'
|
||||
import { z, validateReq } from '../../infrastructure/Validation.js'
|
||||
|
||||
const learnSchema = z.object({
|
||||
body: z.object({
|
||||
word: z.string().min(1),
|
||||
}),
|
||||
})
|
||||
|
||||
const unlearnSchema = z.object({
|
||||
body: z.object({
|
||||
word: z.string().min(1),
|
||||
}),
|
||||
})
|
||||
|
||||
export default {
|
||||
learn(req, res, next) {
|
||||
const { word } = req.body
|
||||
const { body } = validateReq(req, learnSchema)
|
||||
const { word } = body
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
LearnedWordsManager.learnWord(userId, word, err => {
|
||||
if (err) return next(err)
|
||||
@@ -12,7 +28,8 @@ export default {
|
||||
},
|
||||
|
||||
unlearn(req, res, next) {
|
||||
const { word } = req.body
|
||||
const { body } = validateReq(req, unlearnSchema)
|
||||
const { word } = body
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
LearnedWordsManager.unlearnWord(userId, word, err => {
|
||||
if (err) return next(err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import TagsHandler from './TagsHandler.js'
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import Errors from '../Errors/Errors.js'
|
||||
import { z, validateReq } from '../../infrastructure/Validation.js'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
|
||||
async function _getTags(userId, _req, res) {
|
||||
@@ -21,9 +22,17 @@ async function getAllTags(req, res) {
|
||||
await _getTags(userId, req, res)
|
||||
}
|
||||
|
||||
const createTagSchema = z.object({
|
||||
body: z.object({
|
||||
name: z.string(),
|
||||
color: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
async function createTag(req, res) {
|
||||
const { body } = validateReq(req, createTagSchema)
|
||||
const { name, color } = body
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const { name, color } = req.body
|
||||
const tag = await TagsHandler.promises.createTag(userId, name, color)
|
||||
res.json(tag)
|
||||
}
|
||||
@@ -35,10 +44,20 @@ async function addProjectToTag(req, res) {
|
||||
res.status(204).end()
|
||||
}
|
||||
|
||||
const addProjectsToTagSchema = z.object({
|
||||
params: z.object({
|
||||
tagId: z.string(),
|
||||
}),
|
||||
body: z.object({
|
||||
projectIds: z.string().array(),
|
||||
}),
|
||||
})
|
||||
|
||||
async function addProjectsToTag(req, res) {
|
||||
const { params, body } = validateReq(req, addProjectsToTagSchema)
|
||||
const { tagId } = params
|
||||
const { projectIds } = body
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const { tagId } = req.params
|
||||
const { projectIds } = req.body
|
||||
await TagsHandler.promises.addProjectsToTag(userId, tagId, projectIds)
|
||||
res.status(204).end()
|
||||
}
|
||||
@@ -50,10 +69,20 @@ async function removeProjectFromTag(req, res, next) {
|
||||
res.status(204).end()
|
||||
}
|
||||
|
||||
const removeProjectsFromTagSchema = z.object({
|
||||
params: z.object({
|
||||
tagId: z.string(),
|
||||
}),
|
||||
body: z.object({
|
||||
projectIds: z.string().array(),
|
||||
}),
|
||||
})
|
||||
|
||||
async function removeProjectsFromTag(req, res, next) {
|
||||
const { params, body } = validateReq(req, removeProjectsFromTagSchema)
|
||||
const { tagId } = params
|
||||
const { projectIds } = body
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const { tagId } = req.params
|
||||
const { projectIds } = req.body
|
||||
await TagsHandler.promises.removeProjectsFromTag(userId, tagId, projectIds)
|
||||
res.status(204).end()
|
||||
}
|
||||
@@ -65,10 +94,20 @@ async function deleteTag(req, res) {
|
||||
res.status(204).end()
|
||||
}
|
||||
|
||||
const renameTagSchema = z.object({
|
||||
params: z.object({
|
||||
tagId: z.string(),
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
async function renameTag(req, res) {
|
||||
const { params, body } = validateReq(req, renameTagSchema)
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const { tagId } = req.params
|
||||
const name = req.body?.name
|
||||
const { tagId } = params
|
||||
const name = body.name
|
||||
if (!name) {
|
||||
return res.status(400).end()
|
||||
}
|
||||
@@ -76,11 +115,22 @@ async function renameTag(req, res) {
|
||||
res.status(204).end()
|
||||
}
|
||||
|
||||
const editTagSchema = z.object({
|
||||
params: z.object({
|
||||
tagId: z.string(),
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string(),
|
||||
color: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
async function editTag(req, res) {
|
||||
const { params, body } = validateReq(req, editTagSchema)
|
||||
const { tagId } = params
|
||||
const name = body.name
|
||||
const color = body.color
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const { tagId } = req.params
|
||||
const name = req.body?.name
|
||||
const color = req.body?.color
|
||||
if (!name) {
|
||||
return res.status(400).end()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import metrics from '@overleaf/metrics'
|
||||
import AuthenticationManager from '../Authentication/AuthenticationManager.js'
|
||||
import SessionManager from '../Authentication/SessionManager.js'
|
||||
import Features from '../../infrastructure/Features.js'
|
||||
import { z, validateReq } from '../../infrastructure/Validation.js'
|
||||
import UserAuditLogHandler from './UserAuditLogHandler.js'
|
||||
import UserSessionsManager from './UserSessionsManager.js'
|
||||
import UserUpdater from './UserUpdater.js'
|
||||
@@ -328,7 +329,18 @@ async function unsubscribe(req, res, next) {
|
||||
})
|
||||
}
|
||||
|
||||
const updateUserSettingsSchema = z.object({
|
||||
body: z
|
||||
.object({
|
||||
first_name: z.string().max(255).nullish(),
|
||||
last_name: z.string().max(255).nullish(),
|
||||
})
|
||||
.passthrough(),
|
||||
// TODO: complete the schema and remove the passthrough
|
||||
})
|
||||
|
||||
async function updateUserSettings(req, res, next) {
|
||||
const { body } = validateReq(req, updateUserSettingsSchema)
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
req.logger.addFields({ userId })
|
||||
|
||||
@@ -337,68 +349,67 @@ async function updateUserSettings(req, res, next) {
|
||||
throw new OError('problem updating user settings', { userId })
|
||||
}
|
||||
|
||||
if (req.body.first_name != null) {
|
||||
user.first_name = req.body.first_name.trim()
|
||||
if (body.first_name != null) {
|
||||
user.first_name = body.first_name.trim()
|
||||
}
|
||||
if (req.body.last_name != null) {
|
||||
user.last_name = req.body.last_name.trim()
|
||||
if (body.last_name != null) {
|
||||
user.last_name = body.last_name.trim()
|
||||
}
|
||||
if (req.body.role != null) {
|
||||
user.role = req.body.role.trim()
|
||||
if (body.role != null) {
|
||||
user.role = body.role.trim()
|
||||
}
|
||||
if (req.body.institution != null) {
|
||||
user.institution = req.body.institution.trim()
|
||||
if (body.institution != null) {
|
||||
user.institution = body.institution.trim()
|
||||
}
|
||||
if (req.body.mode != null) {
|
||||
user.ace.mode = req.body.mode
|
||||
if (body.mode != null) {
|
||||
user.ace.mode = body.mode
|
||||
}
|
||||
if (req.body.editorTheme != null) {
|
||||
user.ace.theme = req.body.editorTheme
|
||||
if (body.editorTheme != null) {
|
||||
user.ace.theme = body.editorTheme
|
||||
}
|
||||
if (req.body.overallTheme != null) {
|
||||
user.ace.overallTheme = req.body.overallTheme
|
||||
if (body.overallTheme != null) {
|
||||
user.ace.overallTheme = body.overallTheme
|
||||
}
|
||||
if (req.body.fontSize != null) {
|
||||
user.ace.fontSize = req.body.fontSize
|
||||
if (body.fontSize != null) {
|
||||
user.ace.fontSize = body.fontSize
|
||||
}
|
||||
if (req.body.autoComplete != null) {
|
||||
user.ace.autoComplete = req.body.autoComplete
|
||||
if (body.autoComplete != null) {
|
||||
user.ace.autoComplete = body.autoComplete
|
||||
}
|
||||
if (req.body.autoPairDelimiters != null) {
|
||||
user.ace.autoPairDelimiters = req.body.autoPairDelimiters
|
||||
if (body.autoPairDelimiters != null) {
|
||||
user.ace.autoPairDelimiters = body.autoPairDelimiters
|
||||
}
|
||||
if (req.body.spellCheckLanguage != null) {
|
||||
user.ace.spellCheckLanguage = req.body.spellCheckLanguage
|
||||
if (body.spellCheckLanguage != null) {
|
||||
user.ace.spellCheckLanguage = body.spellCheckLanguage
|
||||
}
|
||||
if (req.body.pdfViewer != null) {
|
||||
user.ace.pdfViewer = req.body.pdfViewer
|
||||
if (body.pdfViewer != null) {
|
||||
user.ace.pdfViewer = body.pdfViewer
|
||||
}
|
||||
if (req.body.syntaxValidation != null) {
|
||||
user.ace.syntaxValidation = req.body.syntaxValidation
|
||||
if (body.syntaxValidation != null) {
|
||||
user.ace.syntaxValidation = body.syntaxValidation
|
||||
}
|
||||
if (req.body.fontFamily != null) {
|
||||
user.ace.fontFamily = req.body.fontFamily
|
||||
if (body.fontFamily != null) {
|
||||
user.ace.fontFamily = body.fontFamily
|
||||
}
|
||||
if (req.body.lineHeight != null) {
|
||||
user.ace.lineHeight = req.body.lineHeight
|
||||
if (body.lineHeight != null) {
|
||||
user.ace.lineHeight = body.lineHeight
|
||||
}
|
||||
if (req.body.mathPreview != null) {
|
||||
user.ace.mathPreview = req.body.mathPreview
|
||||
if (body.mathPreview != null) {
|
||||
user.ace.mathPreview = body.mathPreview
|
||||
}
|
||||
if (req.body.breadcrumbs != null) {
|
||||
user.ace.breadcrumbs = Boolean(req.body.breadcrumbs)
|
||||
if (body.breadcrumbs != null) {
|
||||
user.ace.breadcrumbs = Boolean(body.breadcrumbs)
|
||||
}
|
||||
if (req.body.referencesSearchMode != null) {
|
||||
const mode =
|
||||
req.body.referencesSearchMode === 'simple' ? 'simple' : 'advanced'
|
||||
if (body.referencesSearchMode != null) {
|
||||
const mode = body.referencesSearchMode === 'simple' ? 'simple' : 'advanced'
|
||||
user.ace.referencesSearchMode = mode
|
||||
}
|
||||
if (req.body.enableNewEditor != null) {
|
||||
user.ace.enableNewEditor = Boolean(req.body.enableNewEditor)
|
||||
if (body.enableNewEditor != null) {
|
||||
user.ace.enableNewEditor = Boolean(body.enableNewEditor)
|
||||
}
|
||||
await user.save()
|
||||
|
||||
const newEmail = req.body.email?.trim().toLowerCase()
|
||||
const newEmail = body.email?.trim().toLowerCase()
|
||||
if (
|
||||
newEmail == null ||
|
||||
newEmail === user.email ||
|
||||
|
||||
@@ -58,12 +58,10 @@ import SystemMessageController from './Features/SystemMessages/SystemMessageCont
|
||||
import AnalyticsRegistrationSourceMiddleware from './Features/Analytics/AnalyticsRegistrationSourceMiddleware.js'
|
||||
import AnalyticsUTMTrackingMiddleware from './Features/Analytics/AnalyticsUTMTrackingMiddleware.mjs'
|
||||
import CaptchaMiddleware from './Features/Captcha/CaptchaMiddleware.mjs'
|
||||
import { Joi, validate } from './infrastructure/Validation.js'
|
||||
import UnsupportedBrowserMiddleware from './infrastructure/UnsupportedBrowserMiddleware.js'
|
||||
import logger from '@overleaf/logger'
|
||||
import _ from 'lodash'
|
||||
import { plainTextResponse } from './infrastructure/Response.js'
|
||||
import PublicAccessLevels from './Features/Authorization/PublicAccessLevels.js'
|
||||
import SocketDiagnostics from './Features/SocketDiagnostics/SocketDiagnostics.mjs'
|
||||
import ClsiCacheController from './Features/Compile/ClsiCacheController.mjs'
|
||||
import AsyncLocalStorage from './infrastructure/AsyncLocalStorage.js'
|
||||
@@ -305,12 +303,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
webRouter.post(
|
||||
'/user/settings',
|
||||
AuthenticationController.requireLogin(),
|
||||
validate({
|
||||
body: Joi.object({
|
||||
first_name: Joi.string().allow(null, '').max(255),
|
||||
last_name: Joi.string().allow(null, '').max(255),
|
||||
}).unknown(),
|
||||
}),
|
||||
UserController.updateUserSettings
|
||||
)
|
||||
webRouter.post(
|
||||
@@ -565,13 +557,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
)
|
||||
webRouter.post(
|
||||
'/project/:Project_id/settings',
|
||||
validate({
|
||||
body: Joi.object({
|
||||
publicAccessLevel: Joi.string()
|
||||
.valid(PublicAccessLevels.PRIVATE, PublicAccessLevels.TOKEN_BASED)
|
||||
.optional(),
|
||||
}),
|
||||
}),
|
||||
AuthorizationMiddleware.ensureUserCanWriteProjectSettings,
|
||||
ProjectController.updateProjectSettings
|
||||
)
|
||||
@@ -659,7 +644,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
|
||||
webRouter.delete(
|
||||
'/project/:Project_id/output',
|
||||
validate({ query: { clsiserverid: Joi.string() } }),
|
||||
AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
CompileController.deleteAuxFiles
|
||||
)
|
||||
@@ -675,7 +659,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
)
|
||||
webRouter.get(
|
||||
'/project/:Project_id/wordcount',
|
||||
validate({ query: { clsiserverid: Joi.string() } }),
|
||||
AuthorizationMiddleware.ensureUserCanReadProject,
|
||||
CompileController.wordCount
|
||||
)
|
||||
@@ -817,35 +800,18 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
'/tag',
|
||||
AuthenticationController.requireLogin(),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.createTag),
|
||||
validate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
color: Joi.string(),
|
||||
}),
|
||||
}),
|
||||
TagsController.createTag
|
||||
)
|
||||
webRouter.post(
|
||||
'/tag/:tagId/rename',
|
||||
AuthenticationController.requireLogin(),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.renameTag),
|
||||
validate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
TagsController.renameTag
|
||||
)
|
||||
webRouter.post(
|
||||
'/tag/:tagId/edit',
|
||||
AuthenticationController.requireLogin(),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.renameTag),
|
||||
validate({
|
||||
body: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
color: Joi.string(),
|
||||
}),
|
||||
}),
|
||||
TagsController.editTag
|
||||
)
|
||||
webRouter.delete(
|
||||
@@ -864,11 +830,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
'/tag/:tagId/projects',
|
||||
AuthenticationController.requireLogin(),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.addProjectsToTag),
|
||||
validate({
|
||||
body: Joi.object({
|
||||
projectIds: Joi.array().items(Joi.string()).required(),
|
||||
}),
|
||||
}),
|
||||
TagsController.addProjectsToTag
|
||||
)
|
||||
webRouter.delete(
|
||||
@@ -881,11 +842,6 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
'/tag/:tagId/projects/remove',
|
||||
AuthenticationController.requireLogin(),
|
||||
RateLimiterMiddleware.rateLimit(rateLimiters.removeProjectsFromTag),
|
||||
validate({
|
||||
body: Joi.object({
|
||||
projectIds: Joi.array().items(Joi.string()).required(),
|
||||
}),
|
||||
}),
|
||||
TagsController.removeProjectsFromTag
|
||||
)
|
||||
|
||||
@@ -996,22 +952,12 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
|
||||
webRouter.post(
|
||||
'/spelling/learn',
|
||||
validate({
|
||||
body: Joi.object({
|
||||
word: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
AuthenticationController.requireLogin(),
|
||||
SpellingController.learn
|
||||
)
|
||||
|
||||
webRouter.post(
|
||||
'/spelling/unlearn',
|
||||
validate({
|
||||
body: Joi.object({
|
||||
word: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
AuthenticationController.requireLogin(),
|
||||
SpellingController.unlearn
|
||||
)
|
||||
|
||||
@@ -165,6 +165,53 @@ describe('Project CRUD', function () {
|
||||
expectObjectIdArrayEqual(trashedProject.trashed, [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('ProjectAdminSettings', async function () {
|
||||
it('publicAccessLevel can be set to private', async function () {
|
||||
const { response } = await this.user.doRequest('POST', {
|
||||
url: `/project/${this.projectId}/settings/admin`,
|
||||
json: {
|
||||
publicAccessLevel: 'private',
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).to.equal(204)
|
||||
const project = await Project.findById(this.projectId).exec()
|
||||
expect(project.publicAccesLevel).to.equal('private')
|
||||
})
|
||||
it('publicAccessLevel can be set to tokenBased', async function () {
|
||||
await this.user.makePrivate(this.projectId)
|
||||
const { response } = await this.user.doRequest('POST', {
|
||||
url: `/project/${this.projectId}/settings/admin`,
|
||||
json: {
|
||||
publicAccessLevel: 'tokenBased',
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).to.equal(204)
|
||||
const project = await Project.findById(this.projectId).exec()
|
||||
expect(project.publicAccesLevel).to.equal('tokenBased')
|
||||
})
|
||||
it('returns a 400 when publicAccessLevel is set an unsupported access level', async function () {
|
||||
await this.user.makePrivate(this.projectId)
|
||||
const { response, body } = await this.user.doRequest('POST', {
|
||||
url: `/project/${this.projectId}/settings/admin`,
|
||||
json: {
|
||||
publicAccessLevel: 'readOnly',
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).to.equal(400)
|
||||
expect(body).to.include('Unexpected access level')
|
||||
const project = await Project.findById(this.projectId).exec()
|
||||
expect(project.publicAccesLevel).to.equal('private')
|
||||
})
|
||||
it('returns a 500 when no publicAccessLevel is provided', async function () {
|
||||
const { response, body } = await this.user.doRequest('POST', {
|
||||
url: `/project/${this.projectId}/settings/admin`,
|
||||
json: {},
|
||||
})
|
||||
expect(response.statusCode).to.equal(500)
|
||||
expect(body).to.equal('Internal Server Error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function expectObjectIdArrayEqual(objectIdArray, stringArray) {
|
||||
|
||||
@@ -97,6 +97,15 @@ describe('CompileController', function () {
|
||||
done() {}
|
||||
},
|
||||
}),
|
||||
// TODO: remove this once we remove Joi/Celebrate
|
||||
celebrate: (this.celebrate = {
|
||||
celebrate: sinon.stub(),
|
||||
errors: sinon.stub(),
|
||||
Joi: {
|
||||
any: sinon.stub(),
|
||||
extend: sinon.stub(),
|
||||
},
|
||||
}),
|
||||
'./CompileManager': this.CompileManager,
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'./ClsiManager': this.ClsiManager,
|
||||
@@ -118,7 +127,7 @@ describe('CompileController', function () {
|
||||
},
|
||||
},
|
||||
})
|
||||
this.projectId = 'project-id'
|
||||
this.projectId = 'abc123def456abc123def456'
|
||||
this.build_id = '18fbe9e7564-30dcb2f71250c690'
|
||||
this.next = sinon.stub()
|
||||
this.req = new MockRequest()
|
||||
|
||||
@@ -505,6 +505,10 @@ describe('ProjectController', function () {
|
||||
})
|
||||
|
||||
describe('updateProjectSettings', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.req.params.Project_id = ctx.project_id.toString()
|
||||
})
|
||||
|
||||
it('should update the name', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.EditorController.promises.renameProject = sinon.stub().resolves()
|
||||
@@ -570,7 +574,9 @@ describe('ProjectController', function () {
|
||||
it('should update the root doc', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.EditorController.promises.setRootDoc = sinon.stub().resolves()
|
||||
ctx.req.body = { rootDocId: (ctx.rootDocId = 'root-doc-id') }
|
||||
ctx.req.body = {
|
||||
rootDocId: (ctx.rootDocId = 'abc123def456abc123def456'),
|
||||
}
|
||||
ctx.res.sendStatus = code => {
|
||||
ctx.EditorController.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.rootDocId)
|
||||
@@ -590,13 +596,14 @@ describe('ProjectController', function () {
|
||||
ctx.EditorController.promises.setPublicAccessLevel = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.req.params.Project_id = ctx.project_id.toString()
|
||||
ctx.req.body = {
|
||||
publicAccessLevel: 'readOnly',
|
||||
publicAccessLevel: 'tokenBased',
|
||||
}
|
||||
await new Promise(resolve => {
|
||||
ctx.res.sendStatus = code => {
|
||||
ctx.EditorController.promises.setPublicAccessLevel
|
||||
.calledWith(ctx.project_id, 'readOnly')
|
||||
.calledWith(ctx.project_id, 'tokenBased')
|
||||
.should.equal(true)
|
||||
code.should.equal(204)
|
||||
resolve()
|
||||
@@ -611,8 +618,9 @@ describe('ProjectController', function () {
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.req.body = {
|
||||
publicAccessLevel: 'readOnly',
|
||||
publicAccessLevel: 'tokenBased',
|
||||
}
|
||||
ctx.req.params.Project_id = ctx.project_id.toString()
|
||||
await new Promise(resolve => {
|
||||
ctx.res.sendStatus = code => {
|
||||
ctx.ProjectAuditLogHandler.promises.addEntry
|
||||
@@ -622,7 +630,7 @@ describe('ProjectController', function () {
|
||||
ctx.user._id,
|
||||
ctx.req.ip,
|
||||
{
|
||||
publicAccessLevel: 'readOnly',
|
||||
publicAccessLevel: 'tokenBased',
|
||||
status: 'OK',
|
||||
}
|
||||
)
|
||||
@@ -638,8 +646,9 @@ describe('ProjectController', function () {
|
||||
ctx.EditorController.promises.setPublicAccessLevel = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.req.params.Project_id = ctx.project_id.toString()
|
||||
ctx.req.body = {
|
||||
publicAccessLevel: 'readOnly',
|
||||
publicAccessLevel: 'tokenBased',
|
||||
}
|
||||
await new Promise(resolve => {
|
||||
ctx.res.sendStatus = code => {
|
||||
@@ -740,6 +749,7 @@ describe('ProjectController', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.newProjectName = 'my supper great new project'
|
||||
ctx.req.body.newProjectName = ctx.newProjectName
|
||||
ctx.req.params.Project_id = ctx.project_id.toString()
|
||||
})
|
||||
|
||||
it('should call the editor controller', async function (ctx) {
|
||||
@@ -747,9 +757,16 @@ describe('ProjectController', function () {
|
||||
ctx.EditorController.promises.renameProject.resolves()
|
||||
ctx.res.sendStatus = code => {
|
||||
code.should.equal(200)
|
||||
ctx.EditorController.promises.renameProject
|
||||
.calledWith(ctx.project_id, ctx.newProjectName)
|
||||
.should.equal(true)
|
||||
|
||||
expect(ctx.EditorController.promises.renameProject).to.have.been
|
||||
.called
|
||||
expect(
|
||||
ctx.EditorController.promises.renameProject.args[0][0].toString()
|
||||
).to.equal(ctx.project_id.toString())
|
||||
expect(
|
||||
ctx.EditorController.promises.renameProject.args[0][1]
|
||||
).to.equal(ctx.newProjectName)
|
||||
|
||||
resolve()
|
||||
}
|
||||
ctx.ProjectController.renameProject(ctx.req, ctx.res)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { assert, vi } from 'vitest'
|
||||
import { assert, beforeEach, describe, it, vi } from 'vitest'
|
||||
import sinon from 'sinon'
|
||||
import { ZodError } from 'zod'
|
||||
|
||||
const modulePath = '../../../../app/src/Features/Tags/TagsController.mjs'
|
||||
|
||||
@@ -198,20 +199,11 @@ describe('TagsController', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('without a name', async function (ctx) {
|
||||
await new Promise(resolve => {
|
||||
ctx.req.body = { name: undefined }
|
||||
ctx.TagsController.renameTag(ctx.req, {
|
||||
status: code => {
|
||||
assert.equal(code, 400)
|
||||
sinon.assert.notCalled(ctx.TagsHandler.promises.renameTag)
|
||||
resolve()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
it('without a name', function (ctx) {
|
||||
ctx.req.body = { name: undefined }
|
||||
ctx.TagsController.renameTag(ctx.req, ctx.res).should.be.rejectedWith(
|
||||
ZodError
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user