Merge pull request #25190 from overleaf/mj-survey-signup-limits

[web] Add options to limit survey exposure based on signup date

GitOrigin-RevId: 5719997339b5040d5cc42ffe7bee6d7b66bff12d
This commit is contained in:
Mathias Jakobsen
2025-05-02 09:35:34 +01:00
committed by Copybot
parent 21eca03abb
commit 18f2aa5a0c
4 changed files with 70 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ import crypto from 'node:crypto'
import SurveyCache from './SurveyCache.mjs'
import SubscriptionLocator from '../Subscription/SubscriptionLocator.js'
import { callbackify } from '@overleaf/promise-utils'
import UserGetter from '../User/UserGetter.js'
/**
* @import { Survey } from '../../../../types/project/dashboard/survey'
@@ -33,6 +34,25 @@ async function getSurvey(userId) {
return
}
const { earliestSignupDate, latestSignupDate } = survey.options || {}
if (earliestSignupDate || latestSignupDate) {
const user = await UserGetter.promises.getUser(userId, { signUpDate: 1 })
if (!user) {
return
}
const { signUpDate } = user
if (latestSignupDate) {
// Make the check inclusive
latestSignupDate.setHours(23, 59, 59, 999)
if (signUpDate > latestSignupDate) {
return
}
}
if (earliestSignupDate && signUpDate < earliestSignupDate) {
return
}
}
return { name, preText, linkText, url }
}
}

View File

@@ -10,6 +10,7 @@ async function getSurvey() {
}
async function updateSurvey({ name, preText, linkText, url, options }) {
validateOptions(options)
let survey = await getSurvey()
if (!survey) {
survey = new Survey()
@@ -23,6 +24,41 @@ async function updateSurvey({ name, preText, linkText, url, options }) {
return survey
}
function validateOptions(options) {
if (!options) {
return
}
if (typeof options !== 'object') {
throw new Error('options must be an object')
}
const { earliestSignupDate, latestSignupDate } = options
const earliestDate = parseDate(earliestSignupDate)
const latestDate = parseDate(latestSignupDate)
if (earliestDate && latestDate) {
if (earliestDate > latestDate) {
throw new Error('earliestSignupDate must be before latestSignupDate')
}
}
}
function parseDate(date) {
if (date) {
if (typeof date !== 'string') {
throw new Error('Date must be a string')
}
if (date.match(/^\d{4}-\d{2}-\d{2}$/) === null) {
throw new Error('Date must be in YYYY-MM-DD format')
}
const asDate = new Date(date)
if (isNaN(asDate.getTime())) {
throw new Error('Date must be a valid date')
}
return asDate
}
return null
}
async function deleteSurvey() {
const survey = await getSurvey()
if (survey) {

View File

@@ -36,6 +36,12 @@ const SurveySchema = new Schema(
type: Boolean,
default: false,
},
earliestSignupDate: {
type: Date,
},
latestSignupDate: {
type: Date,
},
rolloutPercentage: {
type: Number,
default: 100,

View File

@@ -36,6 +36,7 @@ class User {
this.request = request.defaults({
jar: this.jar,
})
this.signUpDate = options.signUpDate ?? new Date()
}
getSession(options, callback) {
@@ -425,7 +426,13 @@ class User {
UserModel.findOneAndUpdate(
filter,
{ $set: { hashedPassword, emails: this.emails } },
{
$set: {
hashedPassword,
emails: this.emails,
signUpDate: this.signUpDate,
},
},
options
)
.then(user => {