diff --git a/docs/api/parent.md b/docs/api/parent.md
index 556157c..e0cd563 100644
--- a/docs/api/parent.md
+++ b/docs/api/parent.md
@@ -194,3 +194,27 @@ If the ``secondPasswordHash`` is invalid: HTTP status code 401 Unauthorized
If the server does not support this request: HTTP status code 404
On success: ``{"token": "some string"}``; you should not make any assumptions about the token string
+
+## POST /parent/delete-account
+
+Use this to delete an account. This includes the complete family registration
+with users and devices. Due to that, all parents with a linked mail address
+have to authenticate this action.
+
+## request
+
+see [this JSON schema](../schema/deleteaccountpayload.md)
+
+## response
+
+On success: HTTP status code 200
+
+On a invalid request body: HTTP status code 400 Bad Request
+
+On unknown device auth token: HTTP status code 401 Unauthorized
+
+On missing parent authentication: HTTP status code 401 Unauthorized
+
+On unrelated parent authentication: HTTP status code 401 Unauthorized
+
+If a newer endpoint must be used/the client is too old: HTTP status code 410 Gone
diff --git a/docs/schema/DeleteAccountPayload.schema.json b/docs/schema/DeleteAccountPayload.schema.json
new file mode 100644
index 0000000..3d82f61
--- /dev/null
+++ b/docs/schema/DeleteAccountPayload.schema.json
@@ -0,0 +1,23 @@
+{
+ "type": "object",
+ "properties": {
+ "deviceAuthToken": {
+ "type": "string"
+ },
+ "mailAuthTokens": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "deviceAuthToken",
+ "mailAuthTokens"
+ ],
+ "definitions": {},
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "DeleteAccountPayload",
+ "$id": "https://timelimit.io/DeleteAccountPayload"
+}
\ No newline at end of file
diff --git a/docs/schema/README.md b/docs/schema/README.md
index 06a9092..6344daa 100644
--- a/docs/schema/README.md
+++ b/docs/schema/README.md
@@ -14,6 +14,8 @@
* [CreateRegisterDeviceTokenRequest](./createregisterdevicetokenrequest.md) – `https://timelimit.io/CreateRegisterDeviceTokenRequest`
+* [DeleteAccountPayload](./deleteaccountpayload.md) – `https://timelimit.io/DeleteAccountPayload`
+
* [FinishPurchaseByGooglePlayRequest](./finishpurchasebygoogleplayrequest.md) – `https://timelimit.io/FinishPurchaseByGooglePlayRequest`
* [IdentityTokenPayload](./identitytokenpayload.md) – `https://timelimit.io/IdentityTokenPayload`
@@ -282,6 +284,8 @@
* [Untitled array in ClientPushChangesRequest](./clientpushchangesrequest-properties-actions.md) – `https://timelimit.io/ClientPushChangesRequest#/properties/actions`
+* [Untitled array in DeleteAccountPayload](./deleteaccountpayload-properties-mailauthtokens.md) – `https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens`
+
* [Untitled array in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddinstalledappsaction-properties-apps.md) – `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddInstalledAppsAction/properties/apps`
* [Untitled array in SerializedAppLogicAction](./serializedapplogicaction-definitions-serializedaddusedtimeactionversion2-properties-i.md) – `https://timelimit.io/SerializedAppLogicAction#/definitions/SerializedAddUsedTimeActionVersion2/properties/i`
diff --git a/docs/schema/deleteaccountpayload-definitions.md b/docs/schema/deleteaccountpayload-definitions.md
new file mode 100644
index 0000000..a3e9baf
--- /dev/null
+++ b/docs/schema/deleteaccountpayload-definitions.md
@@ -0,0 +1,15 @@
+# Untitled undefined type in DeleteAccountPayload Schema
+
+```txt
+https://timelimit.io/DeleteAccountPayload#/definitions
+```
+
+
+
+| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
+| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
+| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
+
+## definitions Type
+
+unknown
diff --git a/docs/schema/deleteaccountpayload-properties-deviceauthtoken.md b/docs/schema/deleteaccountpayload-properties-deviceauthtoken.md
new file mode 100644
index 0000000..a31e059
--- /dev/null
+++ b/docs/schema/deleteaccountpayload-properties-deviceauthtoken.md
@@ -0,0 +1,15 @@
+# Untitled string in DeleteAccountPayload Schema
+
+```txt
+https://timelimit.io/DeleteAccountPayload#/properties/deviceAuthToken
+```
+
+
+
+| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
+| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
+| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
+
+## deviceAuthToken Type
+
+`string`
diff --git a/docs/schema/deleteaccountpayload-properties-mailauthtokens-items.md b/docs/schema/deleteaccountpayload-properties-mailauthtokens-items.md
new file mode 100644
index 0000000..3a0b83b
--- /dev/null
+++ b/docs/schema/deleteaccountpayload-properties-mailauthtokens-items.md
@@ -0,0 +1,15 @@
+# Untitled string in DeleteAccountPayload Schema
+
+```txt
+https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens/items
+```
+
+
+
+| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
+| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
+| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
+
+## items Type
+
+`string`
diff --git a/docs/schema/deleteaccountpayload-properties-mailauthtokens.md b/docs/schema/deleteaccountpayload-properties-mailauthtokens.md
new file mode 100644
index 0000000..fa419c8
--- /dev/null
+++ b/docs/schema/deleteaccountpayload-properties-mailauthtokens.md
@@ -0,0 +1,15 @@
+# Untitled array in DeleteAccountPayload Schema
+
+```txt
+https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens
+```
+
+
+
+| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
+| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :-------------------------------------------------------------------------------------------- |
+| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [DeleteAccountPayload.schema.json\*](DeleteAccountPayload.schema.json "open original schema") |
+
+## mailAuthTokens Type
+
+`string[]`
diff --git a/docs/schema/deleteaccountpayload.md b/docs/schema/deleteaccountpayload.md
new file mode 100644
index 0000000..30e6cda
--- /dev/null
+++ b/docs/schema/deleteaccountpayload.md
@@ -0,0 +1,60 @@
+# DeleteAccountPayload Schema
+
+```txt
+https://timelimit.io/DeleteAccountPayload
+```
+
+
+
+| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In |
+| :------------------ | :--------- | :------------- | :----------- | :---------------- | :-------------------- | :------------------ | :------------------------------------------------------------------------------------------ |
+| Can be instantiated | Yes | Unknown status | No | Forbidden | Forbidden | none | [DeleteAccountPayload.schema.json](DeleteAccountPayload.schema.json "open original schema") |
+
+## DeleteAccountPayload Type
+
+`object` ([DeleteAccountPayload](deleteaccountpayload.md))
+
+# DeleteAccountPayload Properties
+
+| Property | Type | Required | Nullable | Defined by |
+| :---------------------------------- | :------- | :------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [deviceAuthToken](#deviceauthtoken) | `string` | Required | cannot be null | [DeleteAccountPayload](deleteaccountpayload-properties-deviceauthtoken.md "https://timelimit.io/DeleteAccountPayload#/properties/deviceAuthToken") |
+| [mailAuthTokens](#mailauthtokens) | `array` | Required | cannot be null | [DeleteAccountPayload](deleteaccountpayload-properties-mailauthtokens.md "https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens") |
+
+## deviceAuthToken
+
+
+
+`deviceAuthToken`
+
+* is required
+
+* Type: `string`
+
+* cannot be null
+
+* defined in: [DeleteAccountPayload](deleteaccountpayload-properties-deviceauthtoken.md "https://timelimit.io/DeleteAccountPayload#/properties/deviceAuthToken")
+
+### deviceAuthToken Type
+
+`string`
+
+## mailAuthTokens
+
+
+
+`mailAuthTokens`
+
+* is required
+
+* Type: `string[]`
+
+* cannot be null
+
+* defined in: [DeleteAccountPayload](deleteaccountpayload-properties-mailauthtokens.md "https://timelimit.io/DeleteAccountPayload#/properties/mailAuthTokens")
+
+### mailAuthTokens Type
+
+`string[]`
+
+# DeleteAccountPayload Definitions
diff --git a/other/mail/account-deleted/html.ejs b/other/mail/account-deleted/html.ejs
new file mode 100644
index 0000000..6a25581
--- /dev/null
+++ b/other/mail/account-deleted/html.ejs
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TimeLimit
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sie haben eine Löschung Ihres Benutzerkontos angefordert. Diese wurde soeben durchgeführt.
+ You requested the deletion of your account. This deletion is finished now.
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sie erhalten diese Nachricht aufgrund Ihrer Anfrage. Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
+ You got this mail due to your request. If you have got any questions, then you can reply to this message.
+ © <%= mailimprint %>
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
diff --git a/other/mail/account-deleted/htmltemplate-src.txt b/other/mail/account-deleted/htmltemplate-src.txt
new file mode 100644
index 0000000..18e649b
--- /dev/null
+++ b/other/mail/account-deleted/htmltemplate-src.txt
@@ -0,0 +1,44 @@
+
+
+
+
+ TimeLimit
+
+
+
+
+
+
+ Sie haben eine Löschung Ihres Benutzerkontos angefordert. Diese wurde soeben durchgeführt.
+
+
+
+ You requested the deletion of your account. This deletion is finished now.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sie erhalten diese Nachricht aufgrund Ihrer Anfrage.
+ Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
+
+
+ You got this mail due to your request.
+ If you have got any questions, then you can reply to this message.
+
+
+ © <%= mailimprint %>
+
+
+
+
+
+
diff --git a/other/mail/account-deleted/subject.ejs b/other/mail/account-deleted/subject.ejs
new file mode 100644
index 0000000..e2df167
--- /dev/null
+++ b/other/mail/account-deleted/subject.ejs
@@ -0,0 +1 @@
+Konto gelöscht/Account deleted
diff --git a/other/mail/account-deleted/text.ejs b/other/mail/account-deleted/text.ejs
new file mode 100644
index 0000000..a85ffaf
--- /dev/null
+++ b/other/mail/account-deleted/text.ejs
@@ -0,0 +1,13 @@
+Sie haben eine Löschung Ihres Benutzerkontos angefordert. Diese wurde soeben durchgeführt.
+
+You requested the deletion of your account. This deletion is finished now.
+
+----------------------
+
+Sie erhalten diese Nachricht aufgrund Ihrer Anfrage.
+Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.
+
+You got this mail due to your request.
+If you have got any questions, then you can reply to this message.
+
+ <%- mailimprint %>
diff --git a/scripts/build-schemas.js b/scripts/build-schemas.js
index c62df9e..0cbb58c 100644
--- a/scripts/build-schemas.js
+++ b/scripts/build-schemas.js
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
- * Copyright (C) 2019 - 2022 Jonas Lochmann
+ * Copyright (C) 2019 - 2023 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
@@ -43,7 +43,8 @@ const types = [
'RequestWithAuthToken',
'SendMailLoginCodeRequest',
'SignInByMailCodeRequest',
- 'IdentityTokenPayload'
+ 'IdentityTokenPayload',
+ 'DeleteAccountPayload',
]
const docOnlyTypes = [
diff --git a/src/api/parent.ts b/src/api/parent.ts
index cbe0543..e0e7eca 100644
--- a/src/api/parent.ts
+++ b/src/api/parent.ts
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
- * Copyright (C) 2019 - 2022 Jonas Lochmann
+ * Copyright (C) 2019 - 2023 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,6 +21,7 @@ import { Router } from 'express'
import { BadRequest, Forbidden, Unauthorized } from 'http-errors'
import { config } from '../config'
import { Database, Transaction } from '../database'
+import { deleteAccount } from '../function/cleanup/account-deletion'
import { removeDevice } from '../function/device/remove-device'
import { createAddDeviceToken } from '../function/parent/create-add-device-token'
import { createFamily } from '../function/parent/create-family'
@@ -36,7 +37,8 @@ import {
isCreateFamilyByMailTokenRequest,
isCreateRegisterDeviceTokenRequest, isLinkParentMailAddressRequest,
isMailAuthTokenRequestBody, isRecoverParentPasswordRequest,
- isRemoveDeviceRequest, isSignIntoFamilyRequest, isRequestIdentityTokenRequest
+ isRemoveDeviceRequest, isSignIntoFamilyRequest, isRequestIdentityTokenRequest,
+ isDeleteAccountPayload
} from './validator'
export const createParentRouter = ({
@@ -356,5 +358,19 @@ export const createParentRouter = ({
}
})
+ router.post('/delete-account', json(), async (req, res, next) => {
+ try {
+ if (!isDeleteAccountPayload(req.body)) {
+ throw new BadRequest()
+ }
+
+ await deleteAccount({ database, request: req.body, websocket })
+
+ res.sendStatus(200)
+ } catch (ex) {
+ next(ex)
+ }
+ })
+
return router
}
diff --git a/src/api/schema.ts b/src/api/schema.ts
index 5e16481..afaa5f1 100644
--- a/src/api/schema.ts
+++ b/src/api/schema.ts
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
- * Copyright (C) 2019 - 2022 Jonas Lochmann
+ * Copyright (C) 2019 - 2023 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
@@ -176,5 +176,10 @@ export type IdentityTokenPayload = IdentityTokenCreatePayload & {
exp: number
}
+export interface DeleteAccountPayload {
+ deviceAuthToken: string
+ mailAuthTokens: Array
+}
+
export { SerializedParentAction, SerializedChildAction, SerializedAppLogicAction } from '../action/serialization'
export { ServerDataStatus } from '../object/serverdatastatus'
diff --git a/src/api/validator.ts b/src/api/validator.ts
index 12f5a01..98f176c 100644
--- a/src/api/validator.ts
+++ b/src/api/validator.ts
@@ -1,5 +1,5 @@
// tslint:disable
-import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, RequestIdentityTokenRequest, RequestWithAuthToken, SendMailLoginCodeRequest, SignInByMailCodeRequest, IdentityTokenPayload } from './schema'
+import { ClientPushChangesRequest, ClientPullChangesRequest, MailAuthTokenRequestBody, CreateFamilyByMailTokenRequest, SignIntoFamilyRequest, RecoverParentPasswordRequest, RegisterChildDeviceRequest, SerializedParentAction, SerializedAppLogicAction, SerializedChildAction, CreateRegisterDeviceTokenRequest, CanDoPurchaseRequest, FinishPurchaseByGooglePlayRequest, LinkParentMailAddressRequest, UpdatePrimaryDeviceRequest, RemoveDeviceRequest, RequestIdentityTokenRequest, RequestWithAuthToken, SendMailLoginCodeRequest, SignInByMailCodeRequest, IdentityTokenPayload, DeleteAccountPayload } from './schema'
import Ajv from 'ajv'
const ajv = new Ajv()
@@ -3493,3 +3493,24 @@ export const isIdentityTokenPayload: (value: unknown) => value is IdentityTokenP
"definitions": definitions,
"$schema": "http://json-schema.org/draft-07/schema#"
})
+export const isDeleteAccountPayload: (value: unknown) => value is DeleteAccountPayload = ajv.compile({
+ "type": "object",
+ "properties": {
+ "deviceAuthToken": {
+ "type": "string"
+ },
+ "mailAuthTokens": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "deviceAuthToken",
+ "mailAuthTokens"
+ ],
+ "definitions": definitions,
+ "$schema": "http://json-schema.org/draft-07/schema#"
+})
diff --git a/src/function/cleanup/account-deletion.ts b/src/function/cleanup/account-deletion.ts
new file mode 100644
index 0000000..cf783f5
--- /dev/null
+++ b/src/function/cleanup/account-deletion.ts
@@ -0,0 +1,107 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2023 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
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Unauthorized } from 'http-errors'
+import { DeleteAccountPayload } from '../../api/schema'
+import { Database } from '../../database'
+import { sendAccountDeletedMail } from '../../util/mail'
+import { WebsocketApi } from '../../websocket'
+import { requireMailAndLocaleByAuthToken } from '../authentication'
+import { deleteFamilies } from './delete-families'
+
+export async function deleteAccount({ request, database, websocket }: {
+ request: DeleteAccountPayload
+ database: Database
+ websocket: WebsocketApi
+}) {
+ await database.transaction(async (transaction) => {
+ const deviceEntryUnsafe = await database.device.findOne({
+ where: { deviceAuthToken: request.deviceAuthToken },
+ attributes: ['familyId'],
+ transaction
+ })
+
+ if (!deviceEntryUnsafe) {
+ throw new Unauthorized()
+ }
+
+ const deviceEntry = {
+ familyId: deviceEntryUnsafe.familyId
+ }
+
+ const userEntries = (await database.user.findAll({
+ where: {
+ familyId: deviceEntry.familyId,
+ type: 'parent'
+ },
+ attributes: ['mail'],
+ transaction
+ })).map((item) => ({ mail: item.mail }))
+
+ const registeredMailAddresses = new Set()
+
+ userEntries.forEach((item) => {
+ if (item.mail !== '') registeredMailAddresses.add(item.mail)
+ })
+
+ const authenticatedMailAddresses = new Set()
+
+ for (const mailAuthToken of request.mailAuthTokens) {
+ const info = await requireMailAndLocaleByAuthToken({
+ mailAuthToken,
+ database,
+ transaction,
+ invalidate: true
+ })
+
+ if (!registeredMailAddresses.has(info.mail)) throw new Unauthorized()
+
+ authenticatedMailAddresses.add(info.mail)
+ }
+
+ if (registeredMailAddresses.size !== authenticatedMailAddresses.size) throw new Unauthorized()
+
+ registeredMailAddresses.forEach((mail) => {
+ if (!authenticatedMailAddresses.has(mail)) throw new Unauthorized()
+ })
+
+ const deviceEntries = (await database.device.findAll({
+ where: {
+ familyId: deviceEntry.familyId
+ },
+ transaction,
+ attributes: ['deviceAuthToken']
+ })).map((item) => ({ deviceAuthToken: item.deviceAuthToken }))
+
+ await deleteFamilies({ database, transaction, familiyIds: [deviceEntry.familyId] })
+
+ transaction.afterCommit(() => {
+ for (const device of deviceEntries) {
+ websocket.triggerSyncByDeviceAuthToken({
+ deviceAuthToken: device.deviceAuthToken,
+ isImportant: true
+ })
+ }
+
+ registeredMailAddresses.forEach((receiver) => {
+ sendAccountDeletedMail({ receiver }).catch((ex) => {
+ console.warn('failure while sending account deletion confirmation', ex)
+ })
+ })
+ })
+ })
+}
diff --git a/src/function/cleanup/delete-families.ts b/src/function/cleanup/delete-families.ts
index b5856a6..cf65f86 100644
--- a/src/function/cleanup/delete-families.ts
+++ b/src/function/cleanup/delete-families.ts
@@ -17,10 +17,11 @@
import { difference } from 'lodash'
import * as Sequelize from 'sequelize'
-import { Database } from '../../database'
+import { Database, Transaction } from '../../database'
-export async function deleteFamilies ({ database, familiyIds }: {
+export async function deleteFamilies ({ database, transaction, familiyIds }: {
database: Database
+ transaction: Transaction
familiyIds: Array
// no transaction here because this should run isolated
}) {
@@ -28,128 +29,126 @@ export async function deleteFamilies ({ database, familiyIds }: {
return
}
- await database.transaction(async (transaction) => {
- // category
- await database.category.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // category
+ await database.category.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // categoryapp
- await database.categoryApp.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // categoryapp
+ await database.categoryApp.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // purchase
- await database.purchase.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // purchase
+ await database.purchase.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // timelimitrule
- await database.timelimitRule.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // timelimitrule
+ await database.timelimitRule.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // usedtime
- await database.usedTime.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // usedtime
+ await database.usedTime.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // session durations
- await database.sessionDuration.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // session durations
+ await database.sessionDuration.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // user
- await database.user.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // user
+ await database.user.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
- // device
- const oldDeviceAuthTokens = (await database.device.findAll({
+ // device
+ const oldDeviceAuthTokens = (await database.device.findAll({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ attributes: ['deviceAuthToken'],
+ transaction
+ })).map((item) => item.deviceAuthToken)
+
+ await database.device.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
+ })
+
+ // olddevice
+ if (oldDeviceAuthTokens.length > 0) {
+ const knownOldDeviceAuthTokens = (await database.oldDevice.findAll({
where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
+ deviceAuthToken: {
+ [Sequelize.Op.in]: oldDeviceAuthTokens
}
},
- attributes: ['deviceAuthToken'],
transaction
})).map((item) => item.deviceAuthToken)
- await database.device.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ const oldDeviceAuthTokensToAdd = difference(oldDeviceAuthTokens, knownOldDeviceAuthTokens)
- // olddevice
- if (oldDeviceAuthTokens.length > 0) {
- const knownOldDeviceAuthTokens = (await database.oldDevice.findAll({
- where: {
- deviceAuthToken: {
- [Sequelize.Op.in]: oldDeviceAuthTokens
- }
- },
- transaction
- })).map((item) => item.deviceAuthToken)
-
- const oldDeviceAuthTokensToAdd = difference(oldDeviceAuthTokens, knownOldDeviceAuthTokens)
-
- if (oldDeviceAuthTokensToAdd.length > 0) {
- await database.oldDevice.bulkCreate(
- oldDeviceAuthTokensToAdd.map((item) => ({
- deviceAuthToken: item
- })),
- { transaction }
- )
- }
+ if (oldDeviceAuthTokensToAdd.length > 0) {
+ await database.oldDevice.bulkCreate(
+ oldDeviceAuthTokensToAdd.map((item) => ({
+ deviceAuthToken: item
+ })),
+ { transaction }
+ )
}
+ }
- // family
- await database.family.destroy({
- where: {
- familyId: {
- [Sequelize.Op.in]: familiyIds
- }
- },
- transaction
- })
+ // family
+ await database.family.destroy({
+ where: {
+ familyId: {
+ [Sequelize.Op.in]: familiyIds
+ }
+ },
+ transaction
})
}
diff --git a/src/function/cleanup/delete-old-families.ts b/src/function/cleanup/delete-old-families.ts
index 6001b3d..2f6bf6b 100644
--- a/src/function/cleanup/delete-old-families.ts
+++ b/src/function/cleanup/delete-old-families.ts
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
- * Copyright (C) 2019 - 2020 Jonas Lochmann
+ * Copyright (C) 2019 - 2023 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
@@ -26,9 +26,12 @@ export async function deleteOldFamilies (database: Database) {
if (oldFamilyIds.length > 0) {
const familyIdsToDelete = oldFamilyIds.slice(0, 256) /* limit to 256 families per execution */
- await deleteFamilies({
- database,
- familiyIds: familyIdsToDelete
+ await database.transaction(async (transaction) => {
+ await deleteFamilies({
+ database,
+ transaction,
+ familiyIds: familyIdsToDelete
+ })
})
}
}
diff --git a/src/util/mail.ts b/src/util/mail.ts
index 6d25126..02e7e7a 100644
--- a/src/util/mail.ts
+++ b/src/util/mail.ts
@@ -193,6 +193,19 @@ export const sendPasswordRecoveryUsedMail = async ({ receiver, locale }: {
})
}
+const accountDeletedMailSender = createMailTemplateSender('account-deleted')
+
+export const sendAccountDeletedMail = async ({ receiver }: {
+ receiver: string
+}) => {
+ await accountDeletedMailSender.sendMail({
+ receiver,
+ params: {
+ mailimprint
+ }
+ })
+}
+
function getMailSecurityText (locale: string) {
if (locale === 'de') {
return 'Achten Sie darauf, dass Ihr Kind/Ihre Kinder keinen Zugang zu der E-Mail-Adresse hat/haben, die Sie bei TimeLimit angegeben haben.'