[web] migrate v1 api calls away from ShareLaTeX branded endpoints GitOrigin-RevId: af08ee029d0cee8f0f171d21b4f2b0b76222b3f8
474 lines
12 KiB
JavaScript
474 lines
12 KiB
JavaScript
const AbstractMockApi = require('./AbstractMockApi')
|
|
const moment = require('moment')
|
|
const sinon = require('sinon')
|
|
|
|
class MockV1Api extends AbstractMockApi {
|
|
reset() {
|
|
this.affiliations = []
|
|
this.allInstitutionDomains = new Set()
|
|
this.blocklistedDomains = []
|
|
this.brand_variations = {}
|
|
this.brands = {}
|
|
this.doc_exported = {}
|
|
this.docInfo = {}
|
|
this.existingEmails = []
|
|
this.exportId = null
|
|
this.exportParams = null
|
|
this.institutionDomains = {}
|
|
this.institutionId = 1000
|
|
this.institutions = {}
|
|
this.syncUserFeatures = sinon.stub()
|
|
this.templates = {}
|
|
this.updateEmail = sinon.stub()
|
|
this.users = {}
|
|
this.v1Id = 1000
|
|
this.validation_clients = {}
|
|
}
|
|
|
|
nextInstitutionId() {
|
|
return this.institutionId++
|
|
}
|
|
|
|
nextV1Id() {
|
|
return this.v1Id++
|
|
}
|
|
|
|
setUser(id, user) {
|
|
this.users[id] = user
|
|
}
|
|
|
|
getDocInfo(token) {
|
|
return this.docInfo[token] || null
|
|
}
|
|
|
|
setDocInfo(token, info) {
|
|
this.docInfo[token] = info
|
|
}
|
|
|
|
setExportId(id) {
|
|
this.exportId = id
|
|
}
|
|
|
|
getLastExportParams() {
|
|
return this.exportParams
|
|
}
|
|
|
|
clearExportParams() {
|
|
this.exportParams = null
|
|
}
|
|
|
|
createInstitution(options = {}) {
|
|
const id = options.university_id || this.nextInstitutionId()
|
|
options.id = id // include ID so that it is included in APIs
|
|
this.institutions[id] = { ...options }
|
|
if (options && options.hostname) {
|
|
this.addInstitutionDomain(id, options.hostname, {
|
|
confirmed: options.confirmed,
|
|
})
|
|
}
|
|
return id
|
|
}
|
|
|
|
addInstitutionDomain(institutionId, domain, options = {}) {
|
|
if (this.allInstitutionDomains.has(domain)) return
|
|
if (!this.institutionDomains[institutionId]) {
|
|
this.institutionDomains[institutionId] = {}
|
|
}
|
|
this.institutionDomains[institutionId][domain] = options
|
|
this.allInstitutionDomains.add(domain)
|
|
}
|
|
|
|
updateInstitution(id, options) {
|
|
Object.assign(this.institutions[id], options)
|
|
}
|
|
|
|
updateInstitutionDomain(id, domain, options = {}) {
|
|
if (!this.institutionDomains[id] || !this.institutionDomains[id][domain])
|
|
return
|
|
this.institutionDomains[id][domain] = Object.assign(
|
|
{},
|
|
this.institutionDomains[id][domain],
|
|
options
|
|
)
|
|
}
|
|
|
|
addAffiliation(userId, email, entitlement, confirmedAt) {
|
|
let newAffiliation = true
|
|
const institution = {}
|
|
if (!email) return
|
|
if (!this.affiliations[userId]) this.affiliations[userId] = []
|
|
|
|
if (
|
|
this.affiliations[userId].find(affiliationData => {
|
|
return affiliationData.email === email
|
|
})
|
|
)
|
|
newAffiliation = false
|
|
|
|
if (newAffiliation) {
|
|
const domain = email.split('@').pop()
|
|
|
|
if (this.blocklistedDomains.indexOf(domain.replace('.com', '')) !== -1) {
|
|
return
|
|
}
|
|
|
|
if (this.allInstitutionDomains.has(domain)) {
|
|
for (const [institutionId, domainData] of Object.entries(
|
|
this.institutionDomains
|
|
)) {
|
|
if (domainData[domain]) {
|
|
institution.id = institutionId
|
|
}
|
|
}
|
|
}
|
|
|
|
if (institution.id) {
|
|
this.affiliations[userId].push({ email, institution })
|
|
}
|
|
}
|
|
|
|
if (entitlement !== undefined) {
|
|
this.affiliations[userId].forEach(affiliation => {
|
|
if (affiliation.email === email) {
|
|
affiliation.cached_entitlement = entitlement
|
|
}
|
|
})
|
|
}
|
|
|
|
if (confirmedAt) {
|
|
this.affiliations[userId].forEach(affiliation => {
|
|
if (affiliation.email === email) {
|
|
if (!affiliation.cached_confirmed_at) {
|
|
affiliation.cached_confirmed_at = confirmedAt
|
|
}
|
|
affiliation.cached_reconfirmed_at = confirmedAt
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
setDocExported(token, info) {
|
|
this.doc_exported[token] = info
|
|
}
|
|
|
|
setTemplates(templates) {
|
|
this.templates = templates
|
|
}
|
|
|
|
applyRoutes() {
|
|
this.app.get('/api/v1/overleaf/users/:v1_user_id/plan_code', (req, res) => {
|
|
const user = this.users[req.params.v1_user_id]
|
|
if (user) {
|
|
res.json(user)
|
|
} else {
|
|
res.sendStatus(404)
|
|
}
|
|
})
|
|
|
|
this.app.get(
|
|
'/api/v1/overleaf/users/:v1_user_id/subscriptions',
|
|
(req, res) => {
|
|
const user = this.users[req.params.v1_user_id]
|
|
if (user && user.subscription) {
|
|
res.json(user.subscription)
|
|
} else {
|
|
res.sendStatus(404)
|
|
}
|
|
}
|
|
)
|
|
|
|
this.app.get(
|
|
'/api/v1/overleaf/users/:v1_user_id/subscription_status',
|
|
(req, res) => {
|
|
const user = this.users[req.params.v1_user_id]
|
|
if (user && user.subscription_status) {
|
|
res.json(user.subscription_status)
|
|
} else {
|
|
res.sendStatus(404)
|
|
}
|
|
}
|
|
)
|
|
|
|
this.app.delete(
|
|
'/api/v1/overleaf/users/:v1_user_id/subscription',
|
|
(req, res) => {
|
|
const user = this.users[req.params.v1_user_id]
|
|
if (user) {
|
|
user.canceled = true
|
|
res.sendStatus(200)
|
|
} else {
|
|
res.sendStatus(404)
|
|
}
|
|
}
|
|
)
|
|
|
|
this.app.post('/api/v1/overleaf/users/:v1_user_id/sync', (req, res) => {
|
|
this.syncUserFeatures(req.params.v1_user_id)
|
|
res.sendStatus(200)
|
|
})
|
|
|
|
this.app.post('/api/v1/overleaf/exports', (req, res) => {
|
|
this.exportParams = Object.assign({}, req.body)
|
|
res.json({ exportId: this.exportId })
|
|
})
|
|
|
|
this.app.get('/api/v2/users/:userId/affiliations', (req, res) => {
|
|
if (!this.affiliations[req.params.userId]) return res.json([])
|
|
const affiliations = this.affiliations[req.params.userId].map(
|
|
affiliation => {
|
|
const institutionId = affiliation.institution.id
|
|
const domain = affiliation.email.split('@').pop()
|
|
const domainData =
|
|
this.institutionDomains[institutionId][domain] || {}
|
|
const institutionData = this.institutions[institutionId] || {}
|
|
|
|
affiliation.institution = {
|
|
id: institutionId,
|
|
name: institutionData.name,
|
|
commonsAccount: institutionData.commonsAccount,
|
|
isUniversity: !institutionData.institution,
|
|
ssoBeta: institutionData.sso_beta || false,
|
|
ssoEnabled: institutionData.sso_enabled || false,
|
|
maxConfirmationMonths:
|
|
institutionData.maxConfirmationMonths || null,
|
|
}
|
|
|
|
affiliation.institution.confirmed = !!domainData.confirmed
|
|
|
|
affiliation.licence = 'free'
|
|
if (
|
|
institutionData.commonsAccount &&
|
|
(!institutionData.sso_enabled ||
|
|
(institutionData.sso_enabled &&
|
|
affiliation.cached_entitlement === true))
|
|
) {
|
|
affiliation.licence = 'pro_plus'
|
|
}
|
|
|
|
if (
|
|
institutionData.maxConfirmationMonths &&
|
|
affiliation.cached_reconfirmed_at
|
|
) {
|
|
const lastDayToReconfirm = moment(
|
|
affiliation.cached_reconfirmed_at
|
|
).add(institutionData.maxConfirmationMonths, 'months')
|
|
affiliation.last_day_to_reconfirm = lastDayToReconfirm.toDate()
|
|
affiliation.past_reconfirm_date = lastDayToReconfirm.isBefore()
|
|
}
|
|
|
|
return affiliation
|
|
}
|
|
)
|
|
res.json(affiliations)
|
|
})
|
|
|
|
this.app.post('/api/v2/users/:userId/affiliations', (req, res) => {
|
|
this.addAffiliation(
|
|
req.params.userId,
|
|
req.body.email,
|
|
req.body.entitlement,
|
|
req.body.confirmedAt
|
|
)
|
|
res.sendStatus(201)
|
|
})
|
|
|
|
this.app.delete('/api/v2/users/:userId/affiliations', (req, res) => {
|
|
res.sendStatus(201)
|
|
})
|
|
|
|
this.app.delete('/api/v2/users/:userId/affiliations/:email', (req, res) => {
|
|
res.sendStatus(204)
|
|
})
|
|
|
|
this.app.post(
|
|
'/api/v2/institutions/reconfirmation_lapsed_processed',
|
|
(req, res) => {
|
|
res.sendStatus(200)
|
|
}
|
|
)
|
|
|
|
this.app.get(
|
|
'/api/v2/institutions/need_reconfirmation_lapsed_processed',
|
|
(req, res) => {
|
|
const usersWithAffiliations = []
|
|
Object.keys(this.affiliations).forEach(userId => {
|
|
if (this.affiliations[userId].length > 0) {
|
|
usersWithAffiliations.push(userId)
|
|
}
|
|
})
|
|
res.json({ data: { users: usersWithAffiliations } })
|
|
}
|
|
)
|
|
|
|
this.app.get('/api/v2/brands/:slug', (req, res) => {
|
|
let brand
|
|
if ((brand = this.brands[req.params.slug])) {
|
|
res.json(brand)
|
|
} else {
|
|
res.sendStatus(404)
|
|
}
|
|
})
|
|
|
|
this.app.get('/universities/list', (req, res) => {
|
|
const response = []
|
|
|
|
const university1 = {
|
|
id: 1337,
|
|
name: 'Institution 1337',
|
|
country_code: 'en',
|
|
departments: [],
|
|
}
|
|
|
|
const university2 = {
|
|
id: 243,
|
|
name: 'Institution 243',
|
|
country_code: 'en',
|
|
departments: [],
|
|
}
|
|
|
|
if (req.query.country_code === 'en') {
|
|
response.push(university1)
|
|
}
|
|
if (req.query.search === 'Institution') {
|
|
response.push(university1)
|
|
if (req.query.max_results !== '1') {
|
|
response.push(university2)
|
|
}
|
|
}
|
|
res.json(response)
|
|
})
|
|
|
|
this.app.get('/universities/list/:id', (req, res) =>
|
|
res.json({
|
|
id: parseInt(req.params.id),
|
|
name: `Institution ${req.params.id}`,
|
|
})
|
|
)
|
|
|
|
this.app.get('/university/domains', (req, res) => {
|
|
if (req.query.hostname === 'overleaf.com') {
|
|
res.json([
|
|
{
|
|
id: 42,
|
|
hostname: 'overleaf.com',
|
|
department: 'Overleaf',
|
|
confirmed: true,
|
|
university: {
|
|
id: 1337,
|
|
name: 'Institution 1337',
|
|
departments: [],
|
|
ssoBeta: false,
|
|
ssoEnabled: false,
|
|
},
|
|
},
|
|
])
|
|
} else {
|
|
res.json([])
|
|
}
|
|
})
|
|
|
|
this.app.put('/api/v1/overleaf/users/:id/email', (req, res) => {
|
|
const { email } = req.body && req.body.user
|
|
if (this.existingEmails.includes(email)) {
|
|
res.sendStatus(409)
|
|
} else {
|
|
this.updateEmail(parseInt(req.params.id), email)
|
|
res.sendStatus(200)
|
|
}
|
|
})
|
|
|
|
this.app.post('/api/v1/overleaf/login', (req, res) => {
|
|
for (const id in this.users) {
|
|
const user = this.users[id]
|
|
if (
|
|
user &&
|
|
user.email === req.body.email &&
|
|
user.password === req.body.password
|
|
) {
|
|
return res.json({
|
|
email: user.email,
|
|
valid: true,
|
|
user_profile: user.profile,
|
|
})
|
|
}
|
|
}
|
|
res.status(403).json({
|
|
email: req.body.email,
|
|
valid: false,
|
|
})
|
|
})
|
|
|
|
this.app.get('/api/v2/partners/:partner/conversions/:id', (req, res) => {
|
|
const partner = this.validation_clients[req.params.partner]
|
|
const conversion =
|
|
partner && partner.conversions && partner.conversions[req.params.id]
|
|
|
|
if (conversion) {
|
|
res.status(200).json({
|
|
input_file_uri: conversion,
|
|
brand_variation_id: partner.brand_variation_id,
|
|
})
|
|
} else {
|
|
res.status(404).json({})
|
|
}
|
|
})
|
|
|
|
this.app.get('/api/v2/brand_variations/:id', (req, res) => {
|
|
const variation = this.brand_variations[req.params.id]
|
|
if (variation) {
|
|
res.status(200).json(variation)
|
|
} else {
|
|
res.status(404).json({})
|
|
}
|
|
})
|
|
|
|
this.app.get('/api/v1/overleaf/docs/:token/is_published', (req, res) => {
|
|
return res.json({ allow: true })
|
|
})
|
|
|
|
this.app.get(
|
|
'/api/v1/overleaf/users/:user_id/docs/:token/info',
|
|
(req, res) => {
|
|
const info = this.getDocInfo(req.params.token) || {
|
|
exists: false,
|
|
exported: false,
|
|
}
|
|
res.json(info)
|
|
}
|
|
)
|
|
|
|
this.app.get('/api/v1/overleaf/docs/:token/info', (req, res) => {
|
|
const info = this.getDocInfo(req.params.token) || {
|
|
exists: false,
|
|
exported: false,
|
|
}
|
|
res.json(info)
|
|
})
|
|
|
|
this.app.get(
|
|
'/api/v1/overleaf/docs/read_token/:token/exists',
|
|
(req, res) => {
|
|
res.json({ exists: false })
|
|
}
|
|
)
|
|
|
|
this.app.get('/api/v2/templates/:templateId', (req, res) => {
|
|
const template = this.templates[req.params.templateId]
|
|
if (!template) {
|
|
return res.sendStatus(404)
|
|
}
|
|
res.json(template)
|
|
})
|
|
}
|
|
}
|
|
|
|
module.exports = MockV1Api
|
|
|
|
// type hint for the inherited `instance` method
|
|
/**
|
|
* @function instance
|
|
* @memberOf MockV1Api
|
|
* @static
|
|
* @returns {MockV1Api}
|
|
*/
|