Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add base class for Stores #2493

Merged
merged 12 commits into from
May 16, 2024
4 changes: 2 additions & 2 deletions .github/workflows/e2e-mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:

- name: FILE_NAME env
working-directory: ./packages/desktop/dist
run: echo "FILE_NAME="Quiet-$VERSION-arm64.dmg"" >> $GITHUB_ENV
run: echo "FILE_NAME="Quiet-$VERSION.dmg"" >> $GITHUB_ENV

- name: List dist dir content
working-directory: ./packages/desktop/dist
Expand All @@ -50,7 +50,7 @@ jobs:
run: hdiutil mount $FILE_NAME

- name: Add App file to applications
run: cd ~ && cp -R "/Volumes/Quiet $VERSION-arm64/Quiet.app" /Applications
run: cd ~ && cp -R "/Volumes/Quiet $VERSION/Quiet.app" /Applications

- name: Run invitation link test - Includes 2 separate application clients
uses: nick-fields/retry@14672906e672a08bd6eeb15720e9ed3ce869cdd4 # v2.9.0
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* Cleanup data directory at end of e2e tests
* Update github workflows for PR gating ([#2487](https://github.com/TryQuiet/quiet/issues/2487))
* Don't create duplicate CSRs when joining a community under certain circumstances ([#2321](https://github.com/TryQuiet/quiet/issues/2321))
* Add abstract base classes for stores ([#2407](https://github.com/TryQuiet/quiet/issues/2407))

[2.2.0]

Expand Down
5 changes: 0 additions & 5 deletions packages/backend/src/nest/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ export interface PublicChannelsRepo {
eventsAttached: boolean
}

export interface DirectMessagesRepo {
EmiM marked this conversation as resolved.
Show resolved Hide resolved
db: EventStore<string>
eventsAttached: boolean
}

export type ChannelInfoResponse = Record<string, PublicChannel>

export class StorageOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ describe('RegistrationService', () => {
const peerId = await PeerId.create()
const ipfs = await create()
const loadAllCertificates = async () => {
return await certificatesStore.loadAllCertificates()
return await certificatesStore.getEntries()
}
const saveCertificate = async (payload: SaveCertificatePayload) => {
await certificatesStore.addCertificate(payload.certificate)
await certificatesStore.addEntry(payload.certificate)
}

await orbitDb.create(peerId, ipfs)
Expand Down Expand Up @@ -202,7 +202,7 @@ describe('RegistrationService', () => {

await new Promise(r => setTimeout(r, 2000))

expect((await certificatesStore.loadAllCertificates()).length).toEqual(1)
expect((await certificatesStore.getEntries()).length).toEqual(1)

await orbitDb.stop()
await ipfs.stop()
Expand Down
43 changes: 43 additions & 0 deletions packages/backend/src/nest/storage/base.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import KeyValueStore from 'orbit-db-kvstore'
import Store from 'orbit-db-store'
import EventStore from 'orbit-db-eventstore'
import { EventEmitter } from 'events'
import Logger from '../common/logger'

const logger = Logger('store')

abstract class StoreBase<V, S extends KeyValueStore<V> | EventStore<V>> extends EventEmitter {
protected abstract store: S | undefined

getStore() {
if (!this.store) {
throw new Error('Store not initialized')
}
return this.store
}

getAddress(): Store['address'] {
return this.getStore().address
}

async close(): Promise<void> {
logger('Closing', this.getAddress().path)
await this.store?.close()
EmiM marked this conversation as resolved.
Show resolved Hide resolved
logger('Closed', this.getAddress().path)
}

abstract init(): Promise<void>
abstract clean(): void
}

export abstract class KeyValueStoreBase<V> extends StoreBase<V, KeyValueStore<V>> {
protected store: KeyValueStore<V> | undefined
abstract setEntry(key: string, value: V): Promise<V>
abstract getEntry(key?: string): V | null
}

export abstract class EventStoreBase<V> extends StoreBase<V, EventStore<V>> {
protected store: EventStore<V> | undefined
abstract addEntry(value: V): Promise<string>
abstract getEntries(): Promise<V[]>
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ describe('CertificatesRequestsStore', () => {
]

for (const csr of allCsrs) {
await certificatesRequestsStore.addUserCsr(csr)
await certificatesRequestsStore.addEntry(csr)
// This should not be there, there's bug in orbitdb, it breaks if we add entries without artificial sleep, tho it's awaited.
// https://github.com/TryQuiet/quiet/issues/2121
await new Promise<void>(resolve => setTimeout(() => resolve(), 500))
}

await certificatesRequestsStore.getCsrs()
await certificatesRequestsStore.getEntries()

const filteredCsrs = await certificatesRequestsStore.getCsrs()
const filteredCsrs = await certificatesRequestsStore.getEntries()

expect(filteredCsrs.length).toEqual(allCsrs.length - 1)
expect(filteredCsrs).toEqual([
Expand All @@ -103,7 +103,7 @@ describe('CertificatesRequestsStore', () => {
const spy = jest.fn()

certificatesRequestsStore.on(StorageEvents.CSRS_STORED, spy)
await replicatedEvent(certificatesRequestsStore.store)
await replicatedEvent(certificatesRequestsStore.getStore())

expect(spy).toBeCalledTimes(1)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { getCrypto } from 'pkijs'
import { EventEmitter } from 'events'
import EventStore from 'orbit-db-eventstore'
import { NoCryptoEngineError } from '@quiet/types'
import { loadCSR, keyFromCertificate } from '@quiet/identity'
import { CsrReplicatedPromiseValues, StorageEvents } from '../storage.types'
import { StorageEvents } from '../storage.types'
import { validate } from 'class-validator'
import { UserCsrData } from '../../registration/registration.functions'
import { Injectable } from '@nestjs/common'
import { OrbitDb } from '../orbitDb/orbitDb.service'
import Logger from '../../common/logger'
import { EventStoreBase } from '../base.store'

@Injectable()
export class CertificatesRequestsStore extends EventEmitter {
public store: EventStore<string>

private readonly logger = Logger(CertificatesRequestsStore.name)
export class CertificatesRequestsStore extends EventStoreBase<string> {
protected readonly logger = Logger(CertificatesRequestsStore.name)

constructor(private readonly orbitDbService: OrbitDb) {
super()
Expand Down Expand Up @@ -46,23 +43,14 @@ export class CertificatesRequestsStore extends EventEmitter {

public async loadedCertificateRequests() {
this.emit(StorageEvents.CSRS_STORED, {
csrs: await this.getCsrs(),
csrs: await this.getEntries(),
})
}

public async close() {
this.logger('Closing certificate requests DB')
await this.store?.close()
this.logger('Closed certificate requests DB')
}

public getAddress() {
return this.store?.address
}

public async addUserCsr(csr: string) {
await this.store.add(csr)
return true
public async addEntry(csr: string): Promise<string> {
this.logger('Adding CSR to database')
await this.store?.add(csr)
return csr
}

public static async validateUserCsr(csr: string) {
Expand All @@ -88,9 +76,9 @@ export class CertificatesRequestsStore extends EventEmitter {
return validationErrors
}

public async getCsrs() {
public async getEntries() {
const filteredCsrsMap: Map<string, string> = new Map()
const allEntries = this.store
const allEntries = this.getStore()
.iterator({ limit: -1 })
.collect()
.map(e => {
Expand Down Expand Up @@ -122,9 +110,7 @@ export class CertificatesRequestsStore extends EventEmitter {
}

public clean() {
// FIXME: Add correct typings on object fields.

// @ts-ignore
this.logger('Cleaning certificates requests store')
this.store = undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ describe('CertificatesStore', () => {

certificatesStore.updateMetadata(communityMetadata)

await certificatesStore.addCertificate(certificate)
await certificatesStore.addEntry(certificate)

const certificates = await certificatesStore.getCertificates()
const certificates = await certificatesStore.getEntries()

expect(certificates).toContain(certificate)
})
Expand All @@ -146,9 +146,9 @@ describe('CertificatesStore', () => {

certificatesStore.updateMetadata(communityMetadata)

await certificatesStore.addCertificate(certificate)
await certificatesStore.addEntry(certificate)

const certificates = await certificatesStore.getCertificates()
const certificates = await certificatesStore.getEntries()

expect(certificates).not.toContain(certificate)
})
Expand All @@ -159,9 +159,9 @@ describe('CertificatesStore', () => {

certificatesStore.updateMetadata(communityMetadata)

jest.spyOn(certificatesStore, 'getCertificates').mockResolvedValue([certificate1, certificate2])
jest.spyOn(certificatesStore, 'getEntries').mockResolvedValue([certificate1, certificate2])

const certificates = await certificatesStore.loadAllCertificates()
const certificates = await certificatesStore.getEntries()

expect(certificates).toContain(certificate1)
expect(certificates).toContain(certificate2)
Expand All @@ -172,7 +172,7 @@ describe('CertificatesStore', () => {

certificatesStore.updateMetadata(communityMetadata)

await certificatesStore.addCertificate(certificate)
await certificatesStore.addEntry(certificate)

const result = await certificatesStore.getCertificateUsername(pubkey)
expect(result).toBe(username)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { getCrypto } from 'pkijs'
import { EventEmitter } from 'events'
import { StorageEvents } from '../storage.types'
import EventStore from 'orbit-db-eventstore'
import { CommunityMetadata, NoCryptoEngineError } from '@quiet/types'
import {
keyFromCertificate,
Expand All @@ -16,16 +14,16 @@ import { CertificateData } from '../../registration/registration.functions'
import { OrbitDb } from '../orbitDb/orbitDb.service'
import { Injectable } from '@nestjs/common'
import Logger from '../../common/logger'
import { EventStoreBase } from '../base.store'

@Injectable()
export class CertificatesStore extends EventEmitter {
public store: EventStore<string>
export class CertificatesStore extends EventStoreBase<string> {
protected readonly logger = Logger(CertificatesStore.name)

private metadata: CommunityMetadata | undefined
private filteredCertificatesMapping: Map<string, Partial<UserData>>
private usernameMapping: Map<string, string>

private readonly logger = Logger(CertificatesStore.name)

constructor(private readonly orbitDbService: OrbitDb) {
super()
this.filteredCertificatesMapping = new Map()
Expand Down Expand Up @@ -65,29 +63,14 @@ export class CertificatesStore extends EventEmitter {

public async loadedCertificates() {
this.emit(StorageEvents.CERTIFICATES_STORED, {
certificates: await this.getCertificates(),
certificates: await this.getEntries(),
})
}

public async close() {
this.logger('Closing certificates DB')
await this.store?.close()
this.logger('Closed certificates DB')
}

public getAddress() {
return this.store?.address
}

public async addCertificate(certificate: string) {
public async addEntry(certificate: string): Promise<string> {
this.logger('Adding user certificate')
await this.store?.add(certificate)
return true
}

public async loadAllCertificates() {
const certificates = await this.getCertificates()
return certificates
return certificate
}

public updateMetadata(metadata: CommunityMetadata) {
Expand Down Expand Up @@ -147,14 +130,9 @@ export class CertificatesStore extends EventEmitter {
* as specified in the comment section of
* https://github.com/TryQuiet/quiet/issues/1899
*/
public async getCertificates(): Promise<string[]> {
public async getEntries(): Promise<string[]> {
this.logger('Getting certificates')
if (!this.store) {
this.logger('No store found!')
return []
}

const allCertificates = this.store
const allCertificates = this.getStore()
.iterator({ limit: -1 })
.collect()
.map(e => e.payload.value)
Expand Down Expand Up @@ -199,16 +177,14 @@ export class CertificatesStore extends EventEmitter {
if (cache) return cache

// Perform cryptographic operations and populate cache
await this.getCertificates()
await this.getEntries()

// Return desired data from updated cache
return this.usernameMapping.get(pubkey)
}

public clean() {
// FIXME: Add correct typings on object fields.

// @ts-ignore
this.logger('Cleaning certificates store')
this.store = undefined
this.metadata = undefined
this.filteredCertificatesMapping = new Map()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ describe('CommmunityMetadataStore', () => {

describe('updateCommunityMetadata', () => {
test('updates community metadata if the metadata is valid', async () => {
const ret = await communityMetadataStore.updateCommunityMetadata(metaValid)
const meta = communityMetadataStore.getCommunityMetadata()
const ret = await communityMetadataStore.setEntry(metaValid.id, metaValid)
const meta = communityMetadataStore.getEntry()

expect(ret).toStrictEqual(metaValidWithOwnerId)
expect(meta).toStrictEqual(metaValidWithOwnerId)
Expand All @@ -121,11 +121,9 @@ describe('CommmunityMetadataStore', () => {
...metaValid,
rootCa: 'Something invalid!',
}
const ret = await communityMetadataStore.updateCommunityMetadata(metaInvalid)
const meta = communityMetadataStore.getCommunityMetadata()

expect(ret).toStrictEqual(undefined)
expect(meta).toEqual(undefined)
expect(communityMetadataStore.setEntry(metaInvalid.id, metaInvalid)).rejects.toThrow()
const meta = communityMetadataStore.getEntry()
expect(meta).toEqual(null)
})
})

Expand Down
Loading
Loading