fix: replace first and last name with single field (#4915)

This commit is contained in:
Brian Austin 2023-11-11 20:03:32 -05:00 committed by GitHub
parent 413ab2c538
commit 7fca0d8da5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
98 changed files with 567 additions and 1147 deletions

View file

@ -15,12 +15,12 @@ export class ResetAdminPasswordCommand extends CommandRunner {
async run(): Promise<void> {
const ask = (admin: UserResponseDto) => {
const { id, oauthId, email, firstName, lastName } = admin;
const { id, oauthId, email, name } = admin;
console.log(`Found Admin:
- ID=${id}
- OAuth ID=${oauthId}
- Email=${email}
- Name=${firstName} ${lastName}`);
- Name=${name}`);
return this.inquirer.ask<{ password: string }>('prompt-password', undefined).then(({ password }) => password);
};

View file

@ -33,8 +33,7 @@ export class LoginResponseDto {
accessToken!: string;
userId!: string;
userEmail!: string;
firstName!: string;
lastName!: string;
name!: string;
profileImagePath!: string;
isAdmin!: boolean;
shouldChangePassword!: boolean;
@ -45,8 +44,7 @@ export function mapLoginResponse(entity: UserEntity, accessToken: string): Login
accessToken: accessToken,
userId: entity.id,
userEmail: entity.email,
firstName: entity.firstName,
lastName: entity.lastName,
name: entity.name,
isAdmin: entity.isAdmin,
profileImagePath: entity.profileImagePath,
shouldChangePassword: entity.shouldChangePassword,
@ -62,12 +60,7 @@ export class SignUpDto extends LoginCredentialDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ example: 'Admin' })
firstName!: string;
@IsString()
@IsNotEmpty()
@ApiProperty({ example: 'Doe' })
lastName!: string;
name!: string;
}
export class ChangePasswordDto {

View file

@ -236,7 +236,7 @@ describe('AuthService', () => {
});
describe('adminSignUp', () => {
const dto: SignUpDto = { email: 'test@immich.com', password: 'password', firstName: 'immich', lastName: 'admin' };
const dto: SignUpDto = { email: 'test@immich.com', password: 'password', name: 'immich admin' };
it('should only allow one admin', async () => {
userMock.getAdmin.mockResolvedValue({} as UserEntity);
@ -251,8 +251,7 @@ describe('AuthService', () => {
id: 'admin',
createdAt: new Date('2021-01-01'),
email: 'test@immich.com',
firstName: 'immich',
lastName: 'admin',
name: 'immich admin',
});
expect(userMock.getAdmin).toHaveBeenCalled();
expect(userMock.create).toHaveBeenCalled();

View file

@ -146,8 +146,7 @@ export class AuthService {
const admin = await this.userCore.createUser({
isAdmin: true,
email: dto.email,
firstName: dto.firstName,
lastName: dto.lastName,
name: dto.name,
password: dto.password,
storageLabel: 'admin',
});
@ -273,9 +272,9 @@ export class AuthService {
storageLabel = null;
}
const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`;
user = await this.userCore.createUser({
firstName: profile.given_name || '',
lastName: profile.family_name || '',
name: userName,
email: profile.email,
oauthId: profile.sub,
storageLabel,

View file

@ -7,10 +7,9 @@ import { PartnerService } from './partner.service';
const responseDto = {
admin: <PartnerResponseDto>{
email: 'admin@test.com',
firstName: 'admin_first_name',
name: 'admin_name',
id: 'admin_id',
isAdmin: true,
lastName: 'admin_last_name',
oauthId: '',
profileImagePath: '',
shouldChangePassword: false,
@ -24,10 +23,9 @@ const responseDto = {
},
user1: <PartnerResponseDto>{
email: 'immich@test.com',
firstName: 'immich_first_name',
name: 'immich_name',
id: 'user-id',
isAdmin: false,
lastName: 'immich_last_name',
oauthId: '',
profileImagePath: '',
shouldChangePassword: false,

View file

@ -6,8 +6,7 @@ export interface UserListFilter {
export interface UserStatsQueryResponse {
userId: string;
userFirstName: string;
userLastName: string;
userName: string;
photos: number;
videos: number;
usage: number;

View file

@ -38,9 +38,7 @@ export class UsageByUserDto {
@ApiProperty({ type: 'string' })
userId!: string;
@ApiProperty({ type: 'string' })
userFirstName!: string;
@ApiProperty({ type: 'string' })
userLastName!: string;
userName!: string;
@ApiProperty({ type: 'integer' })
photos!: number;
@ApiProperty({ type: 'integer' })

View file

@ -195,24 +195,21 @@ describe(ServerInfoService.name, () => {
userMock.getUserStats.mockResolvedValue([
{
userId: 'user1',
userFirstName: '1',
userLastName: 'User',
userName: '1 User',
photos: 10,
videos: 11,
usage: 12345,
},
{
userId: 'user2',
userFirstName: '2',
userLastName: 'User',
userName: '2 User',
photos: 10,
videos: 20,
usage: 123456,
},
{
userId: 'user3',
userFirstName: '3',
userLastName: 'User',
userName: '3 User',
photos: 100,
videos: 0,
usage: 987654,
@ -227,25 +224,22 @@ describe(ServerInfoService.name, () => {
{
photos: 10,
usage: 12345,
userFirstName: '1',
userName: '1 User',
userId: 'user1',
userLastName: 'User',
videos: 11,
},
{
photos: 10,
usage: 123456,
userFirstName: '2',
userName: '2 User',
userId: 'user2',
userLastName: 'User',
videos: 20,
},
{
photos: 100,
usage: 987654,
userFirstName: '3',
userName: '3 User',
userId: 'user3',
userLastName: 'User',
videos: 0,
},
],

View file

@ -98,8 +98,7 @@ export class ServerInfoService {
for (const user of userStats) {
const usage = new UsageByUserDto();
usage.userId = user.userId;
usage.userFirstName = user.userFirstName;
usage.userLastName = user.userLastName;
usage.userName = user.userName;
usage.photos = user.photos;
usage.videos = user.videos;
usage.usage = user.usage;

View file

@ -7,8 +7,7 @@ describe('create user DTO', () => {
const params: Partial<CreateUserDto> = {
email: undefined,
password: 'password',
firstName: 'first name',
lastName: 'last name',
name: 'name',
};
let dto: CreateUserDto = plainToInstance(CreateUserDto, params);
let errors = await validate(dto);
@ -31,8 +30,7 @@ describe('create user DTO', () => {
const dto = plainToInstance(CreateUserDto, {
email: someEmail,
password: 'some password',
firstName: 'some first name',
lastName: 'some last name',
name: 'some name',
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
@ -48,8 +46,7 @@ describe('create admin DTO', () => {
isAdmin: true,
email: someEmail,
password: 'some password',
firstName: 'some first name',
lastName: 'some last name',
name: 'some name',
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
@ -64,8 +61,7 @@ describe('create user oauth DTO', () => {
const dto = plainToInstance(CreateUserOAuthDto, {
email: someEmail,
oauthId: 'some oauth id',
firstName: 'some first name',
lastName: 'some last name',
name: 'some name',
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);

View file

@ -13,11 +13,7 @@ export class CreateUserDto {
@IsNotEmpty()
@IsString()
firstName!: string;
@IsNotEmpty()
@IsString()
lastName!: string;
name!: string;
@Optional({ nullable: true })
@IsString()
@ -45,10 +41,7 @@ export class CreateAdminDto {
password!: string;
@IsNotEmpty()
firstName!: string;
@IsNotEmpty()
lastName!: string;
name!: string;
}
export class CreateUserOAuthDto {
@ -59,7 +52,5 @@ export class CreateUserOAuthDto {
@IsNotEmpty()
oauthId!: string;
firstName?: string;
lastName?: string;
name?: string;
}

View file

@ -17,12 +17,7 @@ export class UpdateUserDto {
@Optional()
@IsString()
@IsNotEmpty()
firstName?: string;
@Optional()
@IsString()
@IsNotEmpty()
lastName?: string;
name?: string;
@Optional()
@IsString()

View file

@ -2,8 +2,7 @@ import { UserEntity } from '@app/infra/entities';
export class UserDto {
id!: string;
firstName!: string;
lastName!: string;
name!: string;
email!: string;
profileImagePath!: string;
}
@ -24,8 +23,7 @@ export const mapSimpleUser = (entity: UserEntity): UserDto => {
return {
id: entity.id,
email: entity.email,
firstName: entity.firstName,
lastName: entity.lastName,
name: entity.name,
profileImagePath: entity.profileImagePath,
};
};

View file

@ -289,8 +289,7 @@ describe(UserService.name, () => {
await expect(
sut.create({
email: 'john_smith@email.com',
firstName: 'John',
lastName: 'Smith',
name: 'John Smith',
password: 'password',
}),
).rejects.toBeInstanceOf(BadRequestException);
@ -303,8 +302,7 @@ describe(UserService.name, () => {
await expect(
sut.create({
email: userStub.user1.email,
firstName: userStub.user1.firstName,
lastName: userStub.user1.lastName,
name: userStub.user1.name,
password: 'password',
storageLabel: 'label',
}),
@ -313,8 +311,7 @@ describe(UserService.name, () => {
expect(userMock.getAdmin).toBeCalled();
expect(userMock.create).toBeCalledWith({
email: userStub.user1.email,
firstName: userStub.user1.firstName,
lastName: userStub.user1.lastName,
name: userStub.user1.name,
storageLabel: 'label',
password: expect.anything(),
});

View file

@ -16,10 +16,7 @@ export class UserEntity {
id!: string;
@Column({ default: '' })
firstName!: string;
@Column({ default: '' })
lastName!: string;
name!: string;
@Column({ default: false })
isAdmin!: boolean;

View file

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddUsername1699322864544 implements MigrationInterface {
name = 'AddUsername1699322864544'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "users" ADD "name" character varying NOT NULL DEFAULT ''`);
await queryRunner.query(`UPDATE "users" SET "name" = CONCAT(COALESCE("firstName", ''), ' ', COALESCE("lastName", ''))`);
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "firstName"`);
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "lastName"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "name"`);
await queryRunner.query(`ALTER TABLE "users" ADD "lastName" character varying NOT NULL DEFAULT ''`);
await queryRunner.query(`ALTER TABLE "users" ADD "firstName" character varying NOT NULL DEFAULT ''`);
await queryRunner.query(`UPDATE "users" SET "lastName" = COALESCE("email", '')`);
await queryRunner.query(`UPDATE "users" SET "firstName" = COALESCE("email", '')`);
}
}

View file

@ -80,8 +80,7 @@ export class UserRepository implements IUserRepository {
const stats = await this.userRepository
.createQueryBuilder('users')
.select('users.id', 'userId')
.addSelect('users.firstName', 'userFirstName')
.addSelect('users.lastName', 'userLastName')
.addSelect('users.name', 'userName')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
.addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage')