feat(analytics): add analytics entities

This commit is contained in:
Karol Sójko
2022-11-04 10:50:33 +01:00
parent 2feaa8d956
commit f315b1ac5c
8 changed files with 121 additions and 1 deletions

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class initDatabase1667555285111 implements MigrationInterface {
name = 'initDatabase1667555285111'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `analytics_entities` (`id` int NOT NULL AUTO_INCREMENT, `user_uuid` varchar(36) NOT NULL, INDEX `user_uuid` (`user_uuid`), PRIMARY KEY (`id`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `user_uuid` ON `analytics_entities`')
await queryRunner.query('DROP TABLE `analytics_entities`')
}
}

View File

@@ -28,6 +28,10 @@ import { AnalyticsStoreInterface } from '../Domain/Analytics/AnalyticsStoreInter
import { RedisAnalyticsStore } from '../Infra/Redis/RedisAnalyticsStore'
import { StatisticsStoreInterface } from '../Domain/Statistics/StatisticsStoreInterface'
import { RedisStatisticsStore } from '../Infra/Redis/RedisStatisticsStore'
import { AnalyticsEntityRepositoryInterface } from '../Domain/Entity/AnalyticsEntityRepositoryInterface'
import { MySQLAnalyticsEntityRepository } from '../Infra/MySQL/MySQLAnalyticsEntityRepository'
import { Repository } from 'typeorm'
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -97,8 +101,14 @@ export class ContainerConfigLoader {
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
// Repositories
container
.bind<AnalyticsEntityRepositoryInterface>(TYPES.AnalyticsEntityRepository)
.to(MySQLAnalyticsEntityRepository)
// ORM
container
.bind<Repository<AnalyticsEntity>>(TYPES.ORMAnalyticsEntityRepository)
.toConstantValue(AppDataSource.getRepository(AnalyticsEntity))
// Use Case

View File

@@ -1,4 +1,7 @@
import { DataSource, LoggerOptions } from 'typeorm'
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
import { Env } from './Env'
const env: Env = new Env()
@@ -33,7 +36,7 @@ export const AppDataSource = new DataSource({
],
removeNodeErrorCount: 10,
},
entities: [],
entities: [AnalyticsEntity],
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),

View File

@@ -12,7 +12,9 @@ const TYPES = {
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
// Repositories
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
// ORM
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
// Use Case
// Handlers
// Services

View File

@@ -0,0 +1,14 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'analytics_entities' })
export class AnalyticsEntity {
@PrimaryGeneratedColumn()
declare id: number
@Column({
name: 'user_uuid',
length: 36,
})
@Index('user_uuid')
declare userUuid: string
}

View File

@@ -0,0 +1,7 @@
import { Uuid } from '@standardnotes/common'
import { AnalyticsEntity } from './AnalyticsEntity'
export interface AnalyticsEntityRepositoryInterface {
save(analyticsEntity: AnalyticsEntity): Promise<AnalyticsEntity>
findOneByUserUuid(userUuid: Uuid): Promise<AnalyticsEntity | null>
}

View File

@@ -0,0 +1,42 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { AnalyticsEntity } from '../../Domain/Entity/AnalyticsEntity'
import { MySQLAnalyticsEntityRepository } from './MySQLAnalyticsEntityRepository'
describe('MySQLAnalyticsEntityRepository', () => {
let ormRepository: Repository<AnalyticsEntity>
let analyticsEntity: AnalyticsEntity
let queryBuilder: SelectQueryBuilder<AnalyticsEntity>
const createRepository = () => new MySQLAnalyticsEntityRepository(ormRepository)
beforeEach(() => {
analyticsEntity = {} as jest.Mocked<AnalyticsEntity>
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<AnalyticsEntity>>
ormRepository = {} as jest.Mocked<Repository<AnalyticsEntity>>
ormRepository.save = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(analyticsEntity)
expect(ormRepository.save).toHaveBeenCalledWith(analyticsEntity)
})
it('should find one by user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(analyticsEntity)
const result = await createRepository().findOneByUserUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith('analytics_entity.user_uuid = :userUuid', { userUuid: '123' })
expect(result).toEqual(analyticsEntity)
})
})

View File

@@ -0,0 +1,26 @@
import { Uuid } from '@standardnotes/common'
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import TYPES from '../../Bootstrap/Types'
import { AnalyticsEntity } from '../../Domain/Entity/AnalyticsEntity'
import { AnalyticsEntityRepositoryInterface } from '../../Domain/Entity/AnalyticsEntityRepositoryInterface'
@injectable()
export class MySQLAnalyticsEntityRepository implements AnalyticsEntityRepositoryInterface {
constructor(
@inject(TYPES.ORMAnalyticsEntityRepository)
private ormRepository: Repository<AnalyticsEntity>,
) {}
async findOneByUserUuid(userUuid: Uuid): Promise<AnalyticsEntity | null> {
return this.ormRepository
.createQueryBuilder('analytics_entity')
.where('analytics_entity.user_uuid = :userUuid', { userUuid })
.getOne()
}
async save(analyticsEntity: AnalyticsEntity): Promise<AnalyticsEntity> {
return this.ormRepository.save(analyticsEntity)
}
}