@wepublish/api-db-mongodb
Version:
We.publish Database adapter for mongoDB
1,028 lines (928 loc) • 32.6 kB
text/typescript
import {Db} from 'mongodb'
import {CollectionName, DBInvoice, DBPaymentMethod, DBUser} from './db/schema'
import {
ArticleBlock,
BlockType,
CreateSettingArgs,
PageBlock,
PaymentProviderCustomer,
SettingName,
Subscription,
SubscriptionDeactivationReason
} from '@wepublish/api'
import {slugify} from './utility'
export interface Migration {
readonly version: number
migrate(adapter: Db, locale: string): Promise<void>
}
const SessionDocumentTTL = 60 * 60 * 24 // 24h
export const Migrations: Migration[] = [
{
version: 0,
async migrate(db, locale) {
const migrations = await db.createCollection(CollectionName.Migrations, {strict: true})
await migrations.createIndex({name: 1}, {unique: true})
const users = await db.createCollection(CollectionName.Users, {
strict: true
})
await users.createIndex({email: 1}, {unique: true})
const sessions = await db.createCollection(CollectionName.Sessions, {
strict: true
})
await sessions.createIndex({userID: 1})
await sessions.createIndex({token: 1}, {unique: true})
await sessions.createIndex({expiresAt: 1}, {expireAfterSeconds: SessionDocumentTTL})
const navigations = await db.createCollection(CollectionName.Navigations, {strict: true})
await navigations.createIndex({createdAt: -1})
await navigations.createIndex({modifiedAt: -1})
await navigations.createIndex({name: 1})
await navigations.createIndex({key: 1}, {unique: true})
const authors = await db.createCollection(CollectionName.Authors, {strict: true})
await authors.createIndex({createdAt: -1})
await authors.createIndex({modifiedAt: -1})
await authors.createIndex({name: 1})
await authors.createIndex({slug: 1}, {unique: true})
const images = await db.createCollection(CollectionName.Images, {strict: true})
await images.createIndex({createdAt: -1})
await images.createIndex({modifiedAt: -1})
await images.createIndex({title: 1})
await images.createIndex({tags: 1}, {collation: {locale, strength: 2}})
const articles = await db.createCollection(CollectionName.Articles, {
strict: true
})
await articles.createIndex({createdAt: -1})
await articles.createIndex({modifiedAt: -1})
await articles.createIndex({'published.publishedAt': -1})
await articles.createIndex({'published.updatedAt': -1})
await articles.createIndex({'pending.publishAt': -1})
await articles.createIndex({'draft.tags': 1}, {collation: {locale, strength: 2}})
await articles.createIndex({'pending.tags': 1}, {collation: {locale, strength: 2}})
await articles.createIndex({'published.tags': 1}, {collation: {locale, strength: 2}})
await db.createCollection(CollectionName.ArticlesHistory, {
strict: true
})
const pages = await db.createCollection(CollectionName.Pages, {
strict: true
})
await pages.createIndex({createdAt: -1})
await pages.createIndex({modifiedAt: -1})
await pages.createIndex({'published.publishedAt': -1})
await pages.createIndex({'published.updatedAt': -1})
await pages.createIndex({'pending.publishAt': -1})
await pages.createIndex({'draft.tags': 1}, {collation: {locale, strength: 2}})
await pages.createIndex({'pending.tags': 1}, {collation: {locale, strength: 2}})
await pages.createIndex({'published.tags': 1}, {collation: {locale, strength: 2}})
await db.createCollection(CollectionName.PagesHistory, {
strict: true
})
}
},
{
// Fix incorrect migration index. Add user roles, Add name and roleIDs to users.
version: 1,
async migrate(db, locale) {
const migrations = db.collection(CollectionName.Migrations)
await migrations.dropIndex('name_1')
await migrations.createIndex({version: 1}, {unique: true})
const userRoles = await db.createCollection(CollectionName.UserRoles, {
strict: true
})
await userRoles.createIndex({name: 1}, {unique: true})
await userRoles.insertMany([
{
_id: 'admin',
createdAt: new Date(),
modifiedAt: new Date(),
systemRole: true,
name: 'Admin',
description: 'Administrator Role',
permissionIDs: []
},
{
_id: 'editor',
createdAt: new Date(),
modifiedAt: new Date(),
systemRole: true,
name: 'Editor',
description: 'Editor Role',
permissionIDs: []
}
])
const user = db.collection(CollectionName.Users)
await user.updateMany({}, [
{
$set: {
name: '$email',
roleIDs: ['admin']
}
}
])
}
},
{
// Add peering and token collections and migrate ArticleTeaserGridBlock to TeaserGridBlock.
version: 2,
async migrate(db) {
const userRoles = db.collection(CollectionName.UserRoles)
await userRoles.insertOne({
_id: 'peer',
createdAt: new Date(),
modifiedAt: new Date(),
systemRole: true,
name: 'Peer',
description: 'Peer Role',
permissionIDs: []
})
await db.createCollection(CollectionName.PeerProfiles, {strict: true})
const peers = await db.createCollection(CollectionName.Peers, {strict: true})
await peers.createIndex({slug: 1}, {unique: true})
const tokens = await db.createCollection(CollectionName.Tokens, {
strict: true
})
await tokens.createIndex({name: 1}, {unique: true})
const filter = {
$or: [
{'draft.blocks.type': 'articleTeaserGrid'},
{'published.blocks.type': 'articleTeaserGrid'},
{'pending.blocks.type': 'articleTeaserGrid'}
]
}
function mapArticleTeaserGridBlock(block: any) {
if (block.type === 'articleTeaserGrid') {
return {
type: 'teaserGrid',
teasers: block.teasers.map((teaser: any) =>
teaser
? {
type: 'article',
style: 'default',
articleID: teaser.articleID
}
: null
),
numColumns: block.numColumns
}
}
return block
}
const articles = db.collection(CollectionName.Articles)
const migrationArticles = await articles.find(filter).toArray()
for (const article of migrationArticles) {
if (article.draft) {
article.draft.blocks = article.draft.blocks.map(mapArticleTeaserGridBlock)
}
if (article.pending) {
article.pending.blocks = article.pending.blocks.map(mapArticleTeaserGridBlock)
}
if (article.published) {
article.published.blocks = article.published.blocks.map(mapArticleTeaserGridBlock)
}
await articles.findOneAndReplace({_id: article._id}, article)
}
const pages = db.collection(CollectionName.Pages)
const migrationPages = await pages.find(filter).toArray()
for (const page of migrationPages) {
if (page.draft) {
page.draft.blocks = page.draft.blocks.map(mapArticleTeaserGridBlock)
}
if (page.pending) {
page.pending.blocks = page.pending.blocks.map(mapArticleTeaserGridBlock)
}
if (page.published) {
page.published.blocks = page.published.blocks.map(mapArticleTeaserGridBlock)
}
await pages.findOneAndReplace({_id: page._id}, page)
}
}
},
{
// Add peering and token collections and migrate ArticleTeaserGridBlock to TeaserGridBlock.
version: 3,
async migrate(db) {
const articles = db.collection(CollectionName.Articles)
const migrationArticles = await articles.find().toArray()
for (const article of migrationArticles) {
if (article.draft) {
article.draft.properties = []
}
if (article.pending) {
article.pending.properties = []
}
if (article.published) {
article.published.properties = []
}
await articles.findOneAndReplace({_id: article._id}, article)
}
const pages = db.collection(CollectionName.Pages)
const migrationPages = await pages.find().toArray()
for (const page of migrationPages) {
if (page.draft) {
page.draft.properties = []
}
if (page.pending) {
page.pending.properties = []
}
if (page.published) {
page.published.properties = []
}
await pages.findOneAndReplace({_id: page._id}, page)
}
}
},
{
// Add RTE to page break block if not exists in articles and pages
version: 4,
async migrate(db) {
await db.collection(CollectionName.Articles).updateMany(
{'draft.blocks': {$elemMatch: {type: 'linkPageBreak', richText: {$exists: false}}}},
{
$set: {
'draft.blocks.$[elem].richText': [{children: [{text: ''}], type: 'paragraph'}]
}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Articles).updateMany(
{
'published.blocks': {
$elemMatch: {type: 'linkPageBreak', richText: {$exists: false}}
}
},
{
$set: {'published.blocks.$[elem].richText': [{children: [{text: ''}], type: 'paragraph'}]}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Articles).updateMany(
{
'pending.blocks': {$elemMatch: {type: 'linkPageBreak', richText: {$exists: false}}}
},
{$set: {'pending.blocks.$[elem].richText': [{children: [{text: ''}], type: 'paragraph'}]}},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
// Add RTE to page break block if not exists in pages
await db.collection(CollectionName.Pages).updateMany(
{'draft.blocks': {$elemMatch: {type: 'linkPageBreak', richText: {$exists: false}}}},
{
$set: {
'draft.blocks.$[elem].richText': [{children: [{text: ''}], type: 'paragraph'}]
}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Pages).updateMany(
{
'published.blocks': {
$elemMatch: {type: 'linkPageBreak', richText: {$exists: false}}
}
},
{
$set: {'published.blocks.$[elem].richText': [{children: [{text: ''}], type: 'paragraph'}]}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Pages).updateMany(
{'pending.blocks': {$elemMatch: {type: 'linkPageBreak', richText: {$exists: false}}}},
{
$set: {'pending.blocks.$[elem].richText': [{children: [{text: ''}], type: 'paragraph'}]}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
}
},
{
// Add hideButton false to pageBreakBlocks
version: 5,
async migrate(db) {
await db.collection(CollectionName.Articles).updateMany(
{'draft.blocks': {$elemMatch: {type: 'linkPageBreak', hideButton: {$exists: false}}}},
{
$set: {
'draft.blocks.$[elem].hideButton': false
}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Articles).updateMany(
{
'published.blocks': {
$elemMatch: {type: 'linkPageBreak', hideButton: {$exists: false}}
}
},
{
$set: {'published.blocks.$[elem].hideButton': false}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Articles).updateMany(
{
'pending.blocks': {$elemMatch: {type: 'linkPageBreak', hideButton: {$exists: false}}}
},
{$set: {'pending.blocks.$[elem].hideButton': false}},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
// Add RTE to page break block if not exists in pages
await db.collection(CollectionName.Pages).updateMany(
{'draft.blocks': {$elemMatch: {type: 'linkPageBreak', hideButton: {$exists: false}}}},
{
$set: {
'draft.blocks.$[elem].hideButton': false
}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Pages).updateMany(
{
'published.blocks': {
$elemMatch: {type: 'linkPageBreak', hideButton: {$exists: false}}
}
},
{
$set: {'published.blocks.$[elem].hideButton': false}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
await db.collection(CollectionName.Pages).updateMany(
{'pending.blocks': {$elemMatch: {type: 'linkPageBreak', hideButton: {$exists: false}}}},
{
$set: {'pending.blocks.$[elem].hideButton': false}
},
{arrayFilters: [{'elem.type': 'linkPageBreak'}]}
)
}
},
{
// Add hide author property to article.
version: 6,
async migrate(db) {
await db.collection(CollectionName.Articles).updateMany(
{
pending: {$ne: null},
'pending.hideAuthor': {$exists: false}
},
{$set: {'pending.hideAuthor': false}}
)
await db.collection(CollectionName.Articles).updateMany(
{
published: {$ne: null},
'published.hideAuthor': {$exists: false}
},
{$set: {'published.hideAuthor': false}}
)
await db.collection(CollectionName.Articles).updateMany(
{
draft: {$ne: null},
'draft.hideAuthor': {$exists: false}
},
{$set: {'draft.hideAuthor': false}}
)
}
},
{
// Add Call To Action Details to Peer Profile.
version: 7,
async migrate(db) {
await db.collection(CollectionName.PeerProfiles).updateMany(
{
callToActionURL: {$exists: false},
callToActionText: {$exists: false}
},
{
$set: {
callToActionURL: '',
callToActionText: []
}
}
)
}
},
{
// Add social media metatags to article.
version: 8,
async migrate(db) {
await db.collection(CollectionName.Articles).updateMany(
{
pending: {$ne: null},
'pending.socialMediaAuthorIDs': {$exists: false}
},
{
$set: {
'pending.socialMediaAuthorIDs': []
}
}
)
await db.collection(CollectionName.Articles).updateMany(
{
published: {$ne: null},
'published.socialMediaAuthorIDs': {$exists: false}
},
{
$set: {
'published.socialMediaAuthorIDs': []
}
}
)
await db.collection(CollectionName.Articles).updateMany(
{
draft: {$ne: null},
'draft.socialMediaAuthorIDs': {$exists: false}
},
{
$set: {
'draft.socialMediaAuthorIDs': []
}
}
)
}
},
{
// Add new collection MailLog
version: 9,
async migrate(db) {
const mailLogs = await db.createCollection(CollectionName.MailLog, {strict: true})
await mailLogs.createIndex({subject: 1})
}
},
{
// Add MemberPlan Collection and PaymentMethod Collection
version: 10,
async migrate(db, locale) {
const memberPlans = await db.createCollection(CollectionName.MemberPlans, {
strict: true
})
await memberPlans.createIndex({name: 1})
await memberPlans.createIndex({slug: 1}, {unique: true})
const paymentMethod = await db.createCollection(CollectionName.PaymentMethods, {
strict: true
})
await paymentMethod.createIndex({name: 1})
await paymentMethod.createIndex({paymentAdapter: 1})
const users = db.collection(CollectionName.Users)
await users.createIndex({'subscription.memberPlanId': 1})
await users.updateMany(
{
paymentProviderCustomers: {$exists: false}
},
{$set: {paymentProviderCustomers: {}}}
)
await users.updateMany(
{
active: {$exists: false}
},
{$set: {active: true}}
)
await users.updateMany(
{
lastLogin: {$exists: false}
},
{$set: {lastLogin: null}}
)
await users.updateMany(
{
properties: {$exists: false}
},
{$set: {properties: []}}
)
const invoices = await db.createCollection(CollectionName.Invoices, {
strict: true
})
await invoices.createIndex({mail: 1})
const payments = await db.createCollection(CollectionName.Payments, {
strict: true
})
await payments.createIndex({intentID: 1})
}
},
{
// Add Commenting Table.
version: 11,
async migrate(db) {
const comments = await db.createCollection(CollectionName.Comments, {
strict: true
})
await comments.createIndex({createdAt: -1})
await comments.createIndex({'revisions.createdAt': -1})
}
},
{
// Make slug for published pages unique
version: 12,
async migrate(db, locale) {
const pages = await db.collection(CollectionName.Pages)
await pages.createIndex(
{'published.slug': 1},
{
collation: {locale, strength: 2},
unique: true,
partialFilterExpression: {'published.slug': {$exists: true}}
}
)
}
},
{
// Rename street field in address to address
version: 13,
async migrate(db, locale) {
const users = await db.collection(CollectionName.Users)
await users.updateMany(
{
'address.street': {$exists: true}
},
{
$rename: {'address.street': 'address.streetAddress'}
}
)
}
},
{
// Add emailVerifiedAt and oauth2Accounts to user model
version: 14,
async migrate(db, locale) {
const users = await db.collection(CollectionName.Users)
await users.updateMany(
{
emailVerifiedAt: {$exists: false}
},
{$set: {emailVerifiedAt: null}}
)
await users.updateMany(
{
oauth2Accounts: {$exists: false}
},
{$set: {oauth2Accounts: []}}
)
}
},
{
// Set paymentProviderCustomers to an empty array
version: 15,
async migrate(db, locale) {
const users = await db.collection(CollectionName.Users)
const allUsers: DBUser[] = await users.find({}).toArray()
for (const user of allUsers) {
const paymentProvidersCustomersArray: PaymentProviderCustomer[] = []
const paymentProviderCustomers = Object.keys(user.paymentProviderCustomers)
paymentProviderCustomers.forEach(ppc => {
interface OldPaymentProviderCustomer extends PaymentProviderCustomer {
customerID: never
id: PaymentProviderCustomer['customerID']
}
const userPPC = user.paymentProviderCustomers[+ppc] as OldPaymentProviderCustomer
paymentProvidersCustomersArray.push({
paymentProviderID: ppc,
customerID: userPPC.id
})
})
await users.updateOne(
{_id: user._id},
{$set: {paymentProviderCustomers: paymentProvidersCustomersArray}}
)
}
}
},
{
// add slug to existing paymentMethods
version: 16,
async migrate(db, locale) {
const paymentMethods = await db.collection(CollectionName.PaymentMethods)
const allPaymentMethods: DBPaymentMethod[] = await paymentMethods.find({}).toArray()
for (const paymentMethod of allPaymentMethods) {
const slug = slugify(paymentMethod.name)
await paymentMethods.updateOne({_id: paymentMethod._id}, {$set: {slug}})
}
}
},
{
// migrate existing deactivated subscriptions
version: 17,
async migrate(db, locale) {
const users = await db.collection(CollectionName.Users)
await users.updateMany(
{
$and: [
{'subscription.deactivatedAt': {$exists: true}},
{'subscription.deactivatedAt': null}
]
},
[
{
$set: {
'subscription.deactivation': null
}
},
{
$unset: ['subscription.deactivatedAt']
}
]
)
await users.updateMany(
{
$and: [
{'subscription.deactivatedAt': {$exists: true}},
{'subscription.deactivatedAt': {$ne: null}}
]
},
[
{
$set: {
'subscription.deactivation.date': '$subscription.deactivatedAt',
'subscription.deactivation.reason': SubscriptionDeactivationReason.None
}
},
{
$unset: ['subscription.deactivatedAt']
}
]
)
}
},
{
// migrate existing deactivated subscriptions
version: 18,
async migrate(db, locale) {
// Values required to execute migration
const TEMP_USER_PREFIX = '__temp_'
const removePrefixTempUser = function removePrefixTempUser(userID: string): string {
return userID.replace(TEMP_USER_PREFIX, '')
}
// 1. move subscription from user object into new subscription collection
const users = await db.collection(CollectionName.Users)
const userWithSubscriptions = await users.find({subscription: {$exists: true}}).toArray()
const newSubscriptions: Omit<Subscription, 'id'>[] = []
const OLD_TEMP_USER_REGEX = /^temp_[0-9]+_/
for (const user of userWithSubscriptions) {
if (!user.subscription) continue
// create correct temp user reference id
const userId = OLD_TEMP_USER_REGEX.test(user.email)
? `${TEMP_USER_PREFIX}${user._id}`
: user._id
newSubscriptions.push({
userID: userId,
createdAt: user.createdAt,
modifiedAt: user.modifiedAt,
memberPlanID: user.subscription.memberPlanID,
paymentPeriodicity: user.subscription.paymentPeriodicity,
monthlyAmount: user.subscription.monthlyAmount,
autoRenew: user.subscription.autoRenew,
startsAt: user.subscription.startsAt,
paidUntil: user.subscription.paidUntil,
periods: user.subscription.periods,
paymentMethodID: user.subscription.paymentMethodID,
properties: [],
deactivation: user.subscription.deactivation
})
}
const subscriptions = await db.createCollection(CollectionName.Subscriptions, {
strict: true
})
if (newSubscriptions.length > 0) await subscriptions.insertMany(newSubscriptions)
// 2. create new invoices
const invoices = await db.collection(CollectionName.Invoices)
const allInvoices = await invoices.find({}).toArray()
const allSubscriptions = await subscriptions.find({}).toArray()
const newInvoices: DBInvoice[] = []
for (const invoice of allInvoices) {
const userID = invoice.userID
const subscription = allSubscriptions.find(
subscription => removePrefixTempUser(subscription.userID) === userID
)
if (!subscription) {
continue
}
newInvoices.push({
_id: invoice._id,
createdAt: invoice.createdAt,
modifiedAt: invoice.modifiedAt,
subscriptionID: subscription._id,
description: invoice.description,
canceledAt: invoice.canceledAt,
dueAt: invoice.dueAt,
items: invoice.items,
mail: invoice.mail,
paidAt: invoice.paidAt,
sentReminderAt: invoice.sentReminderAt
})
}
// inform We.Publish operators to remove this manually
await invoices.rename('invoices.bak')
const emptyInvoices = await db.createCollection(CollectionName.Invoices, {
strict: true
})
if (newInvoices.length > 0) await emptyInvoices.insertMany(newInvoices)
// 3. remove subscription object from user collection
await users.updateMany(
{subscription: {$exists: true}},
{
$unset: {subscription: ''}
}
)
// 4. split existing user collection into new temp.user and user collection
const tempUserQuery = {email: {$regex: OLD_TEMP_USER_REGEX}}
const tempUsers = await users.find(tempUserQuery).toArray()
const newTempUserCollection = await db.createCollection('temp.users', {
strict: true
})
if (tempUsers.length) {
// remove old temp_ prefix from emails in the new temp.user collection
for (const tempUser of tempUsers) {
tempUser.email = tempUser.email.replace(OLD_TEMP_USER_REGEX, '')
}
await newTempUserCollection.insertMany(tempUsers)
// 5. delete migrated temp users from user collection
await users.deleteMany(tempUserQuery)
}
}
},
{
// rename image => source to link and author to source; This should make code and data consistent
version: 19,
async migrate(db, locale) {
const images = await db.collection(CollectionName.Images)
await images.updateMany({}, {$rename: {source: 'link'}})
await images.updateMany({}, {$rename: {author: 'source'}})
}
},
{
// change embed block properties width and height from number to string
version: 20,
async migrate(db) {
const articles = db.collection(CollectionName.Articles)
const migrationArticles = await articles.find().toArray()
for (const article of migrationArticles) {
if (article.draft) {
article.draft.blocks.forEach((block: ArticleBlock) => {
if (block.type === BlockType.Embed) {
block.height = String(block.height)
block.width = String(block.width)
}
})
}
if (article.published) {
article.published.blocks.forEach((block: ArticleBlock) => {
if (block.type === BlockType.Embed) {
block.height = String(block.height)
block.width = String(block.width)
}
})
}
if (article.pending) {
article.pending.blocks.forEach((block: ArticleBlock) => {
if (block.type === BlockType.Embed) {
block.height = String(block.height)
block.width = String(block.width)
}
})
}
await articles.findOneAndReplace({_id: article._id}, article)
}
const pages = db.collection(CollectionName.Pages)
const migrationPages = await pages.find().toArray()
for (const page of migrationPages) {
if (page.draft) {
page.draft.blocks.forEach((block: PageBlock) => {
if (block.type === BlockType.Embed) {
block.height = String(block.height)
block.width = String(block.width)
}
})
}
if (page.published) {
page.published.blocks.forEach((block: PageBlock) => {
if (block.type === BlockType.Embed) {
block.height = String(block.height)
block.width = String(block.width)
}
})
}
if (page.pending) {
page.pending.blocks.forEach((block: PageBlock) => {
if (block.type === BlockType.Embed) {
block.height = String(block.height)
block.width = String(block.width)
}
})
}
await pages.findOneAndReplace({_id: page._id}, page)
}
}
},
{
version: 21,
async migrate(db, locale) {
const invoices = db.collection(CollectionName.Invoices)
await invoices.updateMany({}, {$set: {manuallySetAsPaidByUserId: undefined}})
}
},
{
// Rename unused temp user collection. For operators to remove manually since the collection not used anymore.
version: 22,
async migrate(db) {
const collections = await db.listCollections().toArray()
if (collections.includes('temp.users')) {
const tempUser = await db.collection('temp.users')
await tempUser.rename('temp.users.bak')
}
}
},
{
// Try to migrate email addressees of users
version: 23,
async migrate(db) {
const userCollection = await db.collection(CollectionName.Users)
const subscriptionCollection = await db.collection(CollectionName.Subscriptions)
const sessionCollection = await db.collection(CollectionName.Sessions)
const users = await userCollection.find().toArray()
// inform We.Publish operators to remove this manually
await userCollection.rename('23-users.bak')
const emptyUsers = await db.createCollection(CollectionName.Users, {
strict: true
})
type List = {
userId: string
email: string
}
const listSanitizedUsers: List[] = []
for (const user of users) {
user.email = user.email.toLowerCase()
const duplicatedMail = listSanitizedUsers.find(element => element.email === user.email)
// If already a user with normalized mail exist merge them
if (duplicatedMail) {
// Update userID in subscription table
const subscriptions = await subscriptionCollection.find({userID: user._id}).toArray()
for (const subscription of subscriptions) {
subscription.properties.push({
key: 'UserIDChangedByMigration23',
value: subscription.userID,
public: false
})
subscription.userID = duplicatedMail.userId
await subscriptionCollection.replaceOne({_id: subscription._id}, subscription, {
upsert: true
})
}
// Update userID in session table
const sessions = await sessionCollection.find({userID: user._id}).toArray()
for (const session of sessions) {
session.user = duplicatedMail.userId
await sessionCollection.replaceOne({_id: session._id}, session, {upsert: true})
}
} else {
// If user is unique update mail of user
await emptyUsers.insertOne(user)
listSanitizedUsers.push({
userId: user._id,
email: user.email
})
}
}
}
},
{
// add settings category
version: 24,
async migrate(db, locale) {
const settingsDoc = await db.createCollection(CollectionName.Settings, {
strict: true
})
const peeringTimeout: CreateSettingArgs<number> = {
name: SettingName.PEERING_TIMEOUT_MS,
value: 3000,
settingRestriction: {minValue: 1000, maxValue: 10000}
}
const allowAnonCommenting: CreateSettingArgs<boolean> = {
name: SettingName.ALLOW_GUEST_COMMENTING,
value: false,
settingRestriction: {allowedValues: {boolChoice: true}}
}
const sendLoginJWTExpires: CreateSettingArgs<number> = {
name: SettingName.SEND_LOGIN_JWT_EXPIRES_MIN,
value: 10080,
settingRestriction: {minValue: 1, maxValue: 10080}
}
const resetPwdExpires: CreateSettingArgs<number> = {
name: SettingName.RESET_PASSWORD_JWT_EXPIRES_MIN,
value: 1440,
settingRestriction: {minValue: 1, maxValue: 10080}
}
const invoiceReminderFreq: CreateSettingArgs<number> = {
name: SettingName.INVOICE_REMINDER_FREQ,
value: 3,
settingRestriction: {minValue: 0, maxValue: 30}
}
const invoiceReminderTries: CreateSettingArgs<number> = {
name: SettingName.INVOICE_REMINDER_MAX_TRIES,
value: 5,
settingRestriction: {minValue: 0, maxValue: 10}
}
await settingsDoc.insertMany([
peeringTimeout,
allowAnonCommenting,
sendLoginJWTExpires,
resetPwdExpires,
invoiceReminderFreq,
invoiceReminderTries
])
}
}
]
export const LatestMigration = Migrations[Migrations.length - 1]