feat(server,web): update email address (#1186)

* feat: change email

* test: change email
This commit is contained in:
Jason Rasmussen 2022-12-27 11:36:31 -05:00 committed by GitHub
parent fdf51a8855
commit 380f719fd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 72 additions and 9 deletions

View file

@ -9,6 +9,7 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**email** | **String** | | [optional]
**password** | **String** | | [optional]
**firstName** | **String** | | [optional]
**lastName** | **String** | | [optional]

View file

@ -14,6 +14,7 @@ class UpdateUserDto {
/// Returns a new [UpdateUserDto] instance.
UpdateUserDto({
required this.id,
this.email,
this.password,
this.firstName,
this.lastName,
@ -24,6 +25,14 @@ class UpdateUserDto {
String id;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? email;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
@ -75,6 +84,7 @@ class UpdateUserDto {
@override
bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto &&
other.id == id &&
other.email == email &&
other.password == password &&
other.firstName == firstName &&
other.lastName == lastName &&
@ -86,6 +96,7 @@ class UpdateUserDto {
int get hashCode =>
// ignore: unnecessary_parenthesis
(id.hashCode) +
(email == null ? 0 : email!.hashCode) +
(password == null ? 0 : password!.hashCode) +
(firstName == null ? 0 : firstName!.hashCode) +
(lastName == null ? 0 : lastName!.hashCode) +
@ -94,11 +105,16 @@ class UpdateUserDto {
(profileImagePath == null ? 0 : profileImagePath!.hashCode);
@override
String toString() => 'UpdateUserDto[id=$id, password=$password, firstName=$firstName, lastName=$lastName, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword, profileImagePath=$profileImagePath]';
String toString() => 'UpdateUserDto[id=$id, email=$email, password=$password, firstName=$firstName, lastName=$lastName, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword, profileImagePath=$profileImagePath]';
Map<String, dynamic> toJson() {
final _json = <String, dynamic>{};
_json[r'id'] = id;
if (email != null) {
_json[r'email'] = email;
} else {
_json[r'email'] = null;
}
if (password != null) {
_json[r'password'] = password;
} else {
@ -152,6 +168,7 @@ class UpdateUserDto {
return UpdateUserDto(
id: mapValueOfType<String>(json, r'id')!,
email: mapValueOfType<String>(json, r'email'),
password: mapValueOfType<String>(json, r'password'),
firstName: mapValueOfType<String>(json, r'firstName'),
lastName: mapValueOfType<String>(json, r'lastName'),

View file

@ -21,6 +21,11 @@ void main() {
// TODO
});
// String email
test('to test the property `email`', () async {
// TODO
});
// String password
test('to test the property `password`', () async {
// TODO

View file

@ -1,9 +1,13 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { IsEmail, IsNotEmpty, IsOptional } from 'class-validator';
export class UpdateUserDto {
@IsNotEmpty()
id!: string;
@IsEmail()
@IsOptional()
email?: string;
@IsOptional()
password?: string;

View file

@ -28,6 +28,13 @@ export class UserCore {
throw new BadRequestException('Admin user exists');
}
if (dto.email) {
const duplicate = await this.userRepository.getByEmail(dto.email);
if (duplicate && duplicate.id !== id) {
throw new BadRequestException('Email already in user by another account');
}
}
try {
if (dto.password) {
dto.password = await hash(dto.password, SALT_ROUNDS);

View file

@ -102,6 +102,28 @@ describe('UserService', () => {
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
});
it('should let a user change their email', async () => {
const dto = { id: immichUser.id, email: 'updated@test.com' };
userRepositoryMock.get.mockResolvedValue(immichUser);
userRepositoryMock.update.mockResolvedValue(immichUser);
await sut.updateUser(immichUser, dto);
expect(userRepositoryMock.update).toHaveBeenCalledWith(immichUser.id, { email: 'updated@test.com' });
});
it('should not let a user change their email to one already in use', async () => {
const dto = { id: immichUser.id, email: 'updated@test.com' };
userRepositoryMock.get.mockResolvedValue(immichUser);
userRepositoryMock.getByEmail.mockResolvedValue(adminUser);
await expect(sut.updateUser(immichUser, dto)).rejects.toBeInstanceOf(BadRequestException);
expect(userRepositoryMock.update).not.toHaveBeenCalled();
});
it('admin can update any user information', async () => {
const update: UpdateUserDto = {
id: immichUser.id,

View file

@ -2400,6 +2400,9 @@
"id": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string"
},

View file

@ -1779,6 +1779,12 @@ export interface UpdateUserDto {
* @memberof UpdateUserDto
*/
'id': string;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'email'?: string;
/**
*
* @type {string}

View file

@ -1,5 +1,6 @@
<script lang="ts" context="module">
export enum SettingInputFieldType {
EMAIL = 'email',
TEXT = 'text',
NUMBER = 'number',
PASSWORD = 'password'

View file

@ -5,6 +5,7 @@
} from '$lib/components/shared-components/notification/notification';
import { api, UserResponseDto } from '@api';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import SettingInputField, {
SettingInputFieldType
} from '../admin-page/settings/setting-input-field.svelte';
@ -15,6 +16,7 @@
try {
const { data } = await api.userApi.updateUser({
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName
});
@ -26,11 +28,7 @@
type: NotificationType.Info
});
} catch (error) {
console.error('Error [user-profile] [updateProfile]', error);
notificationController.show({
message: 'Unable to save profile',
type: NotificationType.Error
});
handleError(error, 'Unable to save profile');
}
};
</script>
@ -47,10 +45,9 @@
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
inputType={SettingInputFieldType.EMAIL}
label="Email"
bind:value={user.email}
disabled={true}
/>
<SettingInputField