mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
refactor(server): api keys (#1339)
* refactor: api keys * refactor: test module * chore: tests * chore: fix provider * refactor: test mock repos
This commit is contained in:
16
server/libs/domain/src/api-key/api-key.repository.ts
Normal file
16
server/libs/domain/src/api-key/api-key.repository.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { APIKeyEntity } from '@app/infra';
|
||||
|
||||
export const IKeyRepository = 'IKeyRepository';
|
||||
|
||||
export interface IKeyRepository {
|
||||
create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
||||
update(userId: string, id: number, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
||||
delete(userId: string, id: number): Promise<void>;
|
||||
/**
|
||||
* Includes the hashed `key` for verification
|
||||
* @param id
|
||||
*/
|
||||
getKey(id: number): Promise<APIKeyEntity | null>;
|
||||
getById(userId: string, id: number): Promise<APIKeyEntity | null>;
|
||||
getByUserId(userId: string): Promise<APIKeyEntity[]>;
|
||||
}
|
||||
142
server/libs/domain/src/api-key/api-key.service.spec.ts
Normal file
142
server/libs/domain/src/api-key/api-key.service.spec.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { APIKeyEntity } from '@app/infra';
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { authStub, entityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
||||
import { ICryptoRepository } from '../auth';
|
||||
import { IKeyRepository } from './api-key.repository';
|
||||
import { APIKeyService } from './api-key.service';
|
||||
|
||||
const adminKey = Object.freeze({
|
||||
id: 1,
|
||||
name: 'My Key',
|
||||
key: 'my-api-key (hashed)',
|
||||
userId: authStub.admin.id,
|
||||
user: entityStub.admin,
|
||||
} as APIKeyEntity);
|
||||
|
||||
const token = Buffer.from('1:my-api-key', 'utf8').toString('base64');
|
||||
|
||||
describe(APIKeyService.name, () => {
|
||||
let sut: APIKeyService;
|
||||
let keyMock: jest.Mocked<IKeyRepository>;
|
||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoMock = newCryptoRepositoryMock();
|
||||
keyMock = newKeyRepositoryMock();
|
||||
sut = new APIKeyService(cryptoMock, keyMock);
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new key', async () => {
|
||||
keyMock.create.mockResolvedValue(adminKey);
|
||||
|
||||
await sut.create(authStub.admin, { name: 'Test Key' });
|
||||
|
||||
expect(keyMock.create).toHaveBeenCalledWith({
|
||||
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||
name: 'Test Key',
|
||||
userId: authStub.admin.id,
|
||||
});
|
||||
expect(cryptoMock.randomBytes).toHaveBeenCalled();
|
||||
expect(cryptoMock.hash).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not require a name', async () => {
|
||||
keyMock.create.mockResolvedValue(adminKey);
|
||||
|
||||
await sut.create(authStub.admin, {});
|
||||
|
||||
expect(keyMock.create).toHaveBeenCalledWith({
|
||||
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||
name: 'API Key',
|
||||
userId: authStub.admin.id,
|
||||
});
|
||||
expect(cryptoMock.randomBytes).toHaveBeenCalled();
|
||||
expect(cryptoMock.hash).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should throw an error if the key is not found', async () => {
|
||||
keyMock.getById.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.update(authStub.admin, 1, { name: 'New Name' })).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(keyMock.update).not.toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should update a key', async () => {
|
||||
keyMock.getById.mockResolvedValue(adminKey);
|
||||
|
||||
await sut.update(authStub.admin, 1, { name: 'New Name' });
|
||||
|
||||
expect(keyMock.update).toHaveBeenCalledWith(authStub.admin.id, 1, { name: 'New Name' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should throw an error if the key is not found', async () => {
|
||||
keyMock.getById.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.delete(authStub.admin, 1)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(keyMock.delete).not.toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should delete a key', async () => {
|
||||
keyMock.getById.mockResolvedValue(adminKey);
|
||||
|
||||
await sut.delete(authStub.admin, 1);
|
||||
|
||||
expect(keyMock.delete).toHaveBeenCalledWith(authStub.admin.id, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getById', () => {
|
||||
it('should throw an error if the key is not found', async () => {
|
||||
keyMock.getById.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.getById(authStub.admin, 1)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 1);
|
||||
});
|
||||
|
||||
it('should get a key by id', async () => {
|
||||
keyMock.getById.mockResolvedValue(adminKey);
|
||||
|
||||
await sut.getById(authStub.admin, 1);
|
||||
|
||||
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should return all the keys for a user', async () => {
|
||||
keyMock.getByUserId.mockResolvedValue([adminKey]);
|
||||
|
||||
await expect(sut.getAll(authStub.admin)).resolves.toHaveLength(1);
|
||||
|
||||
expect(keyMock.getByUserId).toHaveBeenCalledWith(authStub.admin.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('should throw an error for an invalid id', async () => {
|
||||
keyMock.getKey.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.validate(token)).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
|
||||
expect(keyMock.getKey).toHaveBeenCalledWith(1);
|
||||
expect(cryptoMock.compareSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should validate the token', async () => {
|
||||
keyMock.getKey.mockResolvedValue(adminKey);
|
||||
|
||||
await expect(sut.validate(token)).resolves.toEqual(authStub.admin);
|
||||
|
||||
expect(keyMock.getKey).toHaveBeenCalledWith(1);
|
||||
expect(cryptoMock.compareSync).toHaveBeenCalledWith('my-api-key', 'my-api-key (hashed)');
|
||||
});
|
||||
});
|
||||
});
|
||||
83
server/libs/domain/src/api-key/api-key.service.ts
Normal file
83
server/libs/domain/src/api-key/api-key.service.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { UserEntity } from '@app/infra';
|
||||
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||
import { IKeyRepository } from './api-key.repository';
|
||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
||||
import { APIKeyCreateResponseDto } from './response-dto/api-key-create-response.dto';
|
||||
import { APIKeyResponseDto, mapKey } from './response-dto/api-key-response.dto';
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyService {
|
||||
constructor(
|
||||
@Inject(ICryptoRepository) private crypto: ICryptoRepository,
|
||||
@Inject(IKeyRepository) private repository: IKeyRepository,
|
||||
) {}
|
||||
|
||||
async create(authUser: AuthUserDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
const key = this.crypto.randomBytes(24).toString('base64').replace(/\W/g, '');
|
||||
const entity = await this.repository.create({
|
||||
key: await this.crypto.hash(key, 10),
|
||||
name: dto.name || 'API Key',
|
||||
userId: authUser.id,
|
||||
});
|
||||
|
||||
const secret = Buffer.from(`${entity.id}:${key}`, 'utf8').toString('base64');
|
||||
|
||||
return { secret, apiKey: mapKey(entity) };
|
||||
}
|
||||
|
||||
async update(authUser: AuthUserDto, id: number, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
|
||||
const exists = await this.repository.getById(authUser.id, id);
|
||||
if (!exists) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
return this.repository.update(authUser.id, id, {
|
||||
name: dto.name,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(authUser: AuthUserDto, id: number): Promise<void> {
|
||||
const exists = await this.repository.getById(authUser.id, id);
|
||||
if (!exists) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
|
||||
await this.repository.delete(authUser.id, id);
|
||||
}
|
||||
|
||||
async getById(authUser: AuthUserDto, id: number): Promise<APIKeyResponseDto> {
|
||||
const key = await this.repository.getById(authUser.id, id);
|
||||
if (!key) {
|
||||
throw new BadRequestException('API Key not found');
|
||||
}
|
||||
return mapKey(key);
|
||||
}
|
||||
|
||||
async getAll(authUser: AuthUserDto): Promise<APIKeyResponseDto[]> {
|
||||
const keys = await this.repository.getByUserId(authUser.id);
|
||||
return keys.map(mapKey);
|
||||
}
|
||||
|
||||
async validate(token: string): Promise<AuthUserDto> {
|
||||
const [_id, key] = Buffer.from(token, 'base64').toString('utf8').split(':');
|
||||
const id = Number(_id);
|
||||
|
||||
if (id && key) {
|
||||
const entity = await this.repository.getKey(id);
|
||||
if (entity?.user && entity?.key && this.crypto.compareSync(key, entity.key)) {
|
||||
const user = entity.user as UserEntity;
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Invalid API Key');
|
||||
}
|
||||
}
|
||||
8
server/libs/domain/src/api-key/dto/api-key-create.dto.ts
Normal file
8
server/libs/domain/src/api-key/dto/api-key-create.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class APIKeyCreateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
}
|
||||
7
server/libs/domain/src/api-key/dto/api-key-update.dto.ts
Normal file
7
server/libs/domain/src/api-key/dto/api-key-update.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class APIKeyUpdateDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name!: string;
|
||||
}
|
||||
2
server/libs/domain/src/api-key/dto/index.ts
Normal file
2
server/libs/domain/src/api-key/dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './api-key-create.dto';
|
||||
export * from './api-key-update.dto';
|
||||
4
server/libs/domain/src/api-key/index.ts
Normal file
4
server/libs/domain/src/api-key/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './api-key.repository';
|
||||
export * from './api-key.service';
|
||||
export * from './dto';
|
||||
export * from './response-dto';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { APIKeyResponseDto } from './api-key-response.dto';
|
||||
|
||||
export class APIKeyCreateResponseDto {
|
||||
secret!: string;
|
||||
apiKey!: APIKeyResponseDto;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { APIKeyEntity } from '@app/infra';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class APIKeyResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
id!: number;
|
||||
name!: string;
|
||||
createdAt!: string;
|
||||
updatedAt!: string;
|
||||
}
|
||||
|
||||
export function mapKey(entity: APIKeyEntity): APIKeyResponseDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
name: entity.name,
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
};
|
||||
}
|
||||
2
server/libs/domain/src/api-key/response-dto/index.ts
Normal file
2
server/libs/domain/src/api-key/response-dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './api-key-create-response.dto';
|
||||
export * from './api-key-response.dto';
|
||||
7
server/libs/domain/src/auth/crypto.repository.ts
Normal file
7
server/libs/domain/src/auth/crypto.repository.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const ICryptoRepository = 'ICryptoRepository';
|
||||
|
||||
export interface ICryptoRepository {
|
||||
randomBytes(size: number): Buffer;
|
||||
hash(data: string | Buffer, saltOrRounds: string | number): Promise<string>;
|
||||
compareSync(data: Buffer | string, encrypted: string): boolean;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './crypto.repository';
|
||||
export * from './dto';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
||||
import { APIKeyService } from './api-key';
|
||||
import { UserService } from './user';
|
||||
|
||||
const providers: Provider[] = [
|
||||
//
|
||||
APIKeyService,
|
||||
UserService,
|
||||
];
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './api-key';
|
||||
export * from './auth';
|
||||
export * from './domain.module';
|
||||
export * from './user';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { IUserRepository } from '@app/domain';
|
||||
import { UserEntity } from '@app/infra';
|
||||
import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { IUserRepository } from '@app/domain';
|
||||
import { when } from 'jest-when';
|
||||
import { UserService } from './user.service';
|
||||
import { newUserRepositoryMock } from '../../test';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
const adminUserAuth: AuthUserDto = Object.freeze({
|
||||
id: 'admin_id',
|
||||
@@ -73,28 +74,18 @@ const adminUserResponse = Object.freeze({
|
||||
createdAt: '2021-01-01',
|
||||
});
|
||||
|
||||
describe('UserService', () => {
|
||||
describe(UserService.name, () => {
|
||||
let sut: UserService;
|
||||
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
||||
|
||||
beforeEach(() => {
|
||||
userRepositoryMock = {
|
||||
get: jest.fn(),
|
||||
getAdmin: jest.fn(),
|
||||
getByEmail: jest.fn(),
|
||||
getByOAuthId: jest.fn(),
|
||||
getList: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
};
|
||||
beforeEach(async () => {
|
||||
userRepositoryMock = newUserRepositoryMock();
|
||||
sut = new UserService(userRepositoryMock);
|
||||
|
||||
when(userRepositoryMock.get).calledWith(adminUser.id).mockResolvedValue(adminUser);
|
||||
when(userRepositoryMock.get).calledWith(adminUser.id, undefined).mockResolvedValue(adminUser);
|
||||
when(userRepositoryMock.get).calledWith(immichUser.id).mockResolvedValue(immichUser);
|
||||
when(userRepositoryMock.get).calledWith(immichUser.id, undefined).mockResolvedValue(immichUser);
|
||||
|
||||
sut = new UserService(userRepositoryMock);
|
||||
});
|
||||
|
||||
describe('getAllUsers', () => {
|
||||
@@ -285,9 +276,7 @@ describe('UserService', () => {
|
||||
|
||||
describe('deleteUser', () => {
|
||||
it('cannot delete admin user', async () => {
|
||||
const result = sut.deleteUser(adminUserAuth, adminUserAuth.id);
|
||||
|
||||
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(sut.deleteUser(adminUserAuth, adminUserAuth.id)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
|
||||
it('should require the auth user be an admin', async () => {
|
||||
|
||||
12
server/libs/domain/test/api-key.repository.mock.ts
Normal file
12
server/libs/domain/test/api-key.repository.mock.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { IKeyRepository } from '../src';
|
||||
|
||||
export const newKeyRepositoryMock = (): jest.Mocked<IKeyRepository> => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
getKey: jest.fn(),
|
||||
getById: jest.fn(),
|
||||
getByUserId: jest.fn(),
|
||||
};
|
||||
};
|
||||
9
server/libs/domain/test/crypto.repository.mock.ts
Normal file
9
server/libs/domain/test/crypto.repository.mock.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ICryptoRepository } from '../src';
|
||||
|
||||
export const newCryptoRepositoryMock = (): jest.Mocked<ICryptoRepository> => {
|
||||
return {
|
||||
randomBytes: jest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')),
|
||||
compareSync: jest.fn().mockReturnValue(true),
|
||||
hash: jest.fn().mockImplementation((input) => Promise.resolve(`${input} (hashed)`)),
|
||||
};
|
||||
};
|
||||
44
server/libs/domain/test/fixtures.ts
Normal file
44
server/libs/domain/test/fixtures.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { UserEntity } from '@app/infra';
|
||||
import { AuthUserDto } from '../src';
|
||||
|
||||
export const authStub = {
|
||||
admin: Object.freeze<AuthUserDto>({
|
||||
id: 'admin_id',
|
||||
email: 'admin@test.com',
|
||||
isAdmin: true,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
}),
|
||||
user1: Object.freeze<AuthUserDto>({
|
||||
id: 'immich_id',
|
||||
email: 'immich@test.com',
|
||||
isAdmin: false,
|
||||
isPublicUser: false,
|
||||
isAllowUpload: true,
|
||||
}),
|
||||
};
|
||||
|
||||
export const entityStub = {
|
||||
admin: Object.freeze<UserEntity>({
|
||||
...authStub.admin,
|
||||
password: 'admin_password',
|
||||
firstName: 'admin_first_name',
|
||||
lastName: 'admin_last_name',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
tags: [],
|
||||
}),
|
||||
user1: Object.freeze<UserEntity>({
|
||||
...authStub.user1,
|
||||
password: 'immich_password',
|
||||
firstName: 'immich_first_name',
|
||||
lastName: 'immich_last_name',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: '2021-01-01',
|
||||
tags: [],
|
||||
}),
|
||||
};
|
||||
4
server/libs/domain/test/index.ts
Normal file
4
server/libs/domain/test/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './api-key.repository.mock';
|
||||
export * from './crypto.repository.mock';
|
||||
export * from './fixtures';
|
||||
export * from './user.repository.mock';
|
||||
15
server/libs/domain/test/user.repository.mock.ts
Normal file
15
server/libs/domain/test/user.repository.mock.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IUserRepository } from '../src';
|
||||
|
||||
export const newUserRepositoryMock = (): jest.Mocked<IUserRepository> => {
|
||||
return {
|
||||
get: jest.fn(),
|
||||
getAdmin: jest.fn(),
|
||||
getByEmail: jest.fn(),
|
||||
getByOAuthId: jest.fn(),
|
||||
getList: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
};
|
||||
};
|
||||
9
server/libs/infra/src/auth/crypto.repository.ts
Normal file
9
server/libs/infra/src/auth/crypto.repository.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ICryptoRepository } from '@app/domain';
|
||||
import { compareSync, hash } from 'bcrypt';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
export const cryptoRepository: ICryptoRepository = {
|
||||
randomBytes,
|
||||
hash,
|
||||
compareSync,
|
||||
};
|
||||
45
server/libs/infra/src/db/repository/api-key.repository.ts
Normal file
45
server/libs/infra/src/db/repository/api-key.repository.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { IKeyRepository } from '@app/domain';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { APIKeyEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyRepository implements IKeyRepository {
|
||||
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
||||
|
||||
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
return this.repository.save(dto);
|
||||
}
|
||||
|
||||
async update(userId: string, id: number, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
await this.repository.update({ userId, id }, dto);
|
||||
return this.repository.findOneOrFail({ where: { id: dto.id } });
|
||||
}
|
||||
|
||||
async delete(userId: string, id: number): Promise<void> {
|
||||
await this.repository.delete({ userId, id });
|
||||
}
|
||||
|
||||
getKey(id: number): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
},
|
||||
where: { id },
|
||||
relations: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getById(userId: string, id: number): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({ where: { userId, id } });
|
||||
}
|
||||
|
||||
getByUserId(userId: string): Promise<APIKeyEntity[]> {
|
||||
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './api-key.repository';
|
||||
export * from './user.repository';
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { ICryptoRepository, IKeyRepository, IUserRepository } from '@app/domain';
|
||||
import { databaseConfig, UserEntity } from '@app/infra';
|
||||
import { IUserRepository } from '@app/domain';
|
||||
import { Global, Module, Provider } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UserRepository } from './db';
|
||||
import { cryptoRepository } from './auth/crypto.repository';
|
||||
import { APIKeyEntity, UserRepository } from './db';
|
||||
import { APIKeyRepository } from './db/repository';
|
||||
|
||||
const providers: Provider[] = [
|
||||
//
|
||||
{ provide: ICryptoRepository, useValue: cryptoRepository },
|
||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||
{ provide: IUserRepository, useClass: UserRepository },
|
||||
];
|
||||
|
||||
@@ -14,7 +18,7 @@ const providers: Provider[] = [
|
||||
imports: [
|
||||
//
|
||||
TypeOrmModule.forRoot(databaseConfig),
|
||||
TypeOrmModule.forFeature([UserEntity]),
|
||||
TypeOrmModule.forFeature([APIKeyEntity, UserEntity]),
|
||||
],
|
||||
providers: [...providers],
|
||||
exports: [...providers],
|
||||
|
||||
Reference in New Issue
Block a user