Extend transaction usage

This commit is contained in:
Jonas Lochmann
2020-10-02 09:58:13 +02:00
parent 62f4e368a6
commit abc2102da5
47 changed files with 1367 additions and 860 deletions
+15 -11
View File
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -22,16 +22,20 @@ export const canRecoverPassword = async ({ database, mailAuthToken, parentUserId
database: Database
mailAuthToken: string
parentUserId: string
}) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database })
// no transaction here because this is directly called from an API endpoint
}): Promise<boolean> => {
return database.transaction(async (transaction) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database, transaction })
const entry = await database.user.findOne({
where: {
mail,
userId: parentUserId,
type: 'parent'
}
const entry = await database.user.findOne({
where: {
mail,
userId: parentUserId,
type: 'parent'
},
transaction
})
return !!entry
})
return !!entry
}
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -15,13 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Database } from '../../database'
import { Database, Transaction } from '../../database'
import { randomWords } from '../../util/random-words'
import { generateIdWithinFamily } from '../../util/token'
export const createAddDeviceToken = async ({ familyId, database }: {
export const createAddDeviceToken = async ({ familyId, database, transaction }: {
familyId: string
database: Database
transaction: Transaction
}) => {
const token = randomWords(5)
const deviceId = generateIdWithinFamily()
@@ -29,7 +30,8 @@ export const createAddDeviceToken = async ({ familyId, database }: {
await database.addDeviceToken.destroy({
where: {
familyId
}
},
transaction
})
await database.addDeviceToken.create({
@@ -37,7 +39,7 @@ export const createAddDeviceToken = async ({ familyId, database }: {
token: token.toLowerCase(),
deviceId,
createdAt: Date.now().toString()
})
}, { transaction })
return { token, deviceId }
}
+5 -4
View File
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -32,11 +32,12 @@ export const createFamily = async ({ database, mailAuthToken, firstParentDevice,
timeZone: string,
parentName: string,
deviceName: string
// no transaction here because this is directly called from an API endpoint
}) => {
const now = Date.now().toString(10)
const mail = await requireMailByAuthToken({ database, mailAuthToken })
return database.transaction(async (transaction) => {
const now = Date.now().toString(10)
const mail = await requireMailByAuthToken({ database, mailAuthToken, transaction })
// ensure that no family was created for this mail yet
const exisitngUserEntry = await database.user.findOne({
where: {
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -15,10 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Database } from '../../database'
import { Database, Transaction } from '../../database'
import { requireMailByAuthToken } from '../authentication'
const getStatusByMailAddress = async ({ mail, database }: {mail: string, database: Database}) => {
const getStatusByMailAddress = async ({
mail, database, transaction
}: { mail: string, database: Database, transaction: Transaction }) => {
if (!mail) {
throw new Error('no mail address')
}
@@ -26,7 +28,8 @@ const getStatusByMailAddress = async ({ mail, database }: {mail: string, databas
const entry = await database.user.findOne({
where: {
mail
}
},
transaction
})
if (entry) {
@@ -36,9 +39,11 @@ const getStatusByMailAddress = async ({ mail, database }: {mail: string, databas
}
}
export const getStatusByMailToken = async ({ mailAuthToken, database }: {mailAuthToken: string, database: Database}) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database })
const status = await getStatusByMailAddress({ mail, database })
export const getStatusByMailToken = async ({
mailAuthToken, database, transaction
}: { mailAuthToken: string, database: Database, transaction: Transaction }) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database, transaction })
const status = await getStatusByMailAddress({ mail, database, transaction })
return { mail, status }
}
+38 -33
View File
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -21,7 +21,7 @@ import { Database } from '../../database'
import { generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket'
import { requireMailByAuthToken } from '../authentication'
import { notifyClientsAboutChanges } from '../websocket'
import { notifyClientsAboutChangesDelayed } from '../websocket'
export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUserId, parentPasswordSecondHash, database, websocket }: {
mailAuthToken: string
@@ -30,32 +30,35 @@ export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUs
parentPasswordSecondHash: string
database: Database
websocket: WebsocketApi
// no transaction here because this is directly called from an API endpoint
}) => {
const deviceEntry = await database.device.findOne({
where: {
deviceAuthToken
}
})
if (!deviceEntry) {
throw new Unauthorized()
}
const familyId = deviceEntry.familyId
const mailAddress = await requireMailByAuthToken({ mailAuthToken, database })
const exisitingUser = await database.user.findOne({
where: {
mail: mailAddress
}
})
if (exisitingUser) {
throw new Conflict()
}
await database.transaction(async (transaction) => {
const deviceEntry = await database.device.findOne({
where: {
deviceAuthToken
},
transaction
})
if (!deviceEntry) {
throw new Unauthorized()
}
const familyId = deviceEntry.familyId
const mailAddress = await requireMailByAuthToken({ mailAuthToken, database, transaction })
const exisitingUser = await database.user.findOne({
where: {
mail: mailAddress
},
transaction
})
if (exisitingUser) {
throw new Conflict()
}
const parentEntry = await database.user.findOne({
where: {
type: 'parent',
@@ -95,13 +98,15 @@ export const linkMailAddress = async ({ mailAuthToken, deviceAuthToken, parentUs
},
transaction
})
})
await notifyClientsAboutChanges({
familyId,
sourceDeviceId: null,
database,
websocket,
isImportant: true
// notify
await notifyClientsAboutChangesDelayed({
familyId,
sourceDeviceId: null,
database,
websocket,
isImportant: true,
transaction
})
})
}
+15 -21
View File
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
* Copyright (C) 2019 Jonas Lochmann
* Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -16,34 +16,33 @@
*/
import { Conflict } from 'http-errors'
import * as Sequelize from 'sequelize'
import { ParentPassword } from '../../api/schema'
import { Database } from '../../database'
import { generateVersionId } from '../../util/token'
import { WebsocketApi } from '../../websocket'
import { requireMailByAuthToken } from '../authentication'
import { notifyClientsAboutChanges } from '../websocket'
import { notifyClientsAboutChangesDelayed } from '../websocket'
export const recoverParentPassword = async ({ database, websocket, password, mailAuthToken }: {
database: Database
websocket: WebsocketApi
password: ParentPassword
mailAuthToken: string
// no transaction here because this is directly called from an API endpoint
}) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database })
await database.transaction(async (transaction) => {
const mail = await requireMailByAuthToken({ mailAuthToken, database, transaction })
const { familyId } = await database.transaction(async (transaction) => {
// update the user entry
const userEntry = await database.user.findOne({
where: {
mail
},
transaction,
lock: Sequelize.Transaction.LOCK.UPDATE
transaction
})
if (!userEntry) {
return { familyId: null }
throw new Conflict()
}
userEntry.passwordHash = password.hash
@@ -62,18 +61,13 @@ export const recoverParentPassword = async ({ database, websocket, password, mai
transaction
})
return { familyId: userEntry.familyId }
})
if (familyId === null) {
throw new Conflict()
}
await notifyClientsAboutChanges({
database,
familyId,
websocket,
isImportant: true,
sourceDeviceId: null
await notifyClientsAboutChangesDelayed({
database,
familyId: userEntry.familyId,
websocket,
isImportant: true,
sourceDeviceId: null,
transaction
})
})
}
+16 -20
View File
@@ -22,7 +22,7 @@ import { generateAuthToken, generateIdWithinFamily, generateVersionId } from '..
import { WebsocketApi } from '../../websocket'
import { requireMailByAuthToken } from '../authentication'
import { prepareDeviceEntry } from '../device/prepare-device-entry'
import { notifyClientsAboutChanges } from '../websocket'
import { notifyClientsAboutChangesDelayed } from '../websocket'
export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo, deviceName, websocket }: {
database: Database
@@ -30,10 +30,11 @@ export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo,
newDeviceInfo: NewDeviceInfo
deviceName: string
websocket: WebsocketApi
}) => {
const mail = await requireMailByAuthToken({ database, mailAuthToken })
// no transaction here because this is directly called from an API endpoint
}): Promise<{ deviceId: string; deviceAuthToken: string }> => {
return database.transaction(async (transaction) => {
const mail = await requireMailByAuthToken({ database, mailAuthToken, transaction })
const { response, familyId, sourceDeviceId } = await database.transaction(async (transaction) => {
const userEntryUnsafe = await database.user.findOne({
where: {
mail
@@ -73,23 +74,18 @@ export const signInIntoFamily = async ({ database, mailAuthToken, newDeviceInfo,
transaction
})
return {
response: {
deviceId,
deviceAuthToken
},
await notifyClientsAboutChangesDelayed({
familyId: userEntry.familyId,
websocket,
database,
isImportant: true,
sourceDeviceId: deviceId,
familyId: userEntry.familyId
transaction
})
return {
deviceId,
deviceAuthToken
}
})
await notifyClientsAboutChanges({
familyId,
websocket,
database,
isImportant: true,
sourceDeviceId
})
return response
}