mirror of
https://github.com/immich-app/immich.git
synced 2025-12-16 09:13:13 +03:00
chore(server,mobile): remove device info entity (#1527)
* chore(server): remove unused device info code * chore: generate open api * remove any DeviceTypeEnum usage from mobile * chore: coverage * fix: drop device info table --------- Co-authored-by: Fynn Petersen-Frey <zody22@gmail.com>
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
AlbumController,
|
||||
APIKeyController,
|
||||
AuthController,
|
||||
DeviceInfoController,
|
||||
JobController,
|
||||
OAuthController,
|
||||
SearchController,
|
||||
@@ -36,7 +35,6 @@ import { AppCronJobs } from './app.cron-jobs';
|
||||
AlbumController,
|
||||
APIKeyController,
|
||||
AuthController,
|
||||
DeviceInfoController,
|
||||
JobController,
|
||||
OAuthController,
|
||||
SearchController,
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import {
|
||||
AuthUserDto,
|
||||
DeviceInfoResponseDto as ResponseDto,
|
||||
DeviceInfoService,
|
||||
UpsertDeviceInfoDto as UpsertDto,
|
||||
} from '@app/domain';
|
||||
import { Body, Controller, Put } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { GetAuthUser } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
|
||||
@ApiTags('Device Info')
|
||||
@Controller('device-info')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class DeviceInfoController {
|
||||
constructor(private readonly service: DeviceInfoService) {}
|
||||
|
||||
@Put()
|
||||
upsertDeviceInfo(@GetAuthUser() authUser: AuthUserDto, @Body() dto: UpsertDto): Promise<ResponseDto> {
|
||||
return this.service.upsert(authUser, dto);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
export * from './album.controller';
|
||||
export * from './api-key.controller';
|
||||
export * from './auth.controller';
|
||||
export * from './device-info.controller';
|
||||
export * from './job.controller';
|
||||
export * from './oauth.controller';
|
||||
export * from './search.controller';
|
||||
|
||||
@@ -500,45 +500,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/device-info": {
|
||||
"put": {
|
||||
"operationId": "upsertDeviceInfo",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpsertDeviceInfoDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeviceInfoResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Device Info"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/jobs": {
|
||||
"get": {
|
||||
"operationId": "getAllJobsStatus",
|
||||
@@ -4124,63 +4085,6 @@
|
||||
"redirectUri"
|
||||
]
|
||||
},
|
||||
"DeviceTypeEnum": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"IOS",
|
||||
"ANDROID",
|
||||
"WEB"
|
||||
]
|
||||
},
|
||||
"UpsertDeviceInfoDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deviceType": {
|
||||
"$ref": "#/components/schemas/DeviceTypeEnum"
|
||||
},
|
||||
"deviceId": {
|
||||
"type": "string"
|
||||
},
|
||||
"isAutoBackup": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"deviceType",
|
||||
"deviceId"
|
||||
]
|
||||
},
|
||||
"DeviceInfoResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"deviceType": {
|
||||
"$ref": "#/components/schemas/DeviceTypeEnum"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"isAutoBackup": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"deviceType",
|
||||
"userId",
|
||||
"deviceId",
|
||||
"createdAt",
|
||||
"isAutoBackup"
|
||||
]
|
||||
},
|
||||
"JobCountsDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { DeviceInfoEntity } from '@app/infra/entities';
|
||||
import { IDeviceInfoRepository } from './device-info.repository';
|
||||
|
||||
type UpsertKeys = Pick<DeviceInfoEntity, 'deviceId' | 'userId'>;
|
||||
type UpsertEntity = UpsertKeys & Partial<DeviceInfoEntity>;
|
||||
|
||||
export class DeviceInfoCore {
|
||||
constructor(private repository: IDeviceInfoRepository) {}
|
||||
|
||||
async upsert(entity: UpsertEntity) {
|
||||
const exists = await this.repository.get(entity.userId, entity.deviceId);
|
||||
if (!exists) {
|
||||
if (!entity.isAutoBackup) {
|
||||
entity.isAutoBackup = false;
|
||||
}
|
||||
return this.repository.save(entity);
|
||||
}
|
||||
|
||||
exists.isAutoBackup = entity.isAutoBackup ?? exists.isAutoBackup;
|
||||
exists.deviceType = entity.deviceType ?? exists.deviceType;
|
||||
return this.repository.save(exists);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { DeviceInfoEntity } from '@app/infra/entities';
|
||||
|
||||
export const IDeviceInfoRepository = 'IDeviceInfoRepository';
|
||||
|
||||
export interface IDeviceInfoRepository {
|
||||
get(userId: string, deviceId: string): Promise<DeviceInfoEntity | null>;
|
||||
save(entity: Partial<DeviceInfoEntity>): Promise<DeviceInfoEntity>;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { DeviceInfoEntity, DeviceType } from '@app/infra/entities';
|
||||
import { authStub, newDeviceInfoRepositoryMock } from '../../test';
|
||||
import { IDeviceInfoRepository } from './device-info.repository';
|
||||
import { DeviceInfoService } from './device-info.service';
|
||||
|
||||
const deviceId = 'device-123';
|
||||
const userId = 'user-123';
|
||||
|
||||
describe('DeviceInfoService', () => {
|
||||
let sut: DeviceInfoService;
|
||||
let repositoryMock: jest.Mocked<IDeviceInfoRepository>;
|
||||
|
||||
beforeEach(async () => {
|
||||
repositoryMock = newDeviceInfoRepositoryMock();
|
||||
|
||||
sut = new DeviceInfoService(repositoryMock);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(sut).toBeDefined();
|
||||
});
|
||||
|
||||
describe('upsert', () => {
|
||||
it('should create a new record', async () => {
|
||||
const request = { deviceId, userId, deviceType: DeviceType.IOS } as DeviceInfoEntity;
|
||||
const response = { ...request, id: 1 } as DeviceInfoEntity;
|
||||
|
||||
repositoryMock.get.mockResolvedValue(null);
|
||||
repositoryMock.save.mockResolvedValue(response);
|
||||
|
||||
await expect(sut.upsert(authStub.user1, request)).resolves.toEqual(response);
|
||||
|
||||
expect(repositoryMock.get).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should update an existing record', async () => {
|
||||
const request = { deviceId, userId, deviceType: DeviceType.IOS, isAutoBackup: true } as DeviceInfoEntity;
|
||||
const response = { ...request, id: 1 } as DeviceInfoEntity;
|
||||
|
||||
repositoryMock.get.mockResolvedValue(response);
|
||||
repositoryMock.save.mockResolvedValue(response);
|
||||
|
||||
await expect(sut.upsert(authStub.user1, request)).resolves.toEqual(response);
|
||||
|
||||
expect(repositoryMock.get).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should keep properties that were not updated', async () => {
|
||||
const request = { deviceId, userId } as DeviceInfoEntity;
|
||||
const response = { id: 1, isAutoBackup: true, deviceId, userId, deviceType: DeviceType.WEB } as DeviceInfoEntity;
|
||||
|
||||
repositoryMock.get.mockResolvedValue(response);
|
||||
repositoryMock.save.mockResolvedValue(response);
|
||||
|
||||
await expect(sut.upsert(authStub.user1, request)).resolves.toEqual(response);
|
||||
|
||||
expect(repositoryMock.get).toHaveBeenCalledTimes(1);
|
||||
expect(repositoryMock.save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { DeviceInfoCore } from './device-info.core';
|
||||
import { IDeviceInfoRepository } from './device-info.repository';
|
||||
import { UpsertDeviceInfoDto } from './dto';
|
||||
import { DeviceInfoResponseDto, mapDeviceInfoResponse } from './response-dto';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceInfoService {
|
||||
private core: DeviceInfoCore;
|
||||
|
||||
constructor(@Inject(IDeviceInfoRepository) repository: IDeviceInfoRepository) {
|
||||
this.core = new DeviceInfoCore(repository);
|
||||
}
|
||||
|
||||
public async upsert(authUser: AuthUserDto, dto: UpsertDeviceInfoDto): Promise<DeviceInfoResponseDto> {
|
||||
const deviceInfo = await this.core.upsert({ ...dto, userId: authUser.id });
|
||||
return mapDeviceInfoResponse(deviceInfo);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './upsert-device-info.dto';
|
||||
@@ -1,15 +0,0 @@
|
||||
import { IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { DeviceType } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UpsertDeviceInfoDto {
|
||||
@IsNotEmpty()
|
||||
deviceId!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ enumName: 'DeviceTypeEnum', enum: DeviceType })
|
||||
deviceType!: DeviceType;
|
||||
|
||||
@IsOptional()
|
||||
isAutoBackup?: boolean;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './device-info.repository';
|
||||
export * from './device-info.service';
|
||||
export * from './dto';
|
||||
export * from './response-dto';
|
||||
@@ -1,26 +0,0 @@
|
||||
import { DeviceInfoEntity, DeviceType } from '@app/infra/entities';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DeviceInfoResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
id!: number;
|
||||
userId!: string;
|
||||
deviceId!: string;
|
||||
|
||||
@ApiProperty({ enumName: 'DeviceTypeEnum', enum: DeviceType })
|
||||
deviceType!: DeviceType;
|
||||
|
||||
createdAt!: string;
|
||||
isAutoBackup!: boolean;
|
||||
}
|
||||
|
||||
export function mapDeviceInfoResponse(entity: DeviceInfoEntity): DeviceInfoResponseDto {
|
||||
return {
|
||||
id: entity.id,
|
||||
userId: entity.userId,
|
||||
deviceId: entity.deviceId,
|
||||
deviceType: entity.deviceType,
|
||||
createdAt: entity.createdAt,
|
||||
isAutoBackup: entity.isAutoBackup,
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './device-info-response.dto';
|
||||
@@ -3,7 +3,6 @@ import { AlbumService } from './album';
|
||||
import { APIKeyService } from './api-key';
|
||||
import { AssetService } from './asset';
|
||||
import { AuthService } from './auth';
|
||||
import { DeviceInfoService } from './device-info';
|
||||
import { JobService } from './job';
|
||||
import { MediaService } from './media';
|
||||
import { OAuthService } from './oauth';
|
||||
@@ -21,7 +20,6 @@ const providers: Provider[] = [
|
||||
AssetService,
|
||||
APIKeyService,
|
||||
AuthService,
|
||||
DeviceInfoService,
|
||||
JobService,
|
||||
MediaService,
|
||||
OAuthService,
|
||||
|
||||
@@ -4,7 +4,6 @@ export * from './asset';
|
||||
export * from './auth';
|
||||
export * from './communication';
|
||||
export * from './crypto';
|
||||
export * from './device-info';
|
||||
export * from './domain.config';
|
||||
export * from './domain.constant';
|
||||
export * from './domain.module';
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { IDeviceInfoRepository } from '../src';
|
||||
|
||||
export const newDeviceInfoRepositoryMock = (): jest.Mocked<IDeviceInfoRepository> => {
|
||||
return {
|
||||
get: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
};
|
||||
@@ -3,7 +3,6 @@ export * from './api-key.repository.mock';
|
||||
export * from './asset.repository.mock';
|
||||
export * from './communication.repository.mock';
|
||||
export * from './crypto.repository.mock';
|
||||
export * from './device-info.repository.mock';
|
||||
export * from './fixtures';
|
||||
export * from './job.repository.mock';
|
||||
export * from './machine-learning.repository.mock';
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
|
||||
@Entity('device_info')
|
||||
@Unique(['userId', 'deviceId'])
|
||||
export class DeviceInfoEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@Column()
|
||||
deviceId!: string;
|
||||
|
||||
@Column()
|
||||
deviceType!: DeviceType;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
notificationToken!: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: string;
|
||||
|
||||
@Column({ type: 'bool', default: false })
|
||||
isAutoBackup!: boolean;
|
||||
}
|
||||
|
||||
export enum DeviceType {
|
||||
IOS = 'IOS',
|
||||
ANDROID = 'ANDROID',
|
||||
WEB = 'WEB',
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AlbumEntity } from './album.entity';
|
||||
import { APIKeyEntity } from './api-key.entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { DeviceInfoEntity } from './device-info.entity';
|
||||
import { SharedLinkEntity } from './shared-link.entity';
|
||||
import { SmartInfoEntity } from './smart-info.entity';
|
||||
import { SystemConfigEntity } from './system-config.entity';
|
||||
@@ -11,7 +10,6 @@ import { UserEntity } from './user.entity';
|
||||
export * from './album.entity';
|
||||
export * from './api-key.entity';
|
||||
export * from './asset.entity';
|
||||
export * from './device-info.entity';
|
||||
export * from './exif.entity';
|
||||
export * from './shared-link.entity';
|
||||
export * from './smart-info.entity';
|
||||
@@ -24,7 +22,6 @@ export const databaseEntities = [
|
||||
AssetEntity,
|
||||
AlbumEntity,
|
||||
APIKeyEntity,
|
||||
DeviceInfoEntity,
|
||||
UserEntity,
|
||||
SharedLinkEntity,
|
||||
SmartInfoEntity,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
IAssetRepository,
|
||||
ICommunicationRepository,
|
||||
ICryptoRepository,
|
||||
IDeviceInfoRepository,
|
||||
IGeocodingRepository,
|
||||
IJobRepository,
|
||||
IKeyRepository,
|
||||
@@ -32,7 +31,6 @@ import {
|
||||
AssetRepository,
|
||||
CommunicationRepository,
|
||||
CryptoRepository,
|
||||
DeviceInfoRepository,
|
||||
FilesystemProvider,
|
||||
GeocodingRepository,
|
||||
JobRepository,
|
||||
@@ -51,7 +49,6 @@ const providers: Provider[] = [
|
||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||
{ provide: ICommunicationRepository, useClass: CommunicationRepository },
|
||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||
{ provide: IDeviceInfoRepository, useClass: DeviceInfoRepository },
|
||||
{ provide: IGeocodingRepository, useClass: GeocodingRepository },
|
||||
{ provide: IJobRepository, useClass: JobRepository },
|
||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class DropDeviceInfoTable1682710252424 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`drop table device_info`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists device_info
|
||||
(
|
||||
id serial
|
||||
constraint "PK_b1c15a80b0a4e5f4eebadbdd92c"
|
||||
primary key,
|
||||
"userId" varchar not null,
|
||||
"deviceId" varchar not null,
|
||||
"deviceType" varchar not null,
|
||||
"notificationToken" varchar,
|
||||
"createdAt" timestamp default now() not null,
|
||||
"isAutoBackup" boolean default false not null,
|
||||
constraint "UQ_ebad78f36b10d15fbea8560e107"
|
||||
unique ("userId", "deviceId")
|
||||
);
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { IDeviceInfoRepository } from '@app/domain';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { DeviceInfoEntity } from '../entities';
|
||||
|
||||
export class DeviceInfoRepository implements IDeviceInfoRepository {
|
||||
constructor(@InjectRepository(DeviceInfoEntity) private repository: Repository<DeviceInfoEntity>) {}
|
||||
|
||||
get(userId: string, deviceId: string): Promise<DeviceInfoEntity | null> {
|
||||
return this.repository.findOne({ where: { userId, deviceId } });
|
||||
}
|
||||
|
||||
save(entity: Partial<DeviceInfoEntity>): Promise<DeviceInfoEntity> {
|
||||
return this.repository.save(entity);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ export * from './api-key.repository';
|
||||
export * from './asset.repository';
|
||||
export * from './communication.repository';
|
||||
export * from './crypto.repository';
|
||||
export * from './device-info.repository';
|
||||
export * from './filesystem.provider';
|
||||
export * from './geocoding.repository';
|
||||
export * from './job.repository';
|
||||
|
||||
@@ -141,9 +141,9 @@
|
||||
"coverageThreshold": {
|
||||
"./libs/domain/": {
|
||||
"branches": 80,
|
||||
"functions": 88,
|
||||
"functions": 87,
|
||||
"lines": 94,
|
||||
"statements": 94
|
||||
"statements": 93
|
||||
}
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
|
||||
Reference in New Issue
Block a user