• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / blurts-server / #11898

pending completion
#11898

push

circleci

web-flow
Merge pull request #2770 from mozilla/license

Add license headers in source files

282 of 1138 branches covered (24.78%)

Branch coverage included in aggregate %.

959 of 3049 relevant lines covered (31.45%)

2.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/db/tables/subscribers.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
import { destroyOAuthToken } from '../../utils/fxa.js'
6
import Knex from 'knex'
7
import knexConfig from '../knexfile.js'
8
import AppConstants from '../../app-constants.js'
9
import mozlog from '../../utils/log.js'
10
const knex = Knex(knexConfig)
×
11
const { DELETE_UNVERIFIED_SUBSCRIBERS_TIMER } = AppConstants
×
12
const log = mozlog('DB.subscribers')
×
13

14
async function getSubscriberByToken (token) {
15
  const res = await knex('subscribers')
×
16
    .where('primary_verification_token', '=', token)
17

18
  return res[0]
×
19
}
20

21
async function getSubscriberByTokenAndHash (token, emailSha1) {
22
  const res = await knex.table('subscribers')
×
23
    .first()
24
    .where({
25
      primary_verification_token: token,
26
      primary_sha1: emailSha1
27
    })
28
  return res
×
29
}
30

31
async function getSubscribersByHashes (hashes) {
32
  return await knex('subscribers').whereIn('primary_sha1', hashes).andWhere('primary_verified', '=', true)
×
33
}
34

35
async function getSubscriberById (id) {
36
  const [subscriber] = await knex('subscribers').where({
×
37
    id
38
  })
39
  const subscriberAndEmails = await joinEmailAddressesToSubscriber(subscriber)
×
40
  return subscriberAndEmails
×
41
}
42

43
async function getSubscriberByFxaUid (uid) {
44
  const [subscriber] = await knex('subscribers').where({
×
45
    fxa_uid: uid
46
  })
47
  const subscriberAndEmails = await joinEmailAddressesToSubscriber(subscriber)
×
48
  return subscriberAndEmails
×
49
}
50

51
async function getSubscriberByEmail (email) {
52
  const [subscriber] = await knex('subscribers').where({
×
53
    primary_email: email,
54
    primary_verified: true
55
  })
56
  const subscriberAndEmails = await joinEmailAddressesToSubscriber(subscriber)
×
57
  return subscriberAndEmails
×
58
}
59
/**
60
   * Update fxa_refresh_token and fxa_profile_json for subscriber
61
   *
62
   * @param {object} subscriber knex object in DB
63
   * @param {string} fxaAccessToken from Firefox Account Oauth
64
   * @param {string} fxaRefreshToken from Firefox Account Oauth
65
   * @param {string} fxaProfileData from Firefox Account
66
   * @returns {object} updated subscriber knex object in DB
67
   */
68
async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) {
69
  const fxaUID = JSON.parse(fxaProfileData).uid
×
70
  const updated = await knex('subscribers')
×
71
    .where('id', '=', subscriber.id)
72
    .update({
73
      fxa_uid: fxaUID,
74
      fxa_access_token: fxaAccessToken,
75
      fxa_refresh_token: fxaRefreshToken,
76
      fxa_profile_json: fxaProfileData
77
    })
78
    .returning('*')
79
  const updatedSubscriber = Array.isArray(updated) ? updated[0] : null
×
80
  if (updatedSubscriber && subscriber.fxa_refresh_token) {
×
81
    destroyOAuthToken({ refresh_token: subscriber.fxa_refresh_token })
×
82
  }
83
  return updatedSubscriber
×
84
}
85

86
/**
87
   * Update fxa_profile_json for subscriber
88
   * @param {object} subscriber knex object in DB
89
   * @param {string} fxaProfileData from Firefox Account
90
   * @returns {object} updated subscriber knex object in DB
91
   */
92
async function updateFxAProfileData (subscriber, fxaProfileData) {
93
  await knex('subscribers').where('id', subscriber.id)
×
94
    .update({
95
      fxa_profile_json: fxaProfileData
96
    })
97
  return getSubscriberById(subscriber.id)
×
98
}
99

100
/**
101
   * Remove fxa tokens and profile data for subscriber
102
   *
103
   * @param {object} subscriber knex object in DB
104
   * @returns {object} updated subscriber knex object in DB
105
   */
106
async function removeFxAData (subscriber) {
107
  log.debug('removeFxAData', subscriber)
×
108
  const updated = await knex('subscribers')
×
109
    .where('id', '=', subscriber.id)
110
    .update({
111
      fxa_access_token: null,
112
      fxa_refresh_token: null,
113
      fxa_profile_json: null
114
    })
115
    .returning('*')
116
  const updatedSubscriber = Array.isArray(updated) ? updated[0] : null
×
117
  if (updatedSubscriber && subscriber.fxa_refresh_token) {
×
118
    await destroyOAuthToken({ refresh_token: subscriber.fxa_refresh_token })
×
119
  }
120
  if (updatedSubscriber && subscriber.fxa_access_token) {
×
121
    await destroyOAuthToken({ token: subscriber.fxa_access_token })
×
122
  }
123
  return updatedSubscriber
×
124
}
125

126
async function setBreachesLastShownNow (subscriber) {
127
  // TODO: turn 2 db queries into a single query (also see #942)
128
  const nowDateTime = new Date()
×
129
  const nowTimeStamp = nowDateTime.toISOString()
×
130
  await knex('subscribers')
×
131
    .where('id', '=', subscriber.id)
132
    .update({
133
      breaches_last_shown: nowTimeStamp
134
    })
135
  return getSubscriberByEmail(subscriber.primary_email)
×
136
}
137

138
async function setAllEmailsToPrimary (subscriber, allEmailsToPrimary) {
139
  const updated = await knex('subscribers')
×
140
    .where('id', subscriber.id)
141
    .update({
142
      all_emails_to_primary: allEmailsToPrimary
143
    })
144
    .returning('*')
145
  const updatedSubscriber = Array.isArray(updated) ? updated[0] : null
×
146
  return updatedSubscriber
×
147
}
148

149
/**
150
 * OBSOLETE, preserved for backwards compatibility
151
 * TODO: Delete after monitor v2, only use setBreachResolution for v2
152
 * @param {*} options {user, updatedResolvedBreaches}
153
 * @returns subscriber
154
 */
155
async function setBreachesResolved (options) {
156
  const { user, updatedResolvedBreaches } = options
×
157
  await knex('subscribers')
×
158
    .where('id', user.id)
159
    .update({
160
      breaches_resolved: updatedResolvedBreaches
161
    })
162
  return getSubscriberByEmail(user.primary_email)
×
163
}
164

165
/**
166
 * Set "breach_resolution" column with the latest breach resolution object
167
 * This column is meant to replace "breaches_resolved" column, which was used
168
 * for v1.
169
 * @param {object} user user object that contains the id of a user
170
 * @param {object} updatedBreachesResolution {email_id: [{recencyIndex: {allBreachTypes: [BreachType], resolved: [BreachType], isInProgress: bool}}, {}...]}
171
 * @returns subscriber
172
 */
173
async function setBreachResolution (user, updatedBreachesResolution) {
174
  await knex('subscribers')
×
175
    .where('id', user.id)
176
    .update({
177
      breach_resolution: updatedBreachesResolution
178
    })
179
  return getSubscriberByEmail(user.primary_email)
×
180
}
181

182
async function setWaitlistsJoined (options) {
183
  const { user, updatedWaitlistsJoined } = options
×
184
  await knex('subscribers')
×
185
    .where('id', user.id)
186
    .update({
187
      waitlists_joined: updatedWaitlistsJoined
188
    })
189
  return getSubscriberByEmail(user.primary_email)
×
190
}
191

192
async function removeSubscriber (subscriber) {
193
  await knex('email_addresses').where({ subscriber_id: subscriber.id }).del()
×
194
  await knex('subscribers').where({ id: subscriber.id }).del()
×
195
}
196

197
async function removeSubscriberByToken (token, emailSha1) {
198
  const subscriber = await getSubscriberByTokenAndHash(token, emailSha1)
×
199
  if (!subscriber) {
×
200
    return false
×
201
  }
202
  await knex('subscribers')
×
203
    .where({
204
      primary_verification_token: subscriber.primary_verification_token,
205
      primary_sha1: subscriber.primary_sha1
206
    })
207
    .del()
208
  return subscriber
×
209
}
210

211
async function deleteUnverifiedSubscribers () {
212
  const expiredDateTime = new Date(Date.now() - DELETE_UNVERIFIED_SUBSCRIBERS_TIMER * 1000)
×
213
  const expiredTimeStamp = expiredDateTime.toISOString()
×
214
  const numDeleted = await knex('subscribers')
×
215
    .where('primary_verified', false)
216
    .andWhere('created_at', '<', expiredTimeStamp)
217
    .del()
218
  log.info('deleteUnverifiedSubscribers', { msg: `Deleted ${numDeleted} rows.` })
×
219
}
220

221
async function deleteSubscriberByFxAUID (fxaUID) {
222
  await knex('subscribers').where('fxa_uid', fxaUID).del()
×
223
}
224

225
async function updateBreachStats (id, stats) {
226
  await knex('subscribers')
×
227
    .where('id', id)
228
    .update({
229
      breach_stats: stats
230
    })
231
}
232

233
async function updateMonthlyEmailTimestamp (email) {
234
  const res = await knex('subscribers').update({ monthly_email_at: 'now' })
×
235
    .where('primary_email', email)
236
    .returning('monthly_email_at')
237

238
  return res
×
239
}
240

241
async function updateMonthlyEmailOptout (token) {
242
  await knex('subscribers').update('monthly_email_optout', true).where('primary_verification_token', token)
×
243
}
244

245
function getSubscribersWithUnresolvedBreachesQuery () {
246
  return knex('subscribers')
×
247
    .whereRaw('monthly_email_optout IS NOT TRUE')
248
    .whereRaw("greatest(created_at, monthly_email_at) < (now() - interval '30 days')")
249
    .whereRaw("(breach_stats #>> '{numBreaches, numUnresolved}')::int > 0")
250
}
251

252
async function getSubscribersWithUnresolvedBreaches (limit = 0) {
×
253
  let query = getSubscribersWithUnresolvedBreachesQuery()
×
254
    .select('primary_email', 'primary_verification_token', 'breach_stats', 'signup_language')
255
  if (limit) {
×
256
    query = query.limit(limit).orderBy('created_at')
×
257
  }
258
  return await query
×
259
}
260

261
async function getSubscribersWithUnresolvedBreachesCount () {
262
  const query = getSubscribersWithUnresolvedBreachesQuery()
×
263
  const count = parseInt((await query.count({ count: '*' }))[0].count)
×
264
  return count
×
265
}
266

267
/** Private **/
268

269
async function joinEmailAddressesToSubscriber (subscriber) {
270
  if (subscriber) {
×
271
    const emailAddressRecords = await knex('email_addresses').where({
×
272
      subscriber_id: subscriber.id
273
    })
274
    subscriber.email_addresses = emailAddressRecords.map(
×
275
      emailAddress => ({ id: emailAddress.id, email: emailAddress.email })
×
276
    )
277
  }
278
  return subscriber
×
279
}
280
export {
281
  getSubscriberByToken,
282
  getSubscribersByHashes,
283
  getSubscriberByTokenAndHash,
284
  getSubscriberById,
285
  getSubscriberByFxaUid,
286
  getSubscriberByEmail,
287
  getSubscribersWithUnresolvedBreachesQuery,
288
  getSubscribersWithUnresolvedBreaches,
289
  getSubscribersWithUnresolvedBreachesCount,
290
  updateFxAData,
291
  removeFxAData,
292
  updateFxAProfileData,
293
  setBreachesLastShownNow,
294
  setAllEmailsToPrimary,
295
  setBreachesResolved,
296
  setBreachResolution,
297
  setWaitlistsJoined,
298
  updateBreachStats,
299
  updateMonthlyEmailTimestamp,
300
  updateMonthlyEmailOptout,
301
  removeSubscriber,
302
  removeSubscriberByToken,
303
  deleteUnverifiedSubscribers,
304
  deleteSubscriberByFxAUID
305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc