Browse Source

feat: add shared vaults model. (#631)

Co-authored-by: Mo <mo@standardnotes.com>
Karol Sójko 2 năm trước cách đây
mục cha
commit
38e77f04be

+ 3 - 0
.pnp.cjs

@@ -5179,6 +5179,7 @@ const RAW_RUNTIME_STATE =
           ["@standardnotes/responses", "npm:1.13.24"],\
           ["@standardnotes/security", "workspace:packages/security"],\
           ["@standardnotes/settings", "workspace:packages/settings"],\
+          ["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
           ["@standardnotes/time", "workspace:packages/time"],\
           ["@types/cors", "npm:2.8.13"],\
           ["@types/dotenv", "npm:8.2.0"],\
@@ -5188,6 +5189,7 @@ const RAW_RUNTIME_STATE =
           ["@types/newrelic", "npm:9.14.0"],\
           ["@types/node", "npm:20.2.5"],\
           ["@types/prettyjson", "npm:0.0.30"],\
+          ["@types/semver", "npm:7.5.0"],\
           ["@types/ua-parser-js", "npm:0.7.36"],\
           ["@types/uuid", "npm:8.3.4"],\
           ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.59.8"],\
@@ -5210,6 +5212,7 @@ const RAW_RUNTIME_STATE =
           ["prettier", "npm:2.8.8"],\
           ["prettyjson", "npm:1.2.5"],\
           ["reflect-metadata", "npm:0.1.13"],\
+          ["semver", "npm:7.5.1"],\
           ["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
           ["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
           ["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\

+ 1 - 1
packages/domain-core/src/Domain/Common/Dates.ts

@@ -20,7 +20,7 @@ export class Dates extends ValueObject<DatesProps> {
       return Result.fail<Dates>(`Could not create Dates. Creation date should be a date object, given: ${createdAt}`)
     }
     if (!(updatedAt instanceof Date)) {
-      return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${createdAt}`)
+      return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${updatedAt}`)
     }
 
     return Result.ok<Dates>(new Dates({ createdAt, updatedAt }))

+ 30 - 0
packages/domain-core/src/Domain/Common/Timestamps.ts

@@ -0,0 +1,30 @@
+import { Result } from '../Core/Result'
+import { ValueObject } from '../Core/ValueObject'
+import { TimestampsProps } from './TimestampsProps'
+
+export class Timestamps extends ValueObject<TimestampsProps> {
+  get createdAt(): number {
+    return this.props.createdAt
+  }
+
+  get updatedAt(): number {
+    return this.props.updatedAt
+  }
+
+  private constructor(props: TimestampsProps) {
+    super(props)
+  }
+
+  static create(createdAt: number, updatedAt: number): Result<Timestamps> {
+    if (isNaN(createdAt)) {
+      return Result.fail<Timestamps>(
+        `Could not create Timestamps. Creation date should be a number, given: ${createdAt}`,
+      )
+    }
+    if (isNaN(updatedAt)) {
+      return Result.fail<Timestamps>(`Could not create Timestamps. Update date should be a number, given: ${updatedAt}`)
+    }
+
+    return Result.ok<Timestamps>(new Timestamps({ createdAt, updatedAt }))
+  }
+}

+ 4 - 0
packages/domain-core/src/Domain/Common/TimestampsProps.ts

@@ -0,0 +1,4 @@
+export interface TimestampsProps {
+  createdAt: number
+  updatedAt: number
+}

+ 2 - 0
packages/domain-core/src/Domain/index.ts

@@ -17,6 +17,8 @@ export * from './Common/RoleName'
 export * from './Common/RoleNameProps'
 export * from './Common/RoleNameCollection'
 export * from './Common/RoleNameCollectionProps'
+export * from './Common/Timestamps'
+export * from './Common/TimestampsProps'
 export * from './Common/Username'
 export * from './Common/UsernameProps'
 export * from './Common/Uuid'

+ 3 - 0
packages/syncing-server/.env.sample

@@ -20,6 +20,9 @@ DB_TYPE=mysql
 REDIS_URL=redis://cache
 CACHE_TYPE=redis
 
+VALET_TOKEN_SECRET=change-me-!
+VALET_TOKEN_TTL=1000
+
 SNS_TOPIC_ARN=
 SNS_AWS_REGION=
 SQS_QUEUE_URL=

+ 5 - 1
packages/syncing-server/package.json

@@ -24,7 +24,8 @@
     "start": "yarn node dist/bin/server.js",
     "worker": "yarn node dist/bin/worker.js",
     "content-size": "yarn node dist/bin/content.js",
-    "upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
+    "upgrade:snjs": "yarn ncu -u '@standardnotes/*'",
+    "migrate": "yarn clean && yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"
   },
   "dependencies": {
     "@aws-sdk/client-s3": "^3.332.0",
@@ -38,6 +39,7 @@
     "@standardnotes/responses": "^1.13.9",
     "@standardnotes/security": "workspace:*",
     "@standardnotes/settings": "workspace:*",
+    "@standardnotes/sncrypto-node": "workspace:*",
     "@standardnotes/time": "workspace:*",
     "axios": "^1.1.3",
     "cors": "2.8.5",
@@ -51,6 +53,7 @@
     "nodemon": "^2.0.19",
     "prettyjson": "^1.2.5",
     "reflect-metadata": "0.1.13",
+    "semver": "^7.5.1",
     "sqlite3": "^5.1.6",
     "typeorm": "^0.3.15",
     "ua-parser-js": "^1.0.32",
@@ -65,6 +68,7 @@
     "@types/jsonwebtoken": "^9.0.1",
     "@types/node": "^20.2.5",
     "@types/prettyjson": "^0.0.30",
+    "@types/semver": "^7.5.0",
     "@types/ua-parser-js": "^0.7.36",
     "@types/uuid": "^8.3.0",
     "@typescript-eslint/eslint-plugin": "^5.59.2",

+ 17 - 0
packages/syncing-server/src/Domain/SharedVault/SharedVault.ts

@@ -0,0 +1,17 @@
+import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
+
+import { SharedVaultProps } from './SharedVaultProps'
+
+export class SharedVault extends Entity<SharedVaultProps> {
+  get id(): UniqueEntityId {
+    return this._id
+  }
+
+  private constructor(props: SharedVaultProps, id?: UniqueEntityId) {
+    super(props, id)
+  }
+
+  static create(props: SharedVaultProps, id?: UniqueEntityId): Result<SharedVault> {
+    return Result.ok<SharedVault>(new SharedVault(props, id))
+  }
+}

+ 8 - 0
packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts

@@ -0,0 +1,8 @@
+import { Uuid, Timestamps } from '@standardnotes/domain-core'
+
+export interface SharedVaultProps {
+  userUuid: Uuid
+  fileUploadBytesUsed: number
+  fileUploadBytesLimit: number
+  timestamps: Timestamps
+}

+ 7 - 0
packages/syncing-server/src/Domain/SharedVault/SharedVaultsRepositoryInterface.ts

@@ -0,0 +1,7 @@
+import { SharedVault } from './SharedVault'
+
+export interface SharedVaultsRepositoryInterface {
+  findByUuid(uuid: string): Promise<SharedVault | null>
+  save(sharedVault: SharedVault): Promise<void>
+  remove(sharedVault: SharedVault): Promise<void>
+}

+ 38 - 0
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVault.ts

@@ -0,0 +1,38 @@
+import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
+
+@Entity({ name: 'shared_vaults' })
+export class TypeORMSharedVault {
+  @PrimaryGeneratedColumn('uuid')
+  declare uuid: string
+
+  @Column({
+    name: 'user_uuid',
+    length: 36,
+  })
+  @Index('index_shared_vaults_on_user_uuid')
+  declare userUuid: string
+
+  @Column({
+    name: 'file_upload_bytes_used',
+    type: 'int',
+  })
+  declare fileUploadBytesUsed: number
+
+  @Column({
+    name: 'file_upload_bytes_limit',
+    type: 'int',
+  })
+  declare fileUploadBytesLimit: number
+
+  @Column({
+    name: 'created_at_timestamp',
+    type: 'bigint',
+  })
+  declare createdAtTimestamp: number
+
+  @Column({
+    name: 'updated_at_timestamp',
+    type: 'bigint',
+  })
+  declare updatedAtTimestamp: number
+}

+ 38 - 0
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultRepository.ts

@@ -0,0 +1,38 @@
+import { Repository } from 'typeorm'
+import { MapperInterface } from '@standardnotes/domain-core'
+
+import { SharedVaultsRepositoryInterface } from '../../Domain/SharedVault/SharedVaultsRepositoryInterface'
+import { TypeORMSharedVault } from './TypeORMSharedVault'
+import { SharedVault } from '../../Domain/SharedVault/SharedVault'
+
+export class TypeORMSharedVaultRepository implements SharedVaultsRepositoryInterface {
+  constructor(
+    private ormRepository: Repository<TypeORMSharedVault>,
+    private mapper: MapperInterface<SharedVault, TypeORMSharedVault>,
+  ) {}
+
+  async save(sharedVault: SharedVault): Promise<void> {
+    const persistence = this.mapper.toProjection(sharedVault)
+
+    await this.ormRepository.save(persistence)
+  }
+
+  async findByUuid(uuid: string): Promise<SharedVault | null> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('shared_vault')
+      .where('shared_vault.uuid = :uuid', {
+        uuid,
+      })
+      .getOne()
+
+    if (persistence === null) {
+      return null
+    }
+
+    return this.mapper.toDomain(persistence)
+  }
+
+  async remove(sharedVault: SharedVault): Promise<void> {
+    await this.ormRepository.remove(this.mapper.toProjection(sharedVault))
+  }
+}

+ 49 - 0
packages/syncing-server/src/Mapping/SharedVaultPersistenceMapper.ts

@@ -0,0 +1,49 @@
+import { Timestamps, MapperInterface, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
+
+import { SharedVault } from '../Domain/SharedVault/SharedVault'
+import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
+
+export class SharedVaultPersistenceMapper implements MapperInterface<SharedVault, TypeORMSharedVault> {
+  toDomain(projection: TypeORMSharedVault): SharedVault {
+    const userUuidOrError = Uuid.create(projection.userUuid)
+    if (userUuidOrError.isFailed()) {
+      throw new Error(`Failed to create shared vault from projection: ${userUuidOrError.getError()}`)
+    }
+    const userUuid = userUuidOrError.getValue()
+
+    const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
+    if (timestampsOrError.isFailed()) {
+      throw new Error(`Failed to create shared vault from projection: ${timestampsOrError.getError()}`)
+    }
+    const timestamps = timestampsOrError.getValue()
+
+    const sharedVaultOrError = SharedVault.create(
+      {
+        userUuid,
+        fileUploadBytesUsed: projection.fileUploadBytesUsed,
+        fileUploadBytesLimit: projection.fileUploadBytesLimit,
+        timestamps,
+      },
+      new UniqueEntityId(projection.uuid),
+    )
+    if (sharedVaultOrError.isFailed()) {
+      throw new Error(`Failed to create shared vault from projection: ${sharedVaultOrError.getError()}`)
+    }
+    const sharedVault = sharedVaultOrError.getValue()
+
+    return sharedVault
+  }
+
+  toProjection(domain: SharedVault): TypeORMSharedVault {
+    const typeorm = new TypeORMSharedVault()
+
+    typeorm.uuid = domain.id.toString()
+    typeorm.userUuid = domain.props.userUuid.value
+    typeorm.fileUploadBytesUsed = domain.props.fileUploadBytesUsed
+    typeorm.fileUploadBytesLimit = domain.props.fileUploadBytesLimit
+    typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
+    typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
+
+    return typeorm
+  }
+}

+ 4 - 1
yarn.lock

@@ -4088,6 +4088,7 @@ __metadata:
     "@standardnotes/responses": "npm:^1.13.9"
     "@standardnotes/security": "workspace:*"
     "@standardnotes/settings": "workspace:*"
+    "@standardnotes/sncrypto-node": "workspace:*"
     "@standardnotes/time": "workspace:*"
     "@types/cors": "npm:^2.8.9"
     "@types/dotenv": "npm:^8.2.0"
@@ -4097,6 +4098,7 @@ __metadata:
     "@types/newrelic": "npm:^9.13.0"
     "@types/node": "npm:^20.2.5"
     "@types/prettyjson": "npm:^0.0.30"
+    "@types/semver": "npm:^7.5.0"
     "@types/ua-parser-js": "npm:^0.7.36"
     "@types/uuid": "npm:^8.3.0"
     "@typescript-eslint/eslint-plugin": "npm:^5.59.2"
@@ -4119,6 +4121,7 @@ __metadata:
     prettier: "npm:^2.8.8"
     prettyjson: "npm:^1.2.5"
     reflect-metadata: "npm:0.1.13"
+    semver: "npm:^7.5.1"
     sqlite3: "npm:^5.1.6"
     ts-jest: "npm:^29.1.0"
     typeorm: "npm:^0.3.15"
@@ -4656,7 +4659,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/semver@npm:^7.3.12":
+"@types/semver@npm:^7.3.12, @types/semver@npm:^7.5.0":
   version: 7.5.0
   resolution: "@types/semver@npm:7.5.0"
   checksum: dac255fae68157aec375fdb79d483a161c1b9c58e0ab9e18936dd1e9b89dd0ff85d64e482b1505de7e17455b404a0a530c4f9ddd6f21d333c2311c0068687b14