infra(server): fix Album TypeORM relations and change ids to uuids (#1582)

* infra: make api-key primary key column a UUID

* infra: move ManyToMany relations in album entity, make ownerId ManyToOne

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Zack Pollard
2023-02-18 20:58:55 +00:00
committed by GitHub
parent 917f1dea9f
commit 000d0a08f4
24 changed files with 368 additions and 461 deletions

View File

@@ -21,11 +21,9 @@ export class AlbumResponseDto {
export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
const sharedUsers: UserResponseDto[] = [];
entity.sharedUsers?.forEach((userAlbum) => {
if (userAlbum.userInfo) {
const user = mapUser(userAlbum.userInfo);
sharedUsers.push(user);
}
entity.sharedUsers?.forEach((user) => {
const userDto = mapUser(user);
sharedUsers.push(userDto);
});
return {
@@ -38,7 +36,7 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
owner: mapUser(entity.owner),
sharedUsers,
shared: sharedUsers.length > 0 || entity.sharedLinks?.length > 0,
assets: entity.assets?.map((assetAlbum) => mapAsset(assetAlbum.assetInfo)) || [],
assets: entity.assets?.map((asset) => mapAsset(asset)) || [],
assetCount: entity.assets?.length || 0,
};
}
@@ -46,11 +44,9 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto {
const sharedUsers: UserResponseDto[] = [];
entity.sharedUsers?.forEach((userAlbum) => {
if (userAlbum.userInfo) {
const user = mapUser(userAlbum.userInfo);
sharedUsers.push(user);
}
entity.sharedUsers?.forEach((user) => {
const userDto = mapUser(user);
sharedUsers.push(userDto);
});
return {

View File

@@ -4,13 +4,13 @@ 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>;
update(userId: string, id: string, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
delete(userId: string, id: string): Promise<void>;
/**
* Includes the hashed `key` for verification
* @param id
*/
getKey(hashedToken: string): Promise<APIKeyEntity | null>;
getById(userId: string, id: number): Promise<APIKeyEntity | null>;
getById(userId: string, id: string): Promise<APIKeyEntity | null>;
getByUserId(userId: string): Promise<APIKeyEntity[]>;
}

View File

@@ -47,17 +47,19 @@ describe(APIKeyService.name, () => {
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);
await expect(sut.update(authStub.admin, 'random-guid', { name: 'New Name' })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(keyMock.update).not.toHaveBeenCalledWith(1);
expect(keyMock.update).not.toHaveBeenCalledWith('random-guid');
});
it('should update a key', async () => {
keyMock.getById.mockResolvedValue(keyStub.admin);
await sut.update(authStub.admin, 1, { name: 'New Name' });
await sut.update(authStub.admin, 'random-guid', { name: 'New Name' });
expect(keyMock.update).toHaveBeenCalledWith(authStub.admin.id, 1, { name: 'New Name' });
expect(keyMock.update).toHaveBeenCalledWith(authStub.admin.id, 'random-guid', { name: 'New Name' });
});
});
@@ -65,17 +67,17 @@ describe(APIKeyService.name, () => {
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);
await expect(sut.delete(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException);
expect(keyMock.delete).not.toHaveBeenCalledWith(1);
expect(keyMock.delete).not.toHaveBeenCalledWith('random-guid');
});
it('should delete a key', async () => {
keyMock.getById.mockResolvedValue(keyStub.admin);
await sut.delete(authStub.admin, 1);
await sut.delete(authStub.admin, 'random-guid');
expect(keyMock.delete).toHaveBeenCalledWith(authStub.admin.id, 1);
expect(keyMock.delete).toHaveBeenCalledWith(authStub.admin.id, 'random-guid');
});
});
@@ -83,17 +85,17 @@ describe(APIKeyService.name, () => {
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);
await expect(sut.getById(authStub.admin, 'random-guid')).rejects.toBeInstanceOf(BadRequestException);
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 1);
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'random-guid');
});
it('should get a key by id', async () => {
keyMock.getById.mockResolvedValue(keyStub.admin);
await sut.getById(authStub.admin, 1);
await sut.getById(authStub.admin, 'random-guid');
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 1);
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'random-guid');
});
});

View File

@@ -24,7 +24,7 @@ export class APIKeyService {
return { secret, apiKey: mapKey(entity) };
}
async update(authUser: AuthUserDto, id: number, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
async update(authUser: AuthUserDto, id: string, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
const exists = await this.repository.getById(authUser.id, id);
if (!exists) {
throw new BadRequestException('API Key not found');
@@ -35,7 +35,7 @@ export class APIKeyService {
});
}
async delete(authUser: AuthUserDto, id: number): Promise<void> {
async delete(authUser: AuthUserDto, id: string): Promise<void> {
const exists = await this.repository.getById(authUser.id, id);
if (!exists) {
throw new BadRequestException('API Key not found');
@@ -44,7 +44,7 @@ export class APIKeyService {
await this.repository.delete(authUser.id, id);
}
async getById(authUser: AuthUserDto, id: number): Promise<APIKeyResponseDto> {
async getById(authUser: AuthUserDto, id: string): Promise<APIKeyResponseDto> {
const key = await this.repository.getById(authUser.id, id);
if (!key) {
throw new BadRequestException('API Key not found');

View File

@@ -1,9 +1,7 @@
import { APIKeyEntity } from '@app/infra/db/entities';
import { ApiProperty } from '@nestjs/swagger';
export class APIKeyResponseDto {
@ApiProperty({ type: 'integer' })
id!: number;
id!: string;
name!: string;
createdAt!: string;
updatedAt!: string;

View File

@@ -23,7 +23,7 @@ export class SharedLinkResponseDto {
export function mapSharedLink(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
const linkAssets = sharedLink.assets || [];
const albumAssets = (sharedLink?.album?.assets || []).map((albumAsset) => albumAsset.assetInfo);
const albumAssets = (sharedLink?.album?.assets || []).map((asset) => asset);
const assets = _.uniqBy([...linkAssets, ...albumAssets], (asset) => asset.id);
@@ -45,7 +45,7 @@ export function mapSharedLink(sharedLink: SharedLinkEntity): SharedLinkResponseD
export function mapSharedLinkWithNoExif(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
const linkAssets = sharedLink.assets || [];
const albumAssets = (sharedLink?.album?.assets || []).map((albumAsset) => albumAsset.assetInfo);
const albumAssets = (sharedLink?.album?.assets || []).map((asset) => asset);
const assets = _.uniqBy([...linkAssets, ...albumAssets], (asset) => asset.id);