Compare commits

..

12 Commits

Author SHA1 Message Date
standardci
04b52e6773 chore(release): publish new version
- @standardnotes/auth-server@1.141.14
 - @standardnotes/home-server@1.15.55
 - @standardnotes/syncing-server@1.95.19
2023-09-15 08:53:53 +00:00
Karol Sójko
2a1859e4be fix(auth): remove extensive logs from updating transitions 2023-09-15 10:35:50 +02:00
Karol Sójko
dd9a9c68cb fix(auth): upgrade simplewebauthn dependency (#826) 2023-09-15 10:35:14 +02:00
Karol Sójko
9147ff5d49 fix(syncing-server): remove unused index in mongodb 2023-09-14 14:18:20 +02:00
standardci
503b84531b chore(release): publish new version
- @standardnotes/auth-server@1.141.13
 - @standardnotes/home-server@1.15.54
 - @standardnotes/revisions-server@1.33.21
 - @standardnotes/syncing-server@1.95.18
2023-09-14 10:16:54 +00:00
Karol Sójko
fe8ca828fb fix(auth): set ttl for started and not picked up transitions to 10h 2023-09-14 11:57:21 +02:00
Karol Sójko
03a4a3f2ab fix: skip already updated items and revisions in integrity check (#825) 2023-09-14 11:54:30 +02:00
Karol Sójko
3a8607d146 fix(syncing-server): updating with missing creation date (#824) 2023-09-14 11:45:24 +02:00
standardci
93b6e65554 chore(release): publish new version
- @standardnotes/auth-server@1.141.12
 - @standardnotes/home-server@1.15.53
2023-09-13 10:22:30 +00:00
Karol Sójko
5984e4c3e7 fix(auth): remove re-triggering revisions transition 2023-09-13 11:51:34 +02:00
standardci
b4257c10ea chore(release): publish new version
- @standardnotes/auth-server@1.141.11
 - @standardnotes/home-server@1.15.52
2023-09-13 09:41:28 +00:00
Karol Sójko
c164bde847 fix(auth): passing transition timestamp 2023-09-13 11:03:32 +02:00
38 changed files with 291 additions and 219 deletions

106
.pnp.cjs generated
View File

@@ -3326,10 +3326,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@hexagon/base64", [\
["npm:1.1.26", {\
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.26-dbfda05df8-e42582ed12.zip/node_modules/@hexagon/base64/",\
["npm:1.1.27", {\
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.27-df6f264962-899fffaf54.zip/node_modules/@hexagon/base64/",\
"packageDependencies": [\
["@hexagon/base64", "npm:1.1.26"]\
["@hexagon/base64", "npm:1.1.27"]\
],\
"linkType": "HARD"\
}]\
@@ -5067,44 +5067,29 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/iso-webcrypto", [\
["npm:7.2.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-iso-webcrypto-npm-7.2.0-db7b12b859-b57899d0ad.zip/node_modules/@simplewebauthn/iso-webcrypto/",\
"packageDependencies": [\
["@simplewebauthn/iso-webcrypto", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@types/node", "npm:18.16.16"]\
],\
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/server", [\
["npm:7.2.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-7.2.0-f1ed5fde8a-2e37c87edd.zip/node_modules/@simplewebauthn/server/",\
["npm:8.1.1", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-8.1.1-106d3bd108-a07c2a067b.zip/node_modules/@simplewebauthn/server/",\
"packageDependencies": [\
["@simplewebauthn/server", "npm:7.2.0"],\
["@hexagon/base64", "npm:1.1.26"],\
["@simplewebauthn/server", "npm:8.1.1"],\
["@hexagon/base64", "npm:1.1.27"],\
["@peculiar/asn1-android", "npm:2.3.6"],\
["@peculiar/asn1-ecc", "npm:2.3.6"],\
["@peculiar/asn1-rsa", "npm:2.3.6"],\
["@peculiar/asn1-schema", "npm:2.3.6"],\
["@peculiar/asn1-x509", "npm:2.3.6"],\
["@simplewebauthn/iso-webcrypto", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@types/debug", "npm:4.1.8"],\
["@types/node", "npm:18.16.16"],\
["cbor-x", "npm:1.5.3"],\
["cross-fetch", "npm:3.1.6"],\
["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"]\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["cbor-x", "npm:1.5.4"],\
["cross-fetch", "npm:4.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/typescript-types", [\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-7.0.0-cc6ca20415-124238ea18.zip/node_modules/@simplewebauthn/typescript-types/",\
["npm:8.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-8.0.0-f3b313c27b-21e0b13268.zip/node_modules/@simplewebauthn/typescript-types/",\
"packageDependencies": [\
["@simplewebauthn/typescript-types", "npm:7.0.0"]\
["@simplewebauthn/typescript-types", "npm:8.0.0"]\
],\
"linkType": "HARD"\
}]\
@@ -5864,8 +5849,8 @@ const RAW_RUNTIME_STATE =
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@simplewebauthn/server", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@simplewebauthn/server", "npm:8.1.1"],\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
@@ -6720,16 +6705,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/debug", [\
["npm:4.1.8", {\
"packageLocation": "./.yarn/cache/@types-debug-npm-4.1.8-a04e2ca136-9c190e8129.zip/node_modules/@types/debug/",\
"packageDependencies": [\
["@types/debug", "npm:4.1.8"],\
["@types/ms", "npm:0.7.31"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/dotenv", [\
["npm:8.2.0", {\
"packageLocation": "./.yarn/cache/@types-dotenv-npm-8.2.0-f4d0e3d65b-13f90a36f7.zip/node_modules/@types/dotenv/",\
@@ -6956,15 +6931,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/ms", [\
["npm:0.7.31", {\
"packageLocation": "./.yarn/cache/@types-ms-npm-0.7.31-ea3b89342b-cccb52777b.zip/node_modules/@types/ms/",\
"packageDependencies": [\
["@types/ms", "npm:0.7.31"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/newrelic", [\
["npm:9.14.0", {\
"packageLocation": "./.yarn/cache/@types-newrelic-npm-9.14.0-4668da51a1-2ec951bd8f.zip/node_modules/@types/newrelic/",\
@@ -6982,13 +6948,6 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "HARD"\
}],\
["npm:18.16.16", {\
"packageLocation": "./.yarn/cache/@types-node-npm-18.16.16-8a41330dc3-946bd4d8e6.zip/node_modules/@types/node/",\
"packageDependencies": [\
["@types/node", "npm:18.16.16"]\
],\
"linkType": "HARD"\
}],\
["npm:20.2.5", {\
"packageLocation": "./.yarn/cache/@types-node-npm-20.2.5-0014d2d9ce-55e4f8d08e.zip/node_modules/@types/node/",\
"packageDependencies": [\
@@ -8706,10 +8665,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["cbor-x", [\
["npm:1.5.3", {\
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.3-1d452dd267-d4df85b339.zip/node_modules/cbor-x/",\
["npm:1.5.4", {\
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.4-2d5a649a4b-742aea498a.zip/node_modules/cbor-x/",\
"packageDependencies": [\
["cbor-x", "npm:1.5.3"],\
["cbor-x", "npm:1.5.4"],\
["cbor-extract", "npm:2.1.1"]\
],\
"linkType": "HARD"\
@@ -9434,11 +9393,11 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["cross-fetch", [\
["npm:3.1.6", {\
"packageLocation": "./.yarn/cache/cross-fetch-npm-3.1.6-cdb982d446-a8989fca82.zip/node_modules/cross-fetch/",\
["npm:4.0.0", {\
"packageLocation": "./.yarn/cache/cross-fetch-npm-4.0.0-9c67668db4-30e86b703a.zip/node_modules/cross-fetch/",\
"packageDependencies": [\
["cross-fetch", "npm:3.1.6"],\
["node-fetch", "virtual:0f92dfe7f9dc4fd492639d4a5b7805c2b27442bf599fd4f370b22a7966ba078f5d4525e2a8e8af29369f20e1833ed084bd52be59679efaa6c1c6c10cdbcd8baa#npm:2.6.11"]\
["cross-fetch", "npm:4.0.0"],\
["node-fetch", "virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0"]\
],\
"linkType": "HARD"\
}]\
@@ -14174,6 +14133,13 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["npm:2.7.0", {\
"packageLocation": "./.yarn/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip/node_modules/node-fetch/",\
"packageDependencies": [\
["node-fetch", "npm:2.7.0"]\
],\
"linkType": "SOFT"\
}],\
["npm:3.3.1", {\
"packageLocation": "./.yarn/cache/node-fetch-npm-3.3.1-576511fc5a-1d0c635bdf.zip/node_modules/node-fetch/",\
"packageDependencies": [\
@@ -14197,6 +14163,20 @@ const RAW_RUNTIME_STATE =
"encoding"\
],\
"linkType": "HARD"\
}],\
["virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0", {\
"packageLocation": "./.yarn/__virtual__/node-fetch-virtual-0ec1497d1c/0/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip/node_modules/node-fetch/",\
"packageDependencies": [\
["node-fetch", "virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0"],\
["@types/encoding", null],\
["encoding", null],\
["whatwg-url", "npm:5.0.0"]\
],\
"packagePeers": [\
"@types/encoding",\
"encoding"\
],\
"linkType": "HARD"\
}]\
]],\
["node-gyp", [\

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.141.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.13...@standardnotes/auth-server@1.141.14) (2023-09-15)
### Bug Fixes
* **auth:** remove extensive logs from updating transitions ([2a1859e](https://github.com/standardnotes/server/commit/2a1859e4beff4cc7c4348ebbff8357a8e061bf5e))
* **auth:** upgrade simplewebauthn dependency ([#826](https://github.com/standardnotes/server/issues/826)) ([dd9a9c6](https://github.com/standardnotes/server/commit/dd9a9c68cb431a61700a8e5d8247eb1b505a9d62))
## [1.141.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.12...@standardnotes/auth-server@1.141.13) (2023-09-14)
### Bug Fixes
* **auth:** set ttl for started and not picked up transitions to 10h ([fe8ca82](https://github.com/standardnotes/server/commit/fe8ca828fb37306e0e5056627e67366885e86861))
## [1.141.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.11...@standardnotes/auth-server@1.141.12) (2023-09-13)
### Bug Fixes
* **auth:** remove re-triggering revisions transition ([5984e4c](https://github.com/standardnotes/server/commit/5984e4c3e7e550e5ed53805bde1e6dabcbe54da8))
## [1.141.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.10...@standardnotes/auth-server@1.141.11) (2023-09-13)
### Bug Fixes
* **auth:** passing transition timestamp ([c164bde](https://github.com/standardnotes/server/commit/c164bde847b5974e74fd439f0d439526ad439443))
## [1.141.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.9...@standardnotes/auth-server@1.141.10) (2023-09-13)
### Bug Fixes

View File

@@ -10,7 +10,7 @@ import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { TransitionStatusRepositoryInterface } from '../src/Domain/Transition/TransitionStatusRepositoryInterface'
import { TimerInterface } from '@standardnotes/time'
const inputArgs = process.argv.slice(2)
const startDateString = inputArgs[0]
@@ -18,23 +18,28 @@ const endDateString = inputArgs[1]
const requestTransition = async (
userRepository: UserRepositoryInterface,
transitionStatusRepository: TransitionStatusRepositoryInterface,
logger: Logger,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
timer: TimerInterface,
): Promise<void> => {
const startDate = new Date(startDateString)
const endDate = new Date(endDateString)
const users = await userRepository.findAllCreatedBetween(startDate, endDate)
logger.info(`Found ${users.length} users created between ${startDateString} and ${endDateString}`)
const timestamp = timer.getTimestampInMicroseconds()
logger.info(
`[TRANSITION ${timestamp}] Found ${users.length} users created between ${startDateString} and ${endDateString}`,
)
let usersTriggered = 0
for (const user of users) {
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
userUuid: user.uuid,
type: 'items',
timestamp,
})
usersTriggered += 1
@@ -43,22 +48,8 @@ const requestTransition = async (
}
logger.info(
`Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
`[TRANSITION ${timestamp}] Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
)
const revisionStatuses = await transitionStatusRepository.getStatuses('revisions')
const failedStatuses = revisionStatuses.filter((status) => status.status === 'FAILED')
logger.info(`Found ${failedStatuses.length} failed revision transitions`)
for (const status of failedStatuses) {
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
userUuid: status.userUuid,
type: 'revisions',
})
await domainEventPublisher.publish(transitionRequestedEvent)
}
}
const container = new ContainerConfigLoader('worker')
@@ -75,13 +66,9 @@ void container.load().then((container) => {
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
const transitionStatusRepository: TransitionStatusRepositoryInterface = container.get(
TYPES.Auth_TransitionStatusRepository,
)
const timer = container.get<TimerInterface>(TYPES.Auth_Timer)
Promise.resolve(
requestTransition(userRepository, transitionStatusRepository, logger, domainEventFactory, domainEventPublisher),
)
Promise.resolve(requestTransition(userRepository, logger, domainEventFactory, domainEventPublisher, timer))
.then(() => {
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.141.10",
"version": "1.141.14",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -40,8 +40,8 @@
"@aws-sdk/client-sqs": "^3.332.0",
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@simplewebauthn/server": "^7.2.0",
"@simplewebauthn/typescript-types": "^7.0.0",
"@simplewebauthn/server": "^8.1.1",
"@simplewebauthn/typescript-types": "^8.0.0",
"@standardnotes/api": "^1.26.26",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",

View File

@@ -943,7 +943,6 @@ export class ContainerConfigLoader {
new UpdateTransitionStatus(
container.get<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository),
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container

View File

@@ -33,7 +33,11 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
createTransitionRequestedEvent(dto: { userUuid: string; type: 'items' | 'revisions' }): TransitionRequestedEvent {
createTransitionRequestedEvent(dto: {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}): TransitionRequestedEvent {
return {
type: 'TRANSITION_REQUESTED',
createdAt: this.timer.getUTCDate(),
@@ -44,10 +48,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
},
origin: DomainEventService.Auth,
},
payload: {
timestamp: this.timer.getTimestampInMicroseconds(),
...dto,
},
payload: dto,
}
}

View File

@@ -90,5 +90,9 @@ export interface DomainEventFactoryInterface {
}): StatisticPersistenceRequestedEvent
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent
createSessionRefreshedEvent(dto: { userUuid: string }): SessionRefreshedEvent
createTransitionRequestedEvent(dto: { userUuid: string; type: 'items' | 'revisions' }): TransitionRequestedEvent
createTransitionRequestedEvent(dto: {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}): TransitionRequestedEvent
}

View File

@@ -35,7 +35,7 @@ export class GenerateAuthenticatorAuthenticationOptions
.update(`u2f-selector-${dto.username}${this.pseudoKeyParamsKey}`)
.digest('base64url')
const options = generateAuthenticationOptions({
const options = await generateAuthenticationOptions({
allowCredentials: [
{
id: Buffer.from(credentialIdHash),
@@ -56,7 +56,7 @@ export class GenerateAuthenticatorAuthenticationOptions
const userUuid = userUuidOrError.getValue()
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
const options = generateAuthenticationOptions({
const options = await generateAuthenticationOptions({
allowCredentials: authenticators.map((authenticator) => ({
id: authenticator.props.credentialId,
type: 'public-key',

View File

@@ -52,7 +52,7 @@ export class GenerateAuthenticatorRegistrationOptions
}
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
const options = generateRegistrationOptions({
const options = await generateRegistrationOptions({
rpID: this.relyingPartyId,
rpName: this.relyingPartyName,
userID: userUuid.value,

View File

@@ -3,19 +3,14 @@ import { RoleName, Uuid } from '@standardnotes/domain-core'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatus } from './UpdateTransitionStatus'
import { Logger } from 'winston'
describe('UpdateTransitionStatus', () => {
let transitionStatusRepository: TransitionStatusRepositoryInterface
let roleService: RoleServiceInterface
let logger: Logger
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService, logger)
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService)
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.removeStatus = jest.fn()
transitionStatusRepository.updateStatus = jest.fn()

View File

@@ -2,13 +2,11 @@ import { Result, RoleName, UseCaseInterface, Uuid } from '@standardnotes/domain-
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatusDTO } from './UpdateTransitionStatusDTO'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { Logger } from 'winston'
export class UpdateTransitionStatus implements UseCaseInterface<void> {
constructor(
private transitionStatusRepository: TransitionStatusRepositoryInterface,
private roleService: RoleServiceInterface,
private logger: Logger,
) {}
async execute(dto: UpdateTransitionStatusDTO): Promise<Result<void>> {
@@ -30,24 +28,6 @@ export class UpdateTransitionStatus implements UseCaseInterface<void> {
await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
}
const itemStatuses = await this.transitionStatusRepository.getStatuses('items')
const itemsStartedStatusesCount = itemStatuses.filter((status) => status.status === 'STARTED').length
const itemsInProgressStatusesCount = itemStatuses.filter((status) => status.status === 'IN_PROGRESS').length
const itemsFailedStatusesCount = itemStatuses.filter((status) => status.status === 'FAILED').length
this.logger.info(
`[TRANSITION ${dto.transitionTimestamp}] Items transition statuses: ${itemsStartedStatusesCount} started, ${itemsInProgressStatusesCount} in progress, ${itemsFailedStatusesCount} failed`,
)
const revisionStatuses = await this.transitionStatusRepository.getStatuses('revisions')
const revisionsStartedStatusesCount = revisionStatuses.filter((status) => status.status === 'STARTED').length
const revisionsInProgressStatusesCount = revisionStatuses.filter((status) => status.status === 'IN_PROGRESS').length
const revisionsFailedStatusesCount = revisionStatuses.filter((status) => status.status === 'FAILED').length
this.logger.info(
`[TRANSITION ${dto.transitionTimestamp}] Revisions transition statuses: ${revisionsStartedStatusesCount} started, ${revisionsInProgressStatusesCount} in progress, ${revisionsFailedStatusesCount} failed`,
)
return Result.ok()
}
}

View File

@@ -28,7 +28,9 @@ export class RedisTransitionStatusRepository implements TransitionStatusReposito
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED',
): Promise<void> {
if (status === 'IN_PROGRESS') {
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, 7200, status)
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, 7_200, status)
} else if (status === 'STARTED') {
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, 36_000, status)
} else {
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.55](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.54...@standardnotes/home-server@1.15.55) (2023-09-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.54](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.53...@standardnotes/home-server@1.15.54) (2023-09-14)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.53](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.52...@standardnotes/home-server@1.15.53) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.52](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.51...@standardnotes/home-server@1.15.52) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.51](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.50...@standardnotes/home-server@1.15.51) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.15.51",
"version": "1.15.55",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.33.21](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.20...@standardnotes/revisions-server@1.33.21) (2023-09-14)
### Bug Fixes
* skip already updated items and revisions in integrity check ([#825](https://github.com/standardnotes/server/issues/825)) ([03a4a3f](https://github.com/standardnotes/server/commit/03a4a3f2abc0b4e09942ba39dbd227524068dfb6))
## [1.33.20](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.19...@standardnotes/revisions-server@1.33.20) (2023-09-13)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.33.20",
"version": "1.33.21",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -87,6 +87,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid,
newRevisionsInSecondaryCount,
updatedRevisionsInSecondary,
)
if (integrityCheckResult.isFailed()) {
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
@@ -144,6 +145,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
)
) {
this.logger.info(
`Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`,
)
continue
}
@@ -285,6 +290,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid: Uuid,
newRevisionsInSecondaryCount: number,
updatedRevisionsInSecondary: Revision[],
): Promise<Result<boolean>> {
try {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
@@ -314,6 +320,17 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`)
}
if (
updatedRevisionsInSecondary.find(
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
)
) {
this.logger.info(
`Skipping integrity check for revision ${revision.id.toString()} as it was updated in secondary database`,
)
continue
}
if (!revision.isIdenticalTo(revisionInSecondary)) {
return Result.fail(
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.95.19](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.18...@standardnotes/syncing-server@1.95.19) (2023-09-15)
### Bug Fixes
* **syncing-server:** remove unused index in mongodb ([9147ff5](https://github.com/standardnotes/syncing-server-js/commit/9147ff5d49c507d943f4f8c6775f7c1fff878b0f))
## [1.95.18](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.17...@standardnotes/syncing-server@1.95.18) (2023-09-14)
### Bug Fixes
* skip already updated items and revisions in integrity check ([#825](https://github.com/standardnotes/syncing-server-js/issues/825)) ([03a4a3f](https://github.com/standardnotes/syncing-server-js/commit/03a4a3f2abc0b4e09942ba39dbd227524068dfb6))
* **syncing-server:** updating with missing creation date ([#824](https://github.com/standardnotes/syncing-server-js/issues/824)) ([3a8607d](https://github.com/standardnotes/syncing-server-js/commit/3a8607d1465cabedad68b84c753e407342e60d20))
## [1.95.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.16...@standardnotes/syncing-server@1.95.17) (2023-09-13)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.95.17",
"version": "1.95.19",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -298,7 +298,7 @@ describe('UpdateExistingItem', () => {
expect(itemRepository.save).toHaveBeenCalled()
})
it('should return error if created at time is not give in any form', async () => {
it('should fallback to updated at timestamp if created at time is not give in any form', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
@@ -308,13 +308,59 @@ describe('UpdateExistingItem', () => {
...itemHash1.props,
created_at: undefined,
created_at_timestamp: undefined,
updated_at_timestamp: 123,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
roleNames: [RoleName.NAMES.CoreUser],
})
expect(result.isFailed()).toBeTruthy()
expect(result.isFailed()).toBeFalsy()
expect(itemRepository.save).toHaveBeenCalled()
})
it('should fallback to updated at date if created at time is not give in any form', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
existingItem: item1,
onGoingRevisionsTransition: false,
itemHash: ItemHash.create({
...itemHash1.props,
created_at: undefined,
created_at_timestamp: undefined,
updated_at_timestamp: undefined,
updated_at: '2020-01-01T00:00:00.000Z',
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
roleNames: [RoleName.NAMES.CoreUser],
})
expect(result.isFailed()).toBeFalsy()
expect(itemRepository.save).toHaveBeenCalled()
})
it('should fallback to 0 if created at and update at time is not give in any form', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
existingItem: item1,
onGoingRevisionsTransition: false,
itemHash: ItemHash.create({
...itemHash1.props,
created_at: undefined,
created_at_timestamp: undefined,
updated_at_timestamp: undefined,
updated_at: undefined,
}).getValue(),
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
roleNames: [RoleName.NAMES.CoreUser],
})
expect(result.isFailed()).toBeFalsy()
expect(itemRepository.save).toHaveBeenCalled()
})
it('should return error if dates could not be created from timestamps', async () => {

View File

@@ -23,6 +23,7 @@ import { SharedVaultOperationOnItem } from '../../../SharedVault/SharedVaultOper
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
import { RemoveNotificationsForUser } from '../../Messaging/RemoveNotificationsForUser/RemoveNotificationsForUser'
import { ItemRepositoryResolverInterface } from '../../../Item/ItemRepositoryResolverInterface'
import { ItemHash } from '../../../Item/ItemHash'
export class UpdateExistingItem implements UseCaseInterface<Item> {
constructor(
@@ -115,17 +116,7 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
)
const updatedAtDate = this.timer.convertMicrosecondsToDate(updatedAtTimestamp)
let createdAtTimestamp: number
let createdAtDate: Date
if (dto.itemHash.props.created_at_timestamp) {
createdAtTimestamp = dto.itemHash.props.created_at_timestamp
createdAtDate = this.timer.convertMicrosecondsToDate(createdAtTimestamp)
} else if (dto.itemHash.props.created_at) {
createdAtTimestamp = this.timer.convertStringDateToMicroseconds(dto.itemHash.props.created_at)
createdAtDate = this.timer.convertStringDateToDate(dto.itemHash.props.created_at)
} else {
return Result.fail('Created at timestamp is required.')
}
const { createdAtDate, createdAtTimestamp } = this.determineCreatedAt(dto.itemHash)
const datesOrError = Dates.create(createdAtDate, updatedAtDate)
if (datesOrError.isFailed()) {
@@ -221,6 +212,29 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
return Result.ok(dto.existingItem)
}
private determineCreatedAt(itemHash: ItemHash): { createdAtDate: Date; createdAtTimestamp: number } {
let createdAtTimestamp: number
let createdAtDate: Date
if (itemHash.props.created_at_timestamp) {
createdAtTimestamp = itemHash.props.created_at_timestamp
createdAtDate = this.timer.convertMicrosecondsToDate(createdAtTimestamp)
} else if (itemHash.props.created_at) {
createdAtTimestamp = this.timer.convertStringDateToMicroseconds(itemHash.props.created_at)
createdAtDate = this.timer.convertStringDateToDate(itemHash.props.created_at)
} else if (itemHash.props.updated_at_timestamp) {
createdAtTimestamp = itemHash.props.updated_at_timestamp
createdAtDate = this.timer.convertMicrosecondsToDate(itemHash.props.updated_at_timestamp)
} else if (itemHash.props.updated_at) {
createdAtTimestamp = this.timer.convertStringDateToMicroseconds(itemHash.props.updated_at)
createdAtDate = this.timer.convertStringDateToDate(itemHash.props.updated_at)
} else {
createdAtTimestamp = 0
createdAtDate = new Date(0)
}
return { createdAtDate, createdAtTimestamp }
}
private async addNotificationsAndPublishEvents(
userUuid: Uuid,
sharedVaultOperation: SharedVaultOperationOnItem | null,

View File

@@ -82,6 +82,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid,
newItemsInSecondaryCount,
updatedItemsInSecondary,
)
if (integrityCheckResult.isFailed()) {
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
@@ -209,6 +210,8 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
for (const item of items) {
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
this.logger.info(`Skipping saving item ${item.uuid.value} as it was updated in secondary database`)
continue
}
await (this.secondaryItemRepository as ItemRepositoryInterface).save(item)
@@ -234,6 +237,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid: Uuid,
newItemsInSecondaryCount: number,
updatedItemsInSecondary: Item[],
): Promise<Result<boolean>> {
try {
const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
@@ -265,6 +269,13 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
return Result.fail(`Item ${item.uuid.value} not found in secondary database`)
}
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
this.logger.info(
`Skipping integrity check for item ${item.uuid.value} as it was updated in secondary database`,
)
continue
}
if (!item.isIdenticalTo(itemInSecondary)) {
return Result.fail(
`Item ${

View File

@@ -18,7 +18,6 @@ export class MongoDBItem {
declare content: string | null
@Column()
@Index('index_items_on_content_type')
declare contentType: string | null
@Column()

127
yarn.lock
View File

@@ -2659,10 +2659,10 @@ __metadata:
languageName: node
linkType: hard
"@hexagon/base64@npm:^1.1.25":
version: 1.1.26
resolution: "@hexagon/base64@npm:1.1.26"
checksum: e42582ed12465bffaf96307c9d5b7dfd36166ec4dc41a1838b9a560c90c9d136d006099a205e1684fb0dc18002cc5af51a49dd7b81a7c4b86798372d6ee26af3
"@hexagon/base64@npm:^1.1.27":
version: 1.1.27
resolution: "@hexagon/base64@npm:1.1.27"
checksum: 899fffaf54b291e1df997bf33dbf6e068fcfbd83155adc114e14bcb9c1e36c5f820dfaaee3d0c2409f7e84efa4352f51655eac8bec4c2432fca443bf179bce8d
languageName: node
linkType: hard
@@ -3793,7 +3793,7 @@ __metadata:
languageName: node
linkType: hard
"@peculiar/asn1-android@npm:^2.3.3":
"@peculiar/asn1-android@npm:^2.3.6":
version: 2.3.6
resolution: "@peculiar/asn1-android@npm:2.3.6"
dependencies:
@@ -3804,7 +3804,7 @@ __metadata:
languageName: node
linkType: hard
"@peculiar/asn1-ecc@npm:^2.3.4":
"@peculiar/asn1-ecc@npm:^2.3.6":
version: 2.3.6
resolution: "@peculiar/asn1-ecc@npm:2.3.6"
dependencies:
@@ -3816,7 +3816,7 @@ __metadata:
languageName: node
linkType: hard
"@peculiar/asn1-rsa@npm:^2.3.4":
"@peculiar/asn1-rsa@npm:^2.3.6":
version: 2.3.6
resolution: "@peculiar/asn1-rsa@npm:2.3.6"
dependencies:
@@ -3828,7 +3828,7 @@ __metadata:
languageName: node
linkType: hard
"@peculiar/asn1-schema@npm:^2.3.3, @peculiar/asn1-schema@npm:^2.3.6":
"@peculiar/asn1-schema@npm:^2.3.6":
version: 2.3.6
resolution: "@peculiar/asn1-schema@npm:2.3.6"
dependencies:
@@ -3839,7 +3839,7 @@ __metadata:
languageName: node
linkType: hard
"@peculiar/asn1-x509@npm:^2.3.4, @peculiar/asn1-x509@npm:^2.3.6":
"@peculiar/asn1-x509@npm:^2.3.6":
version: 2.3.6
resolution: "@peculiar/asn1-x509@npm:2.3.6"
dependencies:
@@ -3987,41 +3987,27 @@ __metadata:
languageName: node
linkType: hard
"@simplewebauthn/iso-webcrypto@npm:^7.2.0":
version: 7.2.0
resolution: "@simplewebauthn/iso-webcrypto@npm:7.2.0"
"@simplewebauthn/server@npm:^8.1.1":
version: 8.1.1
resolution: "@simplewebauthn/server@npm:8.1.1"
dependencies:
"@simplewebauthn/typescript-types": "npm:*"
"@types/node": "npm:^18.11.9"
checksum: b57899d0ada391507ce8f4601328ed62df5d09f75f6e91b018278631270a96f37ceab95f9e824c9555dd05820e5a99ac386ed067db7902d37bdb6d995fbd7eaf
"@hexagon/base64": "npm:^1.1.27"
"@peculiar/asn1-android": "npm:^2.3.6"
"@peculiar/asn1-ecc": "npm:^2.3.6"
"@peculiar/asn1-rsa": "npm:^2.3.6"
"@peculiar/asn1-schema": "npm:^2.3.6"
"@peculiar/asn1-x509": "npm:^2.3.6"
"@simplewebauthn/typescript-types": "npm:^8.0.0"
cbor-x: "npm:^1.5.2"
cross-fetch: "npm:^4.0.0"
checksum: a07c2a067b25b7f4afe215dcf81e0280aa5c382f5eb3cb1ef9327c6dc84c9618ed5774fc61bbc5717b8633dd5b8b4c41c7800cb32e83c6a4b1d2d1e1e50f5250
languageName: node
linkType: hard
"@simplewebauthn/server@npm:^7.2.0":
version: 7.2.0
resolution: "@simplewebauthn/server@npm:7.2.0"
dependencies:
"@hexagon/base64": "npm:^1.1.25"
"@peculiar/asn1-android": "npm:^2.3.3"
"@peculiar/asn1-ecc": "npm:^2.3.4"
"@peculiar/asn1-rsa": "npm:^2.3.4"
"@peculiar/asn1-schema": "npm:^2.3.3"
"@peculiar/asn1-x509": "npm:^2.3.4"
"@simplewebauthn/iso-webcrypto": "npm:^7.2.0"
"@simplewebauthn/typescript-types": "npm:*"
"@types/debug": "npm:^4.1.7"
"@types/node": "npm:^18.11.9"
cbor-x: "npm:^1.4.1"
cross-fetch: "npm:^3.1.5"
debug: "npm:^4.3.2"
checksum: 2e37c87edd05abace8ba8c5b1f4e2cb4adb9ec4dcf0b237d25f375f35538d25e31cc0ae196029006cf5124c983216a3cf69127732942863d2960bd72ed5783c4
languageName: node
linkType: hard
"@simplewebauthn/typescript-types@npm:*, @simplewebauthn/typescript-types@npm:^7.0.0":
version: 7.0.0
resolution: "@simplewebauthn/typescript-types@npm:7.0.0"
checksum: 124238ea1859c80761c4cdbf19107e2e8e96fdefa64affb55fb4fc67d1ac5e3354c3098c908729d2de439a633115d98da77ded7289286fe576559306fa933815
"@simplewebauthn/typescript-types@npm:^8.0.0":
version: 8.0.0
resolution: "@simplewebauthn/typescript-types@npm:8.0.0"
checksum: 21e0b13268f237d7cd6ecdc6cdceb884ddcc85e18a3554b65d10da4f502056aefa810db94f504beaad96b4ac0c7861a022954dcc6aa8721d4215571c2c3dcdf5
languageName: node
linkType: hard
@@ -4744,8 +4730,8 @@ __metadata:
"@cbor-extract/cbor-extract-linux-arm64": "npm:^2.1.1"
"@cbor-extract/cbor-extract-linux-x64": "npm:^2.1.1"
"@newrelic/winston-enricher": "npm:^4.0.1"
"@simplewebauthn/server": "npm:^7.2.0"
"@simplewebauthn/typescript-types": "npm:^7.0.0"
"@simplewebauthn/server": "npm:^8.1.1"
"@simplewebauthn/typescript-types": "npm:^8.0.0"
"@standardnotes/api": "npm:^1.26.26"
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-core": "workspace:^"
@@ -5589,15 +5575,6 @@ __metadata:
languageName: node
linkType: hard
"@types/debug@npm:^4.1.7":
version: 4.1.8
resolution: "@types/debug@npm:4.1.8"
dependencies:
"@types/ms": "npm:*"
checksum: 9c190e812984e0f6e02dfdfb0c7a3081a55cf3fc712a4e059336bd9f8329db70211eb851ce409311520876549cff2c4785ce48dd4c9fef8e48549c87bec29ded
languageName: node
linkType: hard
"@types/dotenv@npm:^8.2.0":
version: 8.2.0
resolution: "@types/dotenv@npm:8.2.0"
@@ -5792,13 +5769,6 @@ __metadata:
languageName: node
linkType: hard
"@types/ms@npm:*":
version: 0.7.31
resolution: "@types/ms@npm:0.7.31"
checksum: cccb52777bb683c65ac5bab61351cd3910c9ce3512b1d903a591fc9694bb83afad6e48bf0beee5b47b6a8b620a05f5d82f8febfd55de05e7d9eb93586cc196c8
languageName: node
linkType: hard
"@types/newrelic@npm:^9.14.0":
version: 9.14.0
resolution: "@types/newrelic@npm:9.14.0"
@@ -5820,13 +5790,6 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^18.11.9":
version: 18.16.16
resolution: "@types/node@npm:18.16.16"
checksum: 946bd4d8e6fa54220e4193bc594de8a2e138e6afebb6efb7d862d98e30ced25a19476a6f47c81e690b9ac77f616f64217e0bcf4811916ccd9b5935e5bea0e4a0
languageName: node
linkType: hard
"@types/node@npm:^20.5.7":
version: 20.5.7
resolution: "@types/node@npm:20.5.7"
@@ -7208,15 +7171,15 @@ __metadata:
languageName: node
linkType: hard
"cbor-x@npm:^1.4.1":
version: 1.5.3
resolution: "cbor-x@npm:1.5.3"
"cbor-x@npm:^1.5.2":
version: 1.5.4
resolution: "cbor-x@npm:1.5.4"
dependencies:
cbor-extract: "npm:^2.1.1"
dependenciesMeta:
cbor-extract:
optional: true
checksum: d4df85b33969826f4c96a4b4a8fbe03132fb0817fba876f16d41ad6d1a7d2668ec04c923f313220506029cc2b5ab212901ba24b4594d0115e0f527ef31506fbf
checksum: 742aea498abfe004a7ff4db2a1c0e00d9e9c1d89db4ad9aa94a9b886cd2ce10a133f20e32788c83696eef368e18c2b5bc82e4b1480c5af91937816a5630989d6
languageName: node
linkType: hard
@@ -7848,12 +7811,12 @@ __metadata:
languageName: node
linkType: hard
"cross-fetch@npm:^3.1.5":
version: 3.1.6
resolution: "cross-fetch@npm:3.1.6"
"cross-fetch@npm:^4.0.0":
version: 4.0.0
resolution: "cross-fetch@npm:4.0.0"
dependencies:
node-fetch: "npm:^2.6.11"
checksum: a8989fca821cae97520976d00f85ce7c3ab8af7e00cc06c94fd94c49ada6847f4cdeabca8e0ebd4aa6c7343f70bea7e0c64d5910b846aab218136a450585aa61
node-fetch: "npm:^2.6.12"
checksum: 30e86b703a455baca17b7f2088fdd88b71193b39e7cb61f3385511dc6064b7741c816329c0abff8a74d306969455c8797131d056518a981fd4d2424ecd4ab451
languageName: node
linkType: hard
@@ -12007,7 +11970,21 @@ __metadata:
languageName: node
linkType: hard
"node-fetch@npm:^2.6.11, node-fetch@npm:^2.6.7":
"node-fetch@npm:^2.6.12":
version: 2.7.0
resolution: "node-fetch@npm:2.7.0"
dependencies:
whatwg-url: "npm:^5.0.0"
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
checksum: a3ad7889038bf6c49046272515d4f0e3167088b40fd37e1cc6eeea745f5a68cec798d55ac3210e2bc51891cb745e3dc30a734cc5f4b4df764f45886881b198b1
languageName: node
linkType: hard
"node-fetch@npm:^2.6.7":
version: 2.6.11
resolution: "node-fetch@npm:2.6.11"
dependencies: