chore(server): sql versioning (#5346)

* chore(server): sql versioning

* chore: always add newline to end of file

* refactor: generator

* chore: pr feedback

* chore: pr feedback
This commit is contained in:
Jason Rasmussen
2023-11-30 10:10:30 -05:00
committed by GitHub
parent ffecfbe075
commit 5e55a17b2a
34 changed files with 3012 additions and 9 deletions
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import { ActivityEntity } from '../entities/activity.entity';
import { DummyValue, GenerateSql } from '../infra.util';
export interface ActivitySearch {
albumId?: string;
@@ -15,6 +16,7 @@ export interface ActivitySearch {
export class ActivityRepository implements IActivityRepository {
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
@GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
search(options: ActivitySearch): Promise<ActivityEntity[]> {
const { userId, assetId, albumId, isLiked } = options;
return this.repository.find({
@@ -41,6 +43,7 @@ export class ActivityRepository implements IActivityRepository {
await this.repository.delete(id);
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getStatistics(assetId: string, albumId: string): Promise<number> {
return this.repository.count({
where: { assetId, albumId, isLiked: false },
@@ -4,6 +4,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm';
import { dataSource } from '../database.config';
import { AlbumEntity, AssetEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class AlbumRepository implements IAlbumRepository {
@@ -13,6 +14,7 @@ export class AlbumRepository implements IAlbumRepository {
@InjectDataSource() private dataSource: DataSource,
) {}
@GenerateSql({ params: [DummyValue.UUID, {}] })
getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null> {
const relations: FindOptionsRelations<AlbumEntity> = {
owner: true,
@@ -36,6 +38,7 @@ export class AlbumRepository implements IAlbumRepository {
return this.repository.findOne({ where: { id }, relations, order });
}
@GenerateSql({ params: [[DummyValue.UUID]] })
getByIds(ids: string[]): Promise<AlbumEntity[]> {
return this.repository.find({
where: {
@@ -48,6 +51,7 @@ export class AlbumRepository implements IAlbumRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]> {
return this.repository.find({
where: [
@@ -59,6 +63,7 @@ export class AlbumRepository implements IAlbumRepository {
});
}
@GenerateSql({ params: [[DummyValue.UUID]] })
async getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]> {
// Guard against running invalid query when ids list is empty.
if (!ids.length) {
@@ -91,6 +96,7 @@ export class AlbumRepository implements IAlbumRepository {
* - Thumbnail references an asset outside the album
* - Empty album still has a thumbnail set
*/
@GenerateSql()
async getInvalidThumbnail(): Promise<string[]> {
// Using dataSource, because there is no direct access to albums_assets_assets.
const albumHasAssets = this.dataSource
@@ -113,6 +119,7 @@ export class AlbumRepository implements IAlbumRepository {
return albums.map((album) => album.id);
}
@GenerateSql({ params: [DummyValue.UUID] })
getOwned(ownerId: string): Promise<AlbumEntity[]> {
return this.repository.find({
relations: { sharedUsers: true, sharedLinks: true, owner: true },
@@ -124,6 +131,7 @@ export class AlbumRepository implements IAlbumRepository {
/**
* Get albums shared with and shared by owner.
*/
@GenerateSql({ params: [DummyValue.UUID] })
getShared(ownerId: string): Promise<AlbumEntity[]> {
return this.repository.find({
relations: { sharedUsers: true, sharedLinks: true, owner: true },
@@ -139,6 +147,7 @@ export class AlbumRepository implements IAlbumRepository {
/**
* Get albums of owner that are _not_ shared
*/
@GenerateSql({ params: [DummyValue.UUID] })
getNotShared(ownerId: string): Promise<AlbumEntity[]> {
return this.repository.find({
relations: { sharedUsers: true, sharedLinks: true, owner: true },
@@ -159,6 +168,7 @@ export class AlbumRepository implements IAlbumRepository {
await this.repository.delete({ ownerId: userId });
}
@GenerateSql()
getAll(): Promise<AlbumEntity[]> {
return this.repository.find({
relations: {
@@ -167,6 +177,7 @@ export class AlbumRepository implements IAlbumRepository {
});
}
// @GenerateSql({ params: [DummyValue.UUID] })
async removeAsset(assetId: string): Promise<void> {
// Using dataSource, because there is no direct access to albums_assets_assets.
await this.dataSource
@@ -176,6 +187,7 @@ export class AlbumRepository implements IAlbumRepository {
.where('"albums_assets_assets"."assetsId" = :assetId', { assetId });
}
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] })
async removeAssets(asset: AlbumAssets): Promise<void> {
await this.dataSource
.createQueryBuilder()
@@ -195,6 +207,7 @@ export class AlbumRepository implements IAlbumRepository {
* @param assetIds Optional list of asset IDs to filter on.
* @returns Set of Asset IDs for the given album ID.
*/
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }, { name: 'no assets', params: [DummyValue.UUID] })
async getAssetIds(albumId: string, assetIds?: string[]): Promise<Set<string>> {
const query = this.dataSource
.createQueryBuilder()
@@ -210,6 +223,7 @@ export class AlbumRepository implements IAlbumRepository {
return new Set(result.map((row) => row['assetId']));
}
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] })
hasAsset(asset: AlbumAsset): Promise<boolean> {
return this.repository.exist({
where: {
@@ -224,6 +238,7 @@ export class AlbumRepository implements IAlbumRepository {
});
}
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] })
async addAssets({ albumId, assetIds }: AlbumAssets): Promise<void> {
await this.dataSource
.createQueryBuilder()
@@ -266,6 +281,7 @@ export class AlbumRepository implements IAlbumRepository {
*
* @returns Amount of updated album thumbnails or undefined when unknown
*/
@GenerateSql()
async updateThumbnails(): Promise<number | undefined> {
// Subquery for getting a new thumbnail.
const newThumbnail = this.assetRepository
@@ -3,9 +3,10 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { APIKeyEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class APIKeyRepository implements IKeyRepository {
export class ApiKeyRepository implements IKeyRepository {
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
@@ -21,6 +22,7 @@ export class APIKeyRepository implements IKeyRepository {
await this.repository.delete({ userId, id });
}
@GenerateSql({ params: [DummyValue.STRING] })
getKey(hashedToken: string): Promise<APIKeyEntity | null> {
return this.repository.findOne({
select: {
@@ -35,10 +37,12 @@ export class APIKeyRepository implements IKeyRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getById(userId: string, id: string): Promise<APIKeyEntity | null> {
return this.repository.findOne({ where: { userId, id } });
}
@GenerateSql({ params: [DummyValue.STRING] })
getByUserId(userId: string): Promise<APIKeyEntity[]> {
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
}
@@ -22,6 +22,7 @@ import _ from 'lodash';
import { DateTime } from 'luxon';
import { And, FindOptionsRelations, FindOptionsWhere, In, IsNull, LessThan, Not, Repository } from 'typeorm';
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
import OptionalBetween from '../utils/optional-between.util';
import { paginate } from '../utils/pagination.util';
@@ -185,6 +186,7 @@ export class AssetRepository implements IAssetRepository {
return this.repository.save(asset);
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.DATE] })
getByDate(ownerId: string, date: Date): Promise<AssetEntity[]> {
// For reference of a correct approach although slower
@@ -219,6 +221,7 @@ export class AssetRepository implements IAssetRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
getByDayOfYear(ownerId: string, { day, month }: MonthDay): Promise<AssetEntity[]> {
return this.repository
.createQueryBuilder('entity')
@@ -240,6 +243,7 @@ export class AssetRepository implements IAssetRepository {
.getMany();
}
@GenerateSql({ params: [[DummyValue.UUID]] })
getByIds(ids: string[], relations?: FindOptionsRelations<AssetEntity>): Promise<AssetEntity[]> {
if (!relations) {
relations = {
@@ -259,6 +263,7 @@ export class AssetRepository implements IAssetRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID] })
async deleteAll(ownerId: string): Promise<void> {
await this.repository.delete({ ownerId });
}
@@ -291,12 +296,14 @@ export class AssetRepository implements IAssetRepository {
});
}
@GenerateSql({ params: [[DummyValue.UUID]] })
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]> {
return this.repository.find({
where: { library: { id: In(libraryIds) } },
});
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null> {
return this.repository.findOne({
where: { library: { id: libraryId }, originalPath: originalPath },
@@ -333,6 +340,7 @@ export class AssetRepository implements IAssetRepository {
*
* @returns Promise<string[]> - Array of assetIds belong to the device
*/
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
const items = await this.repository.find({
select: { deviceAssetId: true },
@@ -347,6 +355,7 @@ export class AssetRepository implements IAssetRepository {
return items.map((asset) => asset.deviceAssetId);
}
@GenerateSql({ params: [DummyValue.UUID] })
getById(id: string): Promise<AssetEntity | null> {
return this.repository.findOne({
where: { id },
@@ -362,6 +371,7 @@ export class AssetRepository implements IAssetRepository {
});
}
@GenerateSql({ params: [[DummyValue.UUID], { deviceId: DummyValue.STRING }] })
async updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void> {
await this.repository.update({ id: In(ids) }, options);
}
@@ -395,6 +405,7 @@ export class AssetRepository implements IAssetRepository {
await this.repository.remove(asset);
}
@GenerateSql({ params: [[DummyValue.UUID], DummyValue.BUFFER] })
getByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity | null> {
return this.repository.findOne({ where: { ownerId: userId, checksum } });
}
@@ -417,6 +428,14 @@ export class AssetRepository implements IAssetRepository {
});
}
@GenerateSql(
...Object.values(WithProperty)
.filter((property) => property !== WithProperty.IS_OFFLINE)
.map((property) => ({
name: property,
params: [DummyValue.PAGINATION, property],
})),
)
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity> {
let relations: FindOptionsRelations<AssetEntity> = {};
let where: FindOptionsWhere<AssetEntity> | FindOptionsWhere<AssetEntity>[] = {};
@@ -4,11 +4,13 @@ import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Not } from 'typeorm';
import { Repository } from 'typeorm/repository/Repository';
import { LibraryEntity, LibraryType } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class LibraryRepository implements ILibraryRepository {
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
@GenerateSql({ params: [DummyValue.UUID] })
get(id: string, withDeleted = false): Promise<LibraryEntity | null> {
return this.repository.findOneOrFail({
where: {
@@ -19,6 +21,7 @@ export class LibraryRepository implements ILibraryRepository {
});
}
@GenerateSql({ params: [DummyValue.STRING] })
existsByName(name: string, withDeleted = false): Promise<boolean> {
return this.repository.exist({
where: {
@@ -28,10 +31,12 @@ export class LibraryRepository implements ILibraryRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID] })
getCountForUser(ownerId: string): Promise<number> {
return this.repository.countBy({ ownerId });
}
@GenerateSql({ params: [DummyValue.UUID] })
getDefaultUploadLibrary(ownerId: string): Promise<LibraryEntity | null> {
return this.repository.findOne({
where: {
@@ -44,6 +49,7 @@ export class LibraryRepository implements ILibraryRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID] })
getUploadLibraryCount(ownerId: string): Promise<number> {
return this.repository.count({
where: {
@@ -53,6 +59,7 @@ export class LibraryRepository implements ILibraryRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID] })
getAllByUserId(ownerId: string, type?: LibraryType): Promise<LibraryEntity[]> {
return this.repository.find({
where: {
@@ -69,6 +76,7 @@ export class LibraryRepository implements ILibraryRepository {
});
}
@GenerateSql({ params: [] })
getAll(withDeleted = false, type?: LibraryType): Promise<LibraryEntity[]> {
return this.repository.find({
where: { type },
@@ -82,6 +90,7 @@ export class LibraryRepository implements ILibraryRepository {
});
}
@GenerateSql()
getAllDeleted(): Promise<LibraryEntity[]> {
return this.repository.find({
where: {
@@ -114,6 +123,7 @@ export class LibraryRepository implements ILibraryRepository {
return this.save(library);
}
@GenerateSql({ params: [DummyValue.UUID] })
async getStatistics(id: string): Promise<LibraryStatsResponseDto> {
const stats = await this.repository
.createQueryBuilder('libraries')
@@ -134,6 +144,7 @@ export class LibraryRepository implements ILibraryRepository {
};
}
@GenerateSql({ params: [DummyValue.UUID] })
async getOnlineAssetPaths(libraryId: string): Promise<string[]> {
// Return all non-offline asset paths for a given library
const rawResults = await this.repository
@@ -153,6 +164,7 @@ export class LibraryRepository implements ILibraryRepository {
return results;
}
@GenerateSql({ params: [DummyValue.UUID] })
async getAssetIds(libraryId: string, withDeleted = false): Promise<string[]> {
let query = await this.repository
.createQueryBuilder('library')
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { MoveEntity, PathType } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class MoveRepository implements IMoveRepository {
@@ -12,6 +13,7 @@ export class MoveRepository implements IMoveRepository {
return this.repository.save(entity);
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
getByEntity(entityId: string, pathType: PathType): Promise<MoveEntity | null> {
return this.repository.findOne({ where: { entityId, pathType } });
}
@@ -9,6 +9,7 @@ import {
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { AssetEntity, AssetFaceEntity, PersonEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
export class PersonRepository implements IPersonRepository {
constructor(
@@ -36,6 +37,7 @@ export class PersonRepository implements IPersonRepository {
return assetIds;
}
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
async reassignFaces({ oldPersonId, newPersonId }: UpdateFacesData): Promise<number> {
const result = await this.assetFaceRepository
.createQueryBuilder()
@@ -57,18 +59,22 @@ export class PersonRepository implements IPersonRepository {
return people.length;
}
@GenerateSql()
getAllFaces(): Promise<AssetFaceEntity[]> {
return this.assetFaceRepository.find({ relations: { asset: true }, withDeleted: true });
}
@GenerateSql()
getAll(): Promise<PersonEntity[]> {
return this.personRepository.find();
}
@GenerateSql()
getAllWithoutThumbnail(): Promise<PersonEntity[]> {
return this.personRepository.findBy({ thumbnailPath: '' });
}
@GenerateSql({ params: [DummyValue.UUID] })
getAllForUser(userId: string, options?: PersonSearchOptions): Promise<PersonEntity[]> {
const queryBuilder = this.personRepository
.createQueryBuilder('person')
@@ -89,6 +95,7 @@ export class PersonRepository implements IPersonRepository {
return queryBuilder.getMany();
}
@GenerateSql()
getAllWithoutFaces(): Promise<PersonEntity[]> {
return this.personRepository
.createQueryBuilder('person')
@@ -99,10 +106,12 @@ export class PersonRepository implements IPersonRepository {
.getMany();
}
@GenerateSql({ params: [DummyValue.UUID] })
getById(personId: string): Promise<PersonEntity | null> {
return this.personRepository.findOne({ where: { id: personId } });
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING, { withHidden: true }] })
getByName(userId: string, personName: string, { withHidden }: PersonNameSearchOptions): Promise<PersonEntity[]> {
const queryBuilder = this.personRepository
.createQueryBuilder('person')
@@ -121,6 +130,7 @@ export class PersonRepository implements IPersonRepository {
return queryBuilder.getMany();
}
@GenerateSql({ params: [DummyValue.UUID] })
async getStatistics(personId: string): Promise<PersonStatistics> {
return {
assets: await this.assetFaceRepository
@@ -135,6 +145,7 @@ export class PersonRepository implements IPersonRepository {
};
}
@GenerateSql({ params: [DummyValue.UUID] })
getAssets(personId: string): Promise<AssetEntity[]> {
return this.assetRepository.find({
where: {
@@ -171,10 +182,12 @@ export class PersonRepository implements IPersonRepository {
return this.personRepository.findOneByOrFail({ id });
}
@GenerateSql({ params: [[{ assetId: DummyValue.UUID, personId: DummyValue.UUID }]] })
async getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]> {
return this.assetFaceRepository.find({ where: ids, relations: { asset: true }, withDeleted: true });
}
@GenerateSql({ params: [DummyValue.UUID] })
async getRandomFace(personId: string): Promise<AssetFaceEntity | null> {
return this.assetFaceRepository.findOneBy({ personId });
}
@@ -3,11 +3,13 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SharedLinkEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class SharedLinkRepository implements ISharedLinkRepository {
constructor(@InjectRepository(SharedLinkEntity) private repository: Repository<SharedLinkEntity>) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
get(userId: string, id: string): Promise<SharedLinkEntity | null> {
return this.repository.findOne({
where: {
@@ -39,6 +41,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
});
}
@GenerateSql({ params: [DummyValue.UUID] })
getAll(userId: string): Promise<SharedLinkEntity[]> {
return this.repository.find({
where: {
@@ -56,6 +59,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
});
}
@GenerateSql({ params: [DummyValue.BUFFER] })
async getByKey(key: Buffer): Promise<SharedLinkEntity | null> {
return await this.repository.findOne({
where: {
@@ -4,6 +4,7 @@ import axios from 'axios';
import { readFile } from 'fs/promises';
import { In, Repository } from 'typeorm';
import { SystemConfigEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
export class SystemConfigRepository implements ISystemConfigRepository {
constructor(
@@ -14,6 +15,7 @@ export class SystemConfigRepository implements ISystemConfigRepository {
return axios.get(url).then((response) => response.data);
}
@GenerateSql()
load(): Promise<SystemConfigEntity[]> {
return this.repository.find();
}
@@ -26,6 +28,7 @@ export class SystemConfigRepository implements ISystemConfigRepository {
return this.repository.save(items);
}
@GenerateSql({ params: [DummyValue.STRING] })
async deleteKeys(keys: string[]): Promise<void> {
await this.repository.delete({ key: In(keys) });
}
@@ -3,11 +3,13 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserTokenEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class UserTokenRepository implements IUserTokenRepository {
constructor(@InjectRepository(UserTokenEntity) private repository: Repository<UserTokenEntity>) {}
@GenerateSql({ params: [DummyValue.STRING] })
getByToken(token: string): Promise<UserTokenEntity | null> {
return this.repository.findOne({ where: { token }, relations: { user: true } });
}
@@ -35,6 +37,7 @@ export class UserTokenRepository implements IUserTokenRepository {
return this.repository.save(userToken);
}
@GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string): Promise<void> {
await this.repository.delete({ id });
}
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Not, Repository } from 'typeorm';
import { UserEntity } from '../entities';
import { DummyValue, GenerateSql } from '../infra.util';
@Injectable()
export class UserRepository implements IUserRepository {
@@ -16,14 +17,17 @@ export class UserRepository implements IUserRepository {
});
}
@GenerateSql()
async getAdmin(): Promise<UserEntity | null> {
return this.userRepository.findOne({ where: { isAdmin: true } });
}
@GenerateSql()
async hasAdmin(): Promise<boolean> {
return this.userRepository.exist({ where: { isAdmin: true } });
}
@GenerateSql({ params: [DummyValue.EMAIL] })
async getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null> {
let builder = this.userRepository.createQueryBuilder('user').where({ email });
@@ -34,10 +38,12 @@ export class UserRepository implements IUserRepository {
return builder.getOne();
}
@GenerateSql({ params: [DummyValue.STRING] })
async getByStorageLabel(storageLabel: string): Promise<UserEntity | null> {
return this.userRepository.findOne({ where: { storageLabel } });
}
@GenerateSql({ params: [DummyValue.STRING] })
async getByOAuthId(oauthId: string): Promise<UserEntity | null> {
return this.userRepository.findOne({ where: { oauthId } });
}
@@ -76,6 +82,7 @@ export class UserRepository implements IUserRepository {
return this.userRepository.recover(user);
}
@GenerateSql()
async getUserStats(): Promise<UserStatsQueryResponse[]> {
const stats = await this.userRepository
.createQueryBuilder('users')