1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

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 261 additions and 542 deletions

View file

@ -1341,24 +1341,18 @@ export interface CreateUserDto {
* @memberof CreateUserDto * @memberof CreateUserDto
*/ */
'externalPath'?: string | null; 'externalPath'?: string | null;
/**
*
* @type {string}
* @memberof CreateUserDto
*/
'firstName': string;
/**
*
* @type {string}
* @memberof CreateUserDto
*/
'lastName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof CreateUserDto * @memberof CreateUserDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof CreateUserDto
*/
'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -2137,12 +2131,6 @@ export interface LoginResponseDto {
* @memberof LoginResponseDto * @memberof LoginResponseDto
*/ */
'accessToken': string; 'accessToken': string;
/**
*
* @type {string}
* @memberof LoginResponseDto
*/
'firstName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -2154,7 +2142,7 @@ export interface LoginResponseDto {
* @type {string} * @type {string}
* @memberof LoginResponseDto * @memberof LoginResponseDto
*/ */
'lastName': string; 'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -2391,12 +2379,6 @@ export interface PartnerResponseDto {
* @memberof PartnerResponseDto * @memberof PartnerResponseDto
*/ */
'externalPath': string | null; 'externalPath': string | null;
/**
*
* @type {string}
* @memberof PartnerResponseDto
*/
'firstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -2415,18 +2397,18 @@ export interface PartnerResponseDto {
* @memberof PartnerResponseDto * @memberof PartnerResponseDto
*/ */
'isAdmin': boolean; 'isAdmin': boolean;
/**
*
* @type {string}
* @memberof PartnerResponseDto
*/
'lastName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof PartnerResponseDto * @memberof PartnerResponseDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof PartnerResponseDto
*/
'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -3431,13 +3413,7 @@ export interface SignUpDto {
* @type {string} * @type {string}
* @memberof SignUpDto * @memberof SignUpDto
*/ */
'firstName': string; 'name': string;
/**
*
* @type {string}
* @memberof SignUpDto
*/
'lastName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4380,12 +4356,6 @@ export interface UpdateUserDto {
* @memberof UpdateUserDto * @memberof UpdateUserDto
*/ */
'externalPath'?: string; 'externalPath'?: string;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'firstName'?: string;
/** /**
* *
* @type {string} * @type {string}
@ -4398,18 +4368,18 @@ export interface UpdateUserDto {
* @memberof UpdateUserDto * @memberof UpdateUserDto
*/ */
'isAdmin'?: boolean; 'isAdmin'?: boolean;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'lastName'?: string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof UpdateUserDto * @memberof UpdateUserDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'name'?: string;
/** /**
* *
* @type {string} * @type {string}
@ -4447,12 +4417,6 @@ export interface UsageByUserDto {
* @memberof UsageByUserDto * @memberof UsageByUserDto
*/ */
'usage': number; 'usage': number;
/**
*
* @type {string}
* @memberof UsageByUserDto
*/
'userFirstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4464,7 +4428,7 @@ export interface UsageByUserDto {
* @type {string} * @type {string}
* @memberof UsageByUserDto * @memberof UsageByUserDto
*/ */
'userLastName': string; 'userName': string;
/** /**
* *
* @type {number} * @type {number}
@ -4484,12 +4448,6 @@ export interface UserDto {
* @memberof UserDto * @memberof UserDto
*/ */
'email': string; 'email': string;
/**
*
* @type {string}
* @memberof UserDto
*/
'firstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4501,7 +4459,7 @@ export interface UserDto {
* @type {string} * @type {string}
* @memberof UserDto * @memberof UserDto
*/ */
'lastName': string; 'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -4539,12 +4497,6 @@ export interface UserResponseDto {
* @memberof UserResponseDto * @memberof UserResponseDto
*/ */
'externalPath': string | null; 'externalPath': string | null;
/**
*
* @type {string}
* @memberof UserResponseDto
*/
'firstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4557,18 +4509,18 @@ export interface UserResponseDto {
* @memberof UserResponseDto * @memberof UserResponseDto
*/ */
'isAdmin': boolean; 'isAdmin': boolean;
/**
*
* @type {string}
* @memberof UserResponseDto
*/
'lastName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof UserResponseDto * @memberof UserResponseDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof UserResponseDto
*/
'name': string;
/** /**
* *
* @type {string} * @type {string}

View file

@ -51,8 +51,7 @@ immich-admin list-users
{ {
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53', id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
email: 'immich@example.com.com', email: 'immich@example.com.com',
firstName: 'Immich', name: 'Immich Admin',
lastName: 'Admin',
storageLabel: 'admin', storageLabel: 'admin',
externalPath: null, externalPath: null,
profileImagePath: 'upload/profile/e65e6f88-2a30-4dbe-8dd9-1885f4889b53/e65e6f88-2a30-4dbe-8dd9-1885f4889b53.jpg', profileImagePath: 'upload/profile/e65e6f88-2a30-4dbe-8dd9-1885f4889b53/e65e6f88-2a30-4dbe-8dd9-1885f4889b53.jpg',

View file

@ -114,7 +114,7 @@
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",

View file

@ -48,8 +48,7 @@ class Activity {
: ActivityType.like, : ActivityType.like,
user = User( user = User(
email: dto.user.email, email: dto.user.email,
firstName: dto.user.firstName, name: dto.user.name,
lastName: dto.user.lastName,
profileImagePath: dto.user.profileImagePath, profileImagePath: dto.user.profileImagePath,
id: dto.user.id, id: dto.user.id,
// Placeholder values // Placeholder values

View file

@ -61,7 +61,7 @@ class ActivitiesPage extends HookConsumerWidget {
mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max, mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
children: [ children: [
Text( Text(
"${activity.user.firstName} ${activity.user.lastName}", activity.user.name,
style: textStyle, style: textStyle,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View file

@ -124,7 +124,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
) )
: const SizedBox(), : const SizedBox(),
title: Text( title: Text(
album.owner.value?.firstName ?? "", album.owner.value?.name ?? "",
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -155,7 +155,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
radius: 22, radius: 22,
), ),
title: Text( title: Text(
user.firstName, user.name,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),

View file

@ -3,8 +3,7 @@ class AuthenticationState {
final String userId; final String userId;
final String userEmail; final String userEmail;
final bool isAuthenticated; final bool isAuthenticated;
final String firstName; final String name;
final String lastName;
final bool isAdmin; final bool isAdmin;
final bool shouldChangePassword; final bool shouldChangePassword;
final String profileImagePath; final String profileImagePath;
@ -13,8 +12,7 @@ class AuthenticationState {
required this.userId, required this.userId,
required this.userEmail, required this.userEmail,
required this.isAuthenticated, required this.isAuthenticated,
required this.firstName, required this.name,
required this.lastName,
required this.isAdmin, required this.isAdmin,
required this.shouldChangePassword, required this.shouldChangePassword,
required this.profileImagePath, required this.profileImagePath,
@ -25,8 +23,7 @@ class AuthenticationState {
String? userId, String? userId,
String? userEmail, String? userEmail,
bool? isAuthenticated, bool? isAuthenticated,
String? firstName, String? name,
String? lastName,
bool? isAdmin, bool? isAdmin,
bool? shouldChangePassword, bool? shouldChangePassword,
String? profileImagePath, String? profileImagePath,
@ -36,8 +33,7 @@ class AuthenticationState {
userId: userId ?? this.userId, userId: userId ?? this.userId,
userEmail: userEmail ?? this.userEmail, userEmail: userEmail ?? this.userEmail,
isAuthenticated: isAuthenticated ?? this.isAuthenticated, isAuthenticated: isAuthenticated ?? this.isAuthenticated,
firstName: firstName ?? this.firstName, name: name ?? this.name,
lastName: lastName ?? this.lastName,
isAdmin: isAdmin ?? this.isAdmin, isAdmin: isAdmin ?? this.isAdmin,
shouldChangePassword: shouldChangePassword ?? this.shouldChangePassword, shouldChangePassword: shouldChangePassword ?? this.shouldChangePassword,
profileImagePath: profileImagePath ?? this.profileImagePath, profileImagePath: profileImagePath ?? this.profileImagePath,
@ -46,7 +42,7 @@ class AuthenticationState {
@override @override
String toString() { String toString() {
return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, firstName: $firstName, lastName: $lastName, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath)'; return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, name: $name, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath)';
} }
@override @override
@ -58,8 +54,7 @@ class AuthenticationState {
other.userId == userId && other.userId == userId &&
other.userEmail == userEmail && other.userEmail == userEmail &&
other.isAuthenticated == isAuthenticated && other.isAuthenticated == isAuthenticated &&
other.firstName == firstName && other.name == name &&
other.lastName == lastName &&
other.isAdmin == isAdmin && other.isAdmin == isAdmin &&
other.shouldChangePassword == shouldChangePassword && other.shouldChangePassword == shouldChangePassword &&
other.profileImagePath == profileImagePath; other.profileImagePath == profileImagePath;
@ -71,8 +66,7 @@ class AuthenticationState {
userId.hashCode ^ userId.hashCode ^
userEmail.hashCode ^ userEmail.hashCode ^
isAuthenticated.hashCode ^ isAuthenticated.hashCode ^
firstName.hashCode ^ name.hashCode ^
lastName.hashCode ^
isAdmin.hashCode ^ isAdmin.hashCode ^
shouldChangePassword.hashCode ^ shouldChangePassword.hashCode ^
profileImagePath.hashCode; profileImagePath.hashCode;

View file

@ -26,8 +26,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
deviceId: "", deviceId: "",
userId: "", userId: "",
userEmail: "", userEmail: "",
firstName: '', name: '',
lastName: '',
profileImagePath: '', profileImagePath: '',
isAdmin: false, isAdmin: false,
shouldChangePassword: false, shouldChangePassword: false,
@ -117,8 +116,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
deviceId: "", deviceId: "",
userId: "", userId: "",
userEmail: "", userEmail: "",
firstName: '', name: '',
lastName: '',
profileImagePath: '', profileImagePath: '',
isAdmin: false, isAdmin: false,
shouldChangePassword: false, shouldChangePassword: false,
@ -208,8 +206,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
isAuthenticated: true, isAuthenticated: true,
userId: user.id, userId: user.id,
userEmail: user.email, userEmail: user.email,
firstName: user.firstName, name: user.name,
lastName: user.lastName,
profileImagePath: user.profileImagePath, profileImagePath: user.profileImagePath,
isAdmin: user.isAdmin, isAdmin: user.isAdmin,
shouldChangePassword: shouldChangePassword, shouldChangePassword: shouldChangePassword,

View file

@ -46,8 +46,7 @@ class ChangePasswordForm extends HookConsumerWidget {
child: Text( child: Text(
'change_password_form_description'.tr( 'change_password_form_description'.tr(
namedArgs: { namedArgs: {
'firstName': authState.firstName, 'name': authState.name,
'lastName': authState.lastName,
}, },
), ),
style: TextStyle( style: TextStyle(

View file

@ -24,7 +24,7 @@ class PartnerList extends HookConsumerWidget {
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
leading: userAvatar(context, p, radius: 30), leading: userAvatar(context, p, radius: 30),
title: Text( title: Text(
"${p.firstName} ${p.lastName}'s photos", "${p.name}'s photos",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14, fontSize: 14,

View file

@ -25,7 +25,7 @@ class PartnerDetailPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("${partner.firstName} ${partner.lastName}"), title: Text(partner.name),
elevation: 0, elevation: 0,
centerTitle: false, centerTitle: false,
), ),
@ -34,7 +34,7 @@ class PartnerDetailPage extends HookConsumerWidget {
? Padding( ? Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
"It seems ${partner.firstName} does not have any photos...\n" "It seems ${partner.name} does not have any photos...\n"
"Or your server version does not match the app version."), "Or your server version does not match the app version."),
) )
: ImmichAssetGrid( : ImmichAssetGrid(

View file

@ -41,7 +41,7 @@ class PartnerPage extends HookConsumerWidget {
padding: const EdgeInsets.only(right: 8), padding: const EdgeInsets.only(right: 8),
child: userAvatar(context, u), child: userAvatar(context, u),
), ),
Text("${u.firstName} ${u.lastName}"), Text(u.name),
], ],
), ),
), ),
@ -71,7 +71,7 @@ class PartnerPage extends HookConsumerWidget {
return ConfirmDialog( return ConfirmDialog(
title: "partner_page_stop_sharing_title", title: "partner_page_stop_sharing_title",
content: content:
"partner_page_stop_sharing_content".tr(args: [u.firstName]), "partner_page_stop_sharing_content".tr(args: [u.name]),
onOk: () => ref.read(partnerServiceProvider).removePartner(u), onOk: () => ref.read(partnerServiceProvider).removePartner(u),
); );
}, },

View file

@ -68,11 +68,8 @@ class Album {
} }
final name = <String>[]; final name = <String>[];
if (owner.value?.firstName != null) { if (owner.value?.name != null) {
name.add(owner.value!.firstName); name.add(owner.value!.name);
}
if (owner.value?.lastName != null) {
name.add(owner.value!.lastName);
} }
return name.join(' '); return name.join(' ');

View file

@ -11,8 +11,7 @@ class User {
required this.id, required this.id,
required this.updatedAt, required this.updatedAt,
required this.email, required this.email,
required this.firstName, required this.name,
required this.lastName,
required this.isAdmin, required this.isAdmin,
this.isPartnerSharedBy = false, this.isPartnerSharedBy = false,
this.isPartnerSharedWith = false, this.isPartnerSharedWith = false,
@ -27,8 +26,7 @@ class User {
: id = dto.id, : id = dto.id,
updatedAt = dto.updatedAt, updatedAt = dto.updatedAt,
email = dto.email, email = dto.email,
firstName = dto.firstName, name = dto.name,
lastName = dto.lastName,
isPartnerSharedBy = false, isPartnerSharedBy = false,
isPartnerSharedWith = false, isPartnerSharedWith = false,
profileImagePath = dto.profileImagePath, profileImagePath = dto.profileImagePath,
@ -39,8 +37,7 @@ class User {
: id = dto.id, : id = dto.id,
updatedAt = dto.updatedAt, updatedAt = dto.updatedAt,
email = dto.email, email = dto.email,
firstName = dto.firstName, name = dto.name,
lastName = dto.lastName,
isPartnerSharedBy = false, isPartnerSharedBy = false,
isPartnerSharedWith = false, isPartnerSharedWith = false,
profileImagePath = dto.profileImagePath, profileImagePath = dto.profileImagePath,
@ -52,8 +49,7 @@ class User {
String id; String id;
DateTime updatedAt; DateTime updatedAt;
String email; String email;
String firstName; String name;
String lastName;
bool isPartnerSharedBy; bool isPartnerSharedBy;
bool isPartnerSharedWith; bool isPartnerSharedWith;
bool isAdmin; bool isAdmin;
@ -72,8 +68,7 @@ class User {
return id == other.id && return id == other.id &&
updatedAt.isAtSameMomentAs(other.updatedAt) && updatedAt.isAtSameMomentAs(other.updatedAt) &&
email == other.email && email == other.email &&
firstName == other.firstName && name == other.name &&
lastName == other.lastName &&
isPartnerSharedBy == other.isPartnerSharedBy && isPartnerSharedBy == other.isPartnerSharedBy &&
isPartnerSharedWith == other.isPartnerSharedWith && isPartnerSharedWith == other.isPartnerSharedWith &&
profileImagePath == other.profileImagePath && profileImagePath == other.profileImagePath &&
@ -88,8 +83,7 @@ class User {
id.hashCode ^ id.hashCode ^
updatedAt.hashCode ^ updatedAt.hashCode ^
email.hashCode ^ email.hashCode ^
firstName.hashCode ^ name.hashCode ^
lastName.hashCode ^
isPartnerSharedBy.hashCode ^ isPartnerSharedBy.hashCode ^
isPartnerSharedWith.hashCode ^ isPartnerSharedWith.hashCode ^
profileImagePath.hashCode ^ profileImagePath.hashCode ^

Binary file not shown.

View file

@ -132,7 +132,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
), ),
), ),
title: Text( title: Text(
"${authState.firstName} ${authState.lastName}", authState.name,
style: TextStyle( style: TextStyle(
color: context.primaryColor, color: context.primaryColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View file

@ -7,8 +7,7 @@ import 'package:immich_mobile/shared/models/user.dart';
Widget userAvatar(BuildContext context, User u, {double? radius}) { Widget userAvatar(BuildContext context, User u, {double? radius}) {
final url = final url =
"${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${u.id}"; "${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${u.id}";
final firstNameFirstLetter = u.firstName.isNotEmpty ? u.firstName[0] : ""; final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
final lastNameFirstLetter = u.lastName.isNotEmpty ? u.lastName[0] : "";
return CircleAvatar( return CircleAvatar(
radius: radius, radius: radius,
backgroundColor: context.primaryColor.withAlpha(50), backgroundColor: context.primaryColor.withAlpha(50),
@ -19,6 +18,6 @@ Widget userAvatar(BuildContext context, User u, {double? radius}) {
), ),
// silence errors if user has no profile image, use initials as fallback // silence errors if user has no profile image, use initials as fallback
onForegroundImageError: (exception, stackTrace) {}, onForegroundImageError: (exception, stackTrace) {},
child: Text((firstNameFirstLetter + lastNameFirstLetter).toUpperCase()), child: Text(nameFirstLetter.toUpperCase()),
); );
} }

View file

@ -43,7 +43,7 @@ class UserCircleAvatar extends ConsumerWidget {
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}'; '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}';
final textIcon = Text( final textIcon = Text(
user.firstName[0].toUpperCase(), user.name[0].toUpperCase(),
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: context.isDarkTheme ? Colors.black : Colors.white, color: context.isDarkTheme ? Colors.black : Colors.white,

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -62,8 +62,7 @@ void main() {
id: "1", id: "1",
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
email: "a@b.c", email: "a@b.c",
firstName: "first", name: "first last",
lastName: "last",
isAdmin: false, isAdmin: false,
); );
setUpAll(() async { setUpAll(() async {

View file

@ -6844,15 +6844,12 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"memoriesEnabled": { "memoriesEnabled": {
"type": "boolean" "type": "boolean"
}, },
"name": {
"type": "string"
},
"password": { "password": {
"type": "string" "type": "string"
}, },
@ -6864,8 +6861,7 @@
"required": [ "required": [
"email", "email",
"password", "password",
"firstName", "name"
"lastName"
], ],
"type": "object" "type": "object"
}, },
@ -7463,13 +7459,10 @@
"accessToken": { "accessToken": {
"type": "string" "type": "string"
}, },
"firstName": {
"type": "string"
},
"isAdmin": { "isAdmin": {
"type": "boolean" "type": "boolean"
}, },
"lastName": { "name": {
"type": "string" "type": "string"
}, },
"profileImagePath": { "profileImagePath": {
@ -7489,8 +7482,7 @@
"accessToken", "accessToken",
"userId", "userId",
"userEmail", "userEmail",
"firstName", "name",
"lastName",
"profileImagePath", "profileImagePath",
"isAdmin", "isAdmin",
"shouldChangePassword" "shouldChangePassword"
@ -7656,9 +7648,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"firstName": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -7668,12 +7657,12 @@
"isAdmin": { "isAdmin": {
"type": "boolean" "type": "boolean"
}, },
"lastName": {
"type": "string"
},
"memoriesEnabled": { "memoriesEnabled": {
"type": "boolean" "type": "boolean"
}, },
"name": {
"type": "string"
},
"oauthId": { "oauthId": {
"type": "string" "type": "string"
}, },
@ -7694,8 +7683,7 @@
}, },
"required": [ "required": [
"id", "id",
"firstName", "name",
"lastName",
"email", "email",
"profileImagePath", "profileImagePath",
"storageLabel", "storageLabel",
@ -8464,14 +8452,10 @@
"example": "testuser@email.com", "example": "testuser@email.com",
"type": "string" "type": "string"
}, },
"firstName": { "name": {
"example": "Admin", "example": "Admin",
"type": "string" "type": "string"
}, },
"lastName": {
"example": "Doe",
"type": "string"
},
"password": { "password": {
"example": "password", "example": "password",
"type": "string" "type": "string"
@ -8480,8 +8464,7 @@
"required": [ "required": [
"email", "email",
"password", "password",
"firstName", "name"
"lastName"
], ],
"type": "object" "type": "object"
}, },
@ -9163,9 +9146,6 @@
"externalPath": { "externalPath": {
"type": "string" "type": "string"
}, },
"firstName": {
"type": "string"
},
"id": { "id": {
"format": "uuid", "format": "uuid",
"type": "string" "type": "string"
@ -9173,12 +9153,12 @@
"isAdmin": { "isAdmin": {
"type": "boolean" "type": "boolean"
}, },
"lastName": {
"type": "string"
},
"memoriesEnabled": { "memoriesEnabled": {
"type": "boolean" "type": "boolean"
}, },
"name": {
"type": "string"
},
"password": { "password": {
"type": "string" "type": "string"
}, },
@ -9203,13 +9183,10 @@
"format": "int64", "format": "int64",
"type": "integer" "type": "integer"
}, },
"userFirstName": {
"type": "string"
},
"userId": { "userId": {
"type": "string" "type": "string"
}, },
"userLastName": { "userName": {
"type": "string" "type": "string"
}, },
"videos": { "videos": {
@ -9218,8 +9195,7 @@
}, },
"required": [ "required": [
"userId", "userId",
"userFirstName", "userName",
"userLastName",
"photos", "photos",
"videos", "videos",
"usage" "usage"
@ -9231,13 +9207,10 @@
"email": { "email": {
"type": "string" "type": "string"
}, },
"firstName": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
"lastName": { "name": {
"type": "string" "type": "string"
}, },
"profileImagePath": { "profileImagePath": {
@ -9246,8 +9219,7 @@
}, },
"required": [ "required": [
"id", "id",
"firstName", "name",
"lastName",
"email", "email",
"profileImagePath" "profileImagePath"
], ],
@ -9271,21 +9243,18 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"firstName": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
"isAdmin": { "isAdmin": {
"type": "boolean" "type": "boolean"
}, },
"lastName": {
"type": "string"
},
"memoriesEnabled": { "memoriesEnabled": {
"type": "boolean" "type": "boolean"
}, },
"name": {
"type": "string"
},
"oauthId": { "oauthId": {
"type": "string" "type": "string"
}, },
@ -9306,8 +9275,7 @@
}, },
"required": [ "required": [
"id", "id",
"firstName", "name",
"lastName",
"email", "email",
"profileImagePath", "profileImagePath",
"storageLabel", "storageLabel",

View file

@ -16845,11 +16845,6 @@
"luxon": "^3.2.1" "luxon": "^3.2.1"
} }
}, },
"cron-validator": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz",
"integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A=="
},
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,10 +16,7 @@ export class UserEntity {
id!: string; id!: string;
@Column({ default: '' }) @Column({ default: '' })
firstName!: string; name!: string;
@Column({ default: '' })
lastName!: string;
@Column({ default: false }) @Column({ default: false })
isAdmin!: boolean; 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 const stats = await this.userRepository
.createQueryBuilder('users') .createQueryBuilder('users')
.select('users.id', 'userId') .select('users.id', 'userId')
.addSelect('users.firstName', 'userFirstName') .addSelect('users.name', 'userName')
.addSelect('users.lastName', 'userLastName')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos') .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(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
.addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage') .addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage')

View file

@ -350,8 +350,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
const { id: userId } = await api.userApi.create(server, admin.accessToken, { const { id: userId } = await api.userApi.create(server, admin.accessToken, {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}); });
await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] });
const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
@ -371,8 +370,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
const { id: userId } = await api.userApi.create(server, admin.accessToken, { const { id: userId } = await api.userApi.create(server, admin.accessToken, {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}); });
await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] });
const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });
@ -393,8 +391,7 @@ describe(`${ActivityController.name} (e2e)`, () => {
const { id: userId } = await api.userApi.create(server, admin.accessToken, { const { id: userId } = await api.userApi.create(server, admin.accessToken, {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}); });
await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] }); await api.albumApi.addUsers(server, admin.accessToken, album.id, { sharedUserIds: [userId] });
const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' }); const nonOwner = await api.authApi.login(server, { email: 'user1@immich.app', password: 'Password123' });

View file

@ -41,14 +41,12 @@ describe(`${AlbumController.name} (e2e)`, () => {
api.userApi.create(server, admin.accessToken, { api.userApi.create(server, admin.accessToken, {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}), }),
api.userApi.create(server, admin.accessToken, { api.userApi.create(server, admin.accessToken, {
email: 'user2@immich.app', email: 'user2@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 2', name: 'User 2',
lastName: 'Test',
}), }),
]); ]);

View file

@ -22,15 +22,13 @@ import request from 'supertest';
const user1Dto = { const user1Dto = {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}; };
const user2Dto = { const user2Dto = {
email: 'user2@immich.app', email: 'user2@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 2', name: 'User 2',
lastName: 'Test',
}; };
const makeUploadDto = (options?: { omit: string }): Record<string, any> => { const makeUploadDto = (options?: { omit: string }): Record<string, any> => {

View file

@ -13,15 +13,13 @@ import {
import { testApp } from '@test/test-utils'; import { testApp } from '@test/test-utils';
import request from 'supertest'; import request from 'supertest';
const firstName = 'Immich'; const name = 'Immich Admin';
const lastName = 'Admin';
const password = 'Password123'; const password = 'Password123';
const email = 'admin@immich.app'; const email = 'admin@immich.app';
const adminSignupResponse = { const adminSignupResponse = {
id: expect.any(String), id: expect.any(String),
firstName: 'Immich', name: 'Immich Admin',
lastName: 'Admin',
email: 'admin@immich.app', email: 'admin@immich.app',
storageLabel: 'admin', storageLabel: 'admin',
externalPath: null, externalPath: null,
@ -64,23 +62,19 @@ describe(`${AuthController.name} (e2e)`, () => {
const invalid = [ const invalid = [
{ {
should: 'require an email address', should: 'require an email address',
data: { firstName, lastName, password }, data: { name, password },
}, },
{ {
should: 'require a password', should: 'require a password',
data: { firstName, lastName, email }, data: { name, email },
}, },
{ {
should: 'require a first name ', should: 'require a name',
data: { lastName, email, password }, data: { email, password },
},
{
should: 'require a last name ',
data: { firstName, email, password },
}, },
{ {
should: 'require a valid email', should: 'require a valid email',
data: { firstName, lastName, email: 'immich', password }, data: { name, email: 'immich', password },
}, },
]; ];

View file

@ -15,15 +15,13 @@ describe(`${LibraryController.name} (e2e)`, () => {
const user1Dto = { const user1Dto = {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}; };
const user2Dto = { const user2Dto = {
email: 'user2@immich.app', email: 'user2@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 2', name: 'User 2',
lastName: 'Test',
}; };
beforeAll(async () => { beforeAll(async () => {

View file

@ -10,15 +10,13 @@ import request from 'supertest';
const user1Dto = { const user1Dto = {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}; };
const user2Dto = { const user2Dto = {
email: 'user2@immich.app', email: 'user2@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 2', name: 'User 2',
lastName: 'Test',
}; };
describe(`${PartnerController.name} (e2e)`, () => { describe(`${PartnerController.name} (e2e)`, () => {

View file

@ -111,7 +111,7 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
it('should only work for admins', async () => { it('should only work for admins', async () => {
const loginDto = { email: 'test@immich.app', password: 'Immich123' }; const loginDto = { email: 'test@immich.app', password: 'Immich123' };
await api.userApi.create(server, accessToken, { ...loginDto, firstName: 'test', lastName: 'test' }); await api.userApi.create(server, accessToken, { ...loginDto, name: 'test' });
const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto); const { accessToken: userAccessToken } = await api.authApi.login(server, loginDto);
const { status, body } = await request(server) const { status, body } = await request(server)
.get('/server-info/statistics') .get('/server-info/statistics')
@ -132,9 +132,8 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
{ {
photos: 0, photos: 0,
usage: 0, usage: 0,
userFirstName: 'Immich', userName: 'Immich Admin',
userId: loginResponse.userId, userId: loginResponse.userId,
userLastName: 'Admin',
videos: 0, videos: 0,
}, },
], ],

View file

@ -11,8 +11,7 @@ import request from 'supertest';
const user1Dto = { const user1Dto = {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}; };
describe(`${PartnerController.name} (e2e)`, () => { describe(`${PartnerController.name} (e2e)`, () => {

View file

@ -64,8 +64,7 @@ describe(`${SystemConfigController.name} (e2e)`, () => {
const credentials = { email: 'user1@immich.app', password: 'Password123' }; const credentials = { email: 'user1@immich.app', password: 'Password123' };
await api.userApi.create(server, admin.accessToken, { await api.userApi.create(server, admin.accessToken, {
...credentials, ...credentials,
firstName: 'User 1', name: 'User 1',
lastName: 'Test',
}); });
const { accessToken } = await api.authApi.login(server, credentials); const { accessToken } = await api.authApi.login(server, credentials);
const { status, body } = await request(server) const { status, body } = await request(server)

View file

@ -59,8 +59,7 @@ describe(`${UserController.name}`, () => {
const user1 = await api.userApi.create(server, accessToken, { const user1 = await api.userApi.create(server, accessToken, {
email: `user1@immich.app`, email: `user1@immich.app`,
password: 'Password123', password: 'Password123',
firstName: `User 1`, name: `User 1`,
lastName: 'Test',
}); });
await api.userApi.delete(server, accessToken, user1.id); await api.userApi.delete(server, accessToken, user1.id);
@ -78,8 +77,7 @@ describe(`${UserController.name}`, () => {
const user1 = await api.userApi.create(server, accessToken, { const user1 = await api.userApi.create(server, accessToken, {
email: `user1@immich.app`, email: `user1@immich.app`,
password: 'Password123', password: 'Password123',
firstName: `User 1`, name: `User 1`,
lastName: 'Test',
}); });
await api.userApi.delete(server, accessToken, user1.id); await api.userApi.delete(server, accessToken, user1.id);
@ -149,8 +147,7 @@ describe(`${UserController.name}`, () => {
isAdmin: true, isAdmin: true,
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'Immich', name: 'Immich',
lastName: 'User',
}) })
.set('Authorization', `Bearer ${accessToken}`); .set('Authorization', `Bearer ${accessToken}`);
expect(body).toMatchObject({ expect(body).toMatchObject({
@ -167,8 +164,7 @@ describe(`${UserController.name}`, () => {
.send({ .send({
email: 'no-memories@immich.app', email: 'no-memories@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'No Memories', name: 'No Memories',
lastName: 'User',
memoriesEnabled: false, memoriesEnabled: false,
}) })
.set('Authorization', `Bearer ${accessToken}`); .set('Authorization', `Bearer ${accessToken}`);
@ -186,8 +182,7 @@ describe(`${UserController.name}`, () => {
beforeEach(async () => { beforeEach(async () => {
userToDelete = await api.userApi.create(server, accessToken, { userToDelete = await api.userApi.create(server, accessToken, {
email: userStub.user1.email, email: userStub.user1.email,
firstName: userStub.user1.firstName, name: userStub.user1.name,
lastName: userStub.user1.lastName,
password: 'superSecurePassword', password: 'superSecurePassword',
}); });
}); });
@ -246,8 +241,7 @@ describe(`${UserController.name}`, () => {
const user = await api.userApi.create(server, accessToken, { const user = await api.userApi.create(server, accessToken, {
email: 'user1@immich.app', email: 'user1@immich.app',
password: 'Password123', password: 'Password123',
firstName: 'Immich', name: 'Immich User',
lastName: 'User',
}); });
const { status, body } = await request(server) const { status, body } = await request(server)
@ -284,15 +278,13 @@ describe(`${UserController.name}`, () => {
const before = await api.userApi.get(server, accessToken, loginResponse.userId); const before = await api.userApi.get(server, accessToken, loginResponse.userId);
const after = await api.userApi.update(server, accessToken, { const after = await api.userApi.update(server, accessToken, {
id: before.id, id: before.id,
firstName: 'First Name', name: 'Name',
lastName: 'Last Name',
}); });
expect(after).toEqual({ expect(after).toEqual({
...before, ...before,
updatedAt: expect.any(String), updatedAt: expect.any(String),
firstName: 'First Name', name: 'Name',
lastName: 'Last Name',
}); });
expect(before.updatedAt).not.toEqual(after.updatedAt); expect(before.updatedAt).not.toEqual(after.updatedAt);
}); });

View file

@ -1,8 +1,7 @@
import { AuthUserDto } from '@app/domain'; import { AuthUserDto } from '@app/domain';
export const adminSignupStub = { export const adminSignupStub = {
firstName: 'Immich', name: 'Immich Admin',
lastName: 'Admin',
email: 'admin@immich.app', email: 'admin@immich.app',
password: 'Password123', password: 'Password123',
}; };
@ -103,9 +102,8 @@ export const loginResponseStub = {
admin: { admin: {
response: { response: {
accessToken: expect.any(String), accessToken: expect.any(String),
firstName: 'Immich', name: 'Immich Admin',
isAdmin: true, isAdmin: true,
lastName: 'Admin',
profileImagePath: '', profileImagePath: '',
shouldChangePassword: true, shouldChangePassword: true,
userEmail: 'admin@immich.app', userEmail: 'admin@immich.app',
@ -117,8 +115,7 @@ export const loginResponseStub = {
accessToken: 'cmFuZG9tLWJ5dGVz', accessToken: 'cmFuZG9tLWJ5dGVz',
userId: 'user-id', userId: 'user-id',
userEmail: 'immich@test.com', userEmail: 'immich@test.com',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
profileImagePath: '', profileImagePath: '',
isAdmin: false, isAdmin: false,
shouldChangePassword: false, shouldChangePassword: false,
@ -133,8 +130,7 @@ export const loginResponseStub = {
accessToken: 'cmFuZG9tLWJ5dGVz', accessToken: 'cmFuZG9tLWJ5dGVz',
userId: 'user-id', userId: 'user-id',
userEmail: 'immich@test.com', userEmail: 'immich@test.com',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
profileImagePath: '', profileImagePath: '',
isAdmin: false, isAdmin: false,
shouldChangePassword: false, shouldChangePassword: false,
@ -149,8 +145,7 @@ export const loginResponseStub = {
accessToken: 'cmFuZG9tLWJ5dGVz', accessToken: 'cmFuZG9tLWJ5dGVz',
userId: 'user-id', userId: 'user-id',
userEmail: 'immich@test.com', userEmail: 'immich@test.com',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
profileImagePath: '', profileImagePath: '',
isAdmin: false, isAdmin: false,
shouldChangePassword: false, shouldChangePassword: false,

View file

@ -5,8 +5,7 @@ export const userStub = {
admin: Object.freeze<UserEntity>({ admin: Object.freeze<UserEntity>({
...authStub.admin, ...authStub.admin,
password: 'admin_password', password: 'admin_password',
firstName: 'admin_first_name', name: 'admin_name',
lastName: 'admin_last_name',
storageLabel: 'admin', storageLabel: 'admin',
externalPath: null, externalPath: null,
oauthId: '', oauthId: '',
@ -22,8 +21,7 @@ export const userStub = {
user1: Object.freeze<UserEntity>({ user1: Object.freeze<UserEntity>({
...authStub.user1, ...authStub.user1,
password: 'immich_password', password: 'immich_password',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
storageLabel: null, storageLabel: null,
externalPath: null, externalPath: null,
oauthId: '', oauthId: '',
@ -39,8 +37,7 @@ export const userStub = {
user2: Object.freeze<UserEntity>({ user2: Object.freeze<UserEntity>({
...authStub.user2, ...authStub.user2,
password: 'immich_password', password: 'immich_password',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
storageLabel: null, storageLabel: null,
externalPath: null, externalPath: null,
oauthId: '', oauthId: '',
@ -56,8 +53,7 @@ export const userStub = {
storageLabel: Object.freeze<UserEntity>({ storageLabel: Object.freeze<UserEntity>({
...authStub.user1, ...authStub.user1,
password: 'immich_password', password: 'immich_password',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
storageLabel: 'label-1', storageLabel: 'label-1',
externalPath: null, externalPath: null,
oauthId: '', oauthId: '',
@ -73,8 +69,7 @@ export const userStub = {
externalPath1: Object.freeze<UserEntity>({ externalPath1: Object.freeze<UserEntity>({
...authStub.user1, ...authStub.user1,
password: 'immich_password', password: 'immich_password',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
storageLabel: 'label-1', storageLabel: 'label-1',
externalPath: '/data/user1', externalPath: '/data/user1',
oauthId: '', oauthId: '',
@ -90,8 +85,7 @@ export const userStub = {
externalPath2: Object.freeze<UserEntity>({ externalPath2: Object.freeze<UserEntity>({
...authStub.user1, ...authStub.user1,
password: 'immich_password', password: 'immich_password',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
storageLabel: 'label-1', storageLabel: 'label-1',
externalPath: '/data/user2', externalPath: '/data/user2',
oauthId: '', oauthId: '',
@ -107,8 +101,7 @@ export const userStub = {
profilePath: Object.freeze<UserEntity>({ profilePath: Object.freeze<UserEntity>({
...authStub.user1, ...authStub.user1,
password: 'immich_password', password: 'immich_password',
firstName: 'immich_first_name', name: 'immich_name',
lastName: 'immich_last_name',
storageLabel: 'label-1', storageLabel: 'label-1',
externalPath: null, externalPath: null,
oauthId: '', oauthId: '',

View file

@ -1341,24 +1341,18 @@ export interface CreateUserDto {
* @memberof CreateUserDto * @memberof CreateUserDto
*/ */
'externalPath'?: string | null; 'externalPath'?: string | null;
/**
*
* @type {string}
* @memberof CreateUserDto
*/
'firstName': string;
/**
*
* @type {string}
* @memberof CreateUserDto
*/
'lastName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof CreateUserDto * @memberof CreateUserDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof CreateUserDto
*/
'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -2137,12 +2131,6 @@ export interface LoginResponseDto {
* @memberof LoginResponseDto * @memberof LoginResponseDto
*/ */
'accessToken': string; 'accessToken': string;
/**
*
* @type {string}
* @memberof LoginResponseDto
*/
'firstName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -2154,7 +2142,7 @@ export interface LoginResponseDto {
* @type {string} * @type {string}
* @memberof LoginResponseDto * @memberof LoginResponseDto
*/ */
'lastName': string; 'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -2391,12 +2379,6 @@ export interface PartnerResponseDto {
* @memberof PartnerResponseDto * @memberof PartnerResponseDto
*/ */
'externalPath': string | null; 'externalPath': string | null;
/**
*
* @type {string}
* @memberof PartnerResponseDto
*/
'firstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -2415,18 +2397,18 @@ export interface PartnerResponseDto {
* @memberof PartnerResponseDto * @memberof PartnerResponseDto
*/ */
'isAdmin': boolean; 'isAdmin': boolean;
/**
*
* @type {string}
* @memberof PartnerResponseDto
*/
'lastName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof PartnerResponseDto * @memberof PartnerResponseDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof PartnerResponseDto
*/
'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -3431,13 +3413,7 @@ export interface SignUpDto {
* @type {string} * @type {string}
* @memberof SignUpDto * @memberof SignUpDto
*/ */
'firstName': string; 'name': string;
/**
*
* @type {string}
* @memberof SignUpDto
*/
'lastName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4380,12 +4356,6 @@ export interface UpdateUserDto {
* @memberof UpdateUserDto * @memberof UpdateUserDto
*/ */
'externalPath'?: string; 'externalPath'?: string;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'firstName'?: string;
/** /**
* *
* @type {string} * @type {string}
@ -4398,18 +4368,18 @@ export interface UpdateUserDto {
* @memberof UpdateUserDto * @memberof UpdateUserDto
*/ */
'isAdmin'?: boolean; 'isAdmin'?: boolean;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'lastName'?: string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof UpdateUserDto * @memberof UpdateUserDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof UpdateUserDto
*/
'name'?: string;
/** /**
* *
* @type {string} * @type {string}
@ -4447,12 +4417,6 @@ export interface UsageByUserDto {
* @memberof UsageByUserDto * @memberof UsageByUserDto
*/ */
'usage': number; 'usage': number;
/**
*
* @type {string}
* @memberof UsageByUserDto
*/
'userFirstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4464,7 +4428,7 @@ export interface UsageByUserDto {
* @type {string} * @type {string}
* @memberof UsageByUserDto * @memberof UsageByUserDto
*/ */
'userLastName': string; 'userName': string;
/** /**
* *
* @type {number} * @type {number}
@ -4484,12 +4448,6 @@ export interface UserDto {
* @memberof UserDto * @memberof UserDto
*/ */
'email': string; 'email': string;
/**
*
* @type {string}
* @memberof UserDto
*/
'firstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4501,7 +4459,7 @@ export interface UserDto {
* @type {string} * @type {string}
* @memberof UserDto * @memberof UserDto
*/ */
'lastName': string; 'name': string;
/** /**
* *
* @type {string} * @type {string}
@ -4539,12 +4497,6 @@ export interface UserResponseDto {
* @memberof UserResponseDto * @memberof UserResponseDto
*/ */
'externalPath': string | null; 'externalPath': string | null;
/**
*
* @type {string}
* @memberof UserResponseDto
*/
'firstName': string;
/** /**
* *
* @type {string} * @type {string}
@ -4557,18 +4509,18 @@ export interface UserResponseDto {
* @memberof UserResponseDto * @memberof UserResponseDto
*/ */
'isAdmin': boolean; 'isAdmin': boolean;
/**
*
* @type {string}
* @memberof UserResponseDto
*/
'lastName': string;
/** /**
* *
* @type {boolean} * @type {boolean}
* @memberof UserResponseDto * @memberof UserResponseDto
*/ */
'memoriesEnabled'?: boolean; 'memoriesEnabled'?: boolean;
/**
*
* @type {string}
* @memberof UserResponseDto
*/
'name': string;
/** /**
* *
* @type {string} * @type {string}

View file

@ -27,7 +27,7 @@
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<p> <p>
<b>{user.firstName} {user.lastName}</b>'s account and assets will be permanently deleted after 7 days. <b>{user.name}</b>'s account and assets will be permanently deleted after 7 days.
</p> </p>
<p>Are you sure you want to continue?</p> <p>Are you sure you want to continue?</p>
</div> </div>

View file

@ -16,6 +16,6 @@
<ConfirmDialogue title="Restore User" confirmText="Continue" confirmColor="green" on:confirm={restoreUser} on:cancel> <ConfirmDialogue title="Restore User" confirmText="Continue" confirmColor="green" on:confirm={restoreUser} on:cancel>
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
<p><b>{user.firstName} {user.lastName}</b>'s account will be restored.</p> <p><b>{user.name}</b>'s account will be restored.</p>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialogue>

View file

@ -96,7 +96,7 @@
<tr <tr
class="flex h-[50px] w-full place-items-center text-center odd:bg-immich-gray even:bg-immich-bg odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50" class="flex h-[50px] w-full place-items-center text-center odd:bg-immich-gray even:bg-immich-bg odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50"
> >
<td class="w-1/4 text-ellipsis px-2 text-sm">{user.userFirstName} {user.userLastName}</td> <td class="w-1/4 text-ellipsis px-2 text-sm">{user.userName}</td>
<td class="w-1/4 text-ellipsis px-2 text-sm">{user.photos.toLocaleString($locale)}</td> <td class="w-1/4 text-ellipsis px-2 text-sm">{user.photos.toLocaleString($locale)}</td>
<td class="w-1/4 text-ellipsis px-2 text-sm">{user.videos.toLocaleString($locale)}</td> <td class="w-1/4 text-ellipsis px-2 text-sm">{user.videos.toLocaleString($locale)}</td>
<td class="w-1/4 text-ellipsis px-2 text-sm">{asByteUnitString(user.usage, $locale)}</td> <td class="w-1/4 text-ellipsis px-2 text-sm">{asByteUnitString(user.usage, $locale)}</td>

View file

@ -123,8 +123,7 @@
<p>Owned</p> <p>Owned</p>
{:else} {:else}
<p> <p>
Shared by {albumOwner.firstName} Shared by {albumOwner.name}
{albumOwner.lastName}
</p> </p>
{/if} {/if}
{/await} {/await}

View file

@ -56,7 +56,7 @@
<div> <div>
<UserAvatar {user} size="md" /> <UserAvatar {user} size="md" />
</div> </div>
<div class="w-full">{`${user.firstName} ${user.lastName}`}</div> <div class="w-full">{user.name}</div>
<div>Owner</div> <div>Owner</div>
</div> </div>
{#each album.sharedUsers as user (user.id)} {#each album.sharedUsers as user (user.id)}
@ -64,7 +64,7 @@
<div> <div>
<UserAvatar {user} size="md" /> <UserAvatar {user} size="md" />
</div> </div>
<div class="w-full">{`${user.firstName} ${user.lastName}`}</div> <div class="w-full">{user.name}</div>
</div> </div>
{/each} {/each}
</div> </div>

View file

@ -56,7 +56,7 @@
try { try {
await api.albumApi.removeUserFromAlbum({ id: album.id, userId }); await api.albumApi.removeUserFromAlbum({ id: album.id, userId });
dispatch('remove', userId); dispatch('remove', userId);
const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.firstName}`; const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.name}`;
notificationController.show({ type: NotificationType.Info, message }); notificationController.show({ type: NotificationType.Info, message });
} catch (e) { } catch (e) {
handleError(e, 'Unable to remove user'); handleError(e, 'Unable to remove user');
@ -78,7 +78,7 @@
<div class="flex w-full place-items-center justify-between gap-4 p-5"> <div class="flex w-full place-items-center justify-between gap-4 p-5">
<div class="flex place-items-center gap-4"> <div class="flex place-items-center gap-4">
<UserAvatar user={album.owner} size="md" autoColor /> <UserAvatar user={album.owner} size="md" autoColor />
<p class="text-sm font-medium">{album.owner.firstName} {album.owner.lastName}</p> <p class="text-sm font-medium">{album.owner.name}</p>
</div> </div>
<div id="icon-{album.owner.id}" class="flex place-items-center"> <div id="icon-{album.owner.id}" class="flex place-items-center">
@ -91,7 +91,7 @@
> >
<div class="flex place-items-center gap-4"> <div class="flex place-items-center gap-4">
<UserAvatar {user} size="md" autoColor /> <UserAvatar {user} size="md" autoColor />
<p class="text-sm font-medium">{user.firstName} {user.lastName}</p> <p class="text-sm font-medium">{user.name}</p>
</div> </div>
<div id="icon-{user.id}" class="flex place-items-center"> <div id="icon-{user.id}" class="flex place-items-center">
@ -138,7 +138,7 @@
{#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id} {#if selectedRemoveUser && selectedRemoveUser?.id !== currentUser?.id}
<ConfirmDialogue <ConfirmDialogue
title="Remove User?" title="Remove User?"
prompt="Are you sure you want to remove {selectedRemoveUser.firstName} {selectedRemoveUser.lastName}" prompt="Are you sure you want to remove {selectedRemoveUser.name}"
confirmText="Remove" confirmText="Remove"
on:confirm={handleRemoveUser} on:confirm={handleRemoveUser}
on:cancel={() => (selectedRemoveUser = null)} on:cancel={() => (selectedRemoveUser = null)}

View file

@ -72,7 +72,7 @@
class="flex place-items-center gap-1 rounded-full border border-gray-400 p-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" class="flex place-items-center gap-1 rounded-full border border-gray-400 p-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<UserAvatar {user} size="sm" autoColor /> <UserAvatar {user} size="sm" autoColor />
<p class="text-xs font-medium">{user.firstName} {user.lastName}</p> <p class="text-xs font-medium">{user.name}</p>
</button> </button>
{/key} {/key}
{/each} {/each}
@ -99,8 +99,7 @@
<div class="text-left"> <div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg"> <p class="text-immich-fg dark:text-immich-dark-fg">
{user.firstName} {user.name}
{user.lastName}
</p> </p>
<p class="text-xs"> <p class="text-xs">
{user.email} {user.email}

View file

@ -221,13 +221,8 @@
<div class="flex p-3 mx-2 mt-3 rounded-full gap-4 items-center text-sm"> <div class="flex p-3 mx-2 mt-3 rounded-full gap-4 items-center text-sm">
<div class="text-red-600"><Icon path={mdiHeart} size={20} /></div> <div class="text-red-600"><Icon path={mdiHeart} size={20} /></div>
<div <div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
class="w-full" {`${reaction.user.name} liked ${assetType ? `this ${getAssetType(assetType).toLowerCase()}` : 'it'}`}
title={`${reaction.user.firstName} ${reaction.user.lastName} (${reaction.user.email})`}
>
{`${reaction.user.firstName} ${reaction.user.lastName} liked ${
assetType ? `this ${getAssetType(assetType).toLowerCase()}` : 'it'
}`}
</div> </div>
{#if assetId === undefined && reaction.assetId} {#if assetId === undefined && reaction.assetId}
<div class="aspect-square w-[75px] h-[75px]"> <div class="aspect-square w-[75px] h-[75px]">

View file

@ -315,8 +315,7 @@
<div class="mb-auto mt-auto"> <div class="mb-auto mt-auto">
<p> <p>
{asset.owner.firstName} {asset.owner.name}
{asset.owner.lastName}
</p> </p>
</div> </div>
</div> </div>

View file

@ -27,15 +27,13 @@
const email = form.get('email'); const email = form.get('email');
const password = form.get('password'); const password = form.get('password');
const firstName = form.get('firstName'); const name = form.get('name');
const lastName = form.get('lastName');
const { status } = await api.authenticationApi.signUpAdmin({ const { status } = await api.authenticationApi.signUpAdmin({
signUpDto: { signUpDto: {
email: String(email), email: String(email),
password: String(password), password: String(password),
firstName: String(firstName), name: String(name),
lastName: String(lastName),
}, },
}); });
@ -83,13 +81,8 @@
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label class="immich-form-label" for="firstName">First Name</label> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="firstName" name="firstName" type="text" autocomplete="given-name" required /> <input class="immich-form-input" id="name" name="name" type="text" autocomplete="name" required />
</div>
<div class="flex flex-col gap-2">
<label class="immich-form-label" for="lastName">Last Name</label>
<input class="immich-form-input" id="lastName" name="lastName" type="text" autocomplete="family-name" required />
</div> </div>
{#if error} {#if error}

View file

@ -38,16 +38,14 @@
const email = form.get('email'); const email = form.get('email');
const password = form.get('password'); const password = form.get('password');
const firstName = form.get('firstName'); const name = form.get('name');
const lastName = form.get('lastName');
try { try {
const { status } = await api.userApi.createUser({ const { status } = await api.userApi.createUser({
createUserDto: { createUserDto: {
email: String(email), email: String(email),
password: String(password), password: String(password),
firstName: String(firstName), name: String(name),
lastName: String(lastName),
}, },
}); });
@ -112,13 +110,8 @@
</div> </div>
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="firstName">First Name</label> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="firstName" name="firstName" type="text" required /> <input class="immich-form-input" id="name" name="name" type="text" required />
</div>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="lastName">Last Name</label>
<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
</div> </div>
{#if error} {#if error}

View file

@ -20,13 +20,12 @@
const editUser = async () => { const editUser = async () => {
try { try {
const { id, email, firstName, lastName, storageLabel, externalPath } = user; const { id, email, name, storageLabel, externalPath } = user;
const { status } = await api.userApi.updateUser({ const { status } = await api.userApi.updateUser({
updateUserDto: { updateUserDto: {
id, id,
email, email,
firstName, name,
lastName,
storageLabel: storageLabel || '', storageLabel: storageLabel || '',
externalPath: externalPath || '', externalPath: externalPath || '',
}, },
@ -84,20 +83,8 @@
</div> </div>
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="firstName">First Name</label> <label class="immich-form-label" for="name">Name</label>
<input <input class="immich-form-input" id="name" name="name" type="text" required bind:value={user.name} />
class="immich-form-input"
id="firstName"
name="firstName"
type="text"
required
bind:value={user.firstName}
/>
</div>
<div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="lastName">Last Name</label>
<input class="immich-form-input" id="lastName" name="lastName" type="text" required bind:value={user.lastName} />
</div> </div>
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
@ -161,7 +148,7 @@
> >
<svelte:fragment slot="prompt"> <svelte:fragment slot="prompt">
<p> <p>
Are you sure you want to reset <b>{user.firstName} {user.lastName}</b>'s password? Are you sure you want to reset <b>{user.name}</b>'s password?
</p> </p>
</svelte:fragment> </svelte:fragment>
</ConfirmDialogue> </ConfirmDialogue>

View file

@ -26,8 +26,7 @@
<div> <div>
<p class="text-center text-lg font-medium text-immich-primary dark:text-immich-dark-primary"> <p class="text-center text-lg font-medium text-immich-primary dark:text-immich-dark-primary">
{user.firstName} {user.name}
{user.lastName}
</p> </p>
<p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p> <p class="text-sm text-gray-500 dark:text-immich-dark-fg">{user.email}</p>
</div> </div>

View file

@ -133,7 +133,7 @@
out:fade={{ delay: 200, duration: 150 }} out:fade={{ delay: 200, duration: 150 }}
class="absolute -bottom-12 right-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray" class="absolute -bottom-12 right-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
> >
<p>{user.firstName} {user.lastName}</p> <p>{user.name}</p>
<p>{user.email}</p> <p>{user.email}</p>
</div> </div>
{/if} {/if}

View file

@ -9,8 +9,7 @@
interface User { interface User {
id: string; id: string;
firstName: string; name: string;
lastName: string;
email: string; email: string;
profileImagePath: string; profileImagePath: string;
} }
@ -51,7 +50,7 @@
$: colorClass = colorClasses[autoColor ? getUserColor() : color]; $: colorClass = colorClasses[autoColor ? getUserColor() : color];
$: sizeClass = sizeClasses[size]; $: sizeClass = sizeClasses[size];
$: title = `${user.firstName} ${user.lastName} (${user.email})`; $: title = `${user.name} (${user.email})`;
$: interactiveClass = interactive $: interactiveClass = interactive
? 'border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-colors' ? 'border-2 border-immich-primary hover:border-immich-dark-primary dark:hover:border-immich-primary dark:border-immich-dark-primary transition-colors'
: ''; : '';
@ -82,7 +81,7 @@
class:font-medium={!autoColor} class:font-medium={!autoColor}
class:font-semibold={autoColor} class:font-semibold={autoColor}
> >
{((user.firstName[0] || '') + (user.lastName[0] || '')).toUpperCase()} {(user.name[0] || '').toUpperCase()}
</span> </span>
{/if} {/if}
</figure> </figure>

View file

@ -61,8 +61,7 @@
<div class="text-left"> <div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg"> <p class="text-immich-fg dark:text-immich-dark-fg">
{user.firstName} {user.name}
{user.lastName}
</p> </p>
<p class="text-xs"> <p class="text-xs">
{user.email} {user.email}

View file

@ -116,8 +116,7 @@
<UserAvatar user={partner.user} size="md" autoColor /> <UserAvatar user={partner.user} size="md" autoColor />
<div class="text-left"> <div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg"> <p class="text-immich-fg dark:text-immich-dark-fg">
{partner.user.firstName} {partner.user.name}
{partner.user.lastName}
</p> </p>
<p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75"> <p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
{partner.user.email} {partner.user.email}
@ -139,8 +138,8 @@
<!-- I am sharing my assets with this user --> <!-- I am sharing my assets with this user -->
{#if partner.sharedByMe} {#if partner.sharedByMe}
<hr class="my-4 border border-gray-200 dark:border-gray-700" /> <hr class="my-4 border border-gray-200 dark:border-gray-700" />
<p class="text-xs font-medium my-4">SHARED WITH {partner.user.firstName.toUpperCase()}</p> <p class="text-xs font-medium my-4">SHARED WITH {partner.user.name.toUpperCase()}</p>
<p class="text-md">{partner.user.firstName} can access</p> <p class="text-md">{partner.user.name} can access</p>
<ul class="text-sm"> <ul class="text-sm">
<li class="flex gap-2 place-items-center py-1 mt-2"> <li class="flex gap-2 place-items-center py-1 mt-2">
<Icon path={mdiCheck} /> All your photos and videos except those in Archived and Deleted <Icon path={mdiCheck} /> All your photos and videos except those in Archived and Deleted
@ -154,7 +153,7 @@
<!-- this user is sharing assets with me --> <!-- this user is sharing assets with me -->
{#if partner.sharedWithMe} {#if partner.sharedWithMe}
<hr class="my-4 border border-gray-200 dark:border-gray-700" /> <hr class="my-4 border border-gray-200 dark:border-gray-700" />
<p class="text-xs font-medium my-4">PHOTOS FROM {partner.user.firstName.toUpperCase()}</p> <p class="text-xs font-medium my-4">PHOTOS FROM {partner.user.name.toUpperCase()}</p>
<SettingSwitch <SettingSwitch
title="Show in timeline" title="Show in timeline"
subtitle="Show photos and videos from this user in your timeline" subtitle="Show photos and videos from this user in your timeline"
@ -183,7 +182,7 @@
{#if removePartner} {#if removePartner}
<ConfirmDialogue <ConfirmDialogue
title="Stop sharing your photos?" title="Stop sharing your photos?"
prompt="{removePartner.firstName} will no longer be able to access your photos." prompt="{removePartner.name} will no longer be able to access your photos."
on:cancel={() => (removePartner = null)} on:cancel={() => (removePartner = null)}
on:confirm={() => handleRemovePartner()} on:confirm={() => handleRemovePartner()}
/> />

View file

@ -17,8 +17,7 @@
updateUserDto: { updateUserDto: {
id: user.id, id: user.id,
email: user.email, email: user.email,
firstName: user.firstName, name: user.name,
lastName: user.lastName,
}, },
}); });
@ -47,19 +46,7 @@
<SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={user.email} /> <SettingInputField inputType={SettingInputFieldType.EMAIL} label="EMAIL" bind:value={user.email} />
<SettingInputField <SettingInputField inputType={SettingInputFieldType.TEXT} label="NAME" bind:value={user.name} required={true} />
inputType={SettingInputFieldType.TEXT}
label="FIRST NAME"
bind:value={user.firstName}
required={true}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="LAST NAME"
bind:value={user.lastName}
required={true}
/>
<SettingInputField <SettingInputField
inputType={SettingInputFieldType.TEXT} inputType={SettingInputFieldType.TEXT}

View file

@ -39,8 +39,7 @@
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg"> <p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg">
{data.partner.firstName} {data.partner.name}'s photos
{data.partner.lastName}'s photos
</p> </p>
</svelte:fragment> </svelte:fragment>
</ControlAppBar> </ControlAppBar>

View file

@ -72,8 +72,7 @@
<UserAvatar user={partner} size="lg" autoColor /> <UserAvatar user={partner} size="lg" autoColor />
<div class="text-left"> <div class="text-left">
<p class="text-immich-fg dark:text-immich-dark-fg"> <p class="text-immich-fg dark:text-immich-dark-fg">
{partner.firstName} {partner.name}
{partner.lastName}
</p> </p>
<p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75"> <p class="text-xs text-immich-fg/75 dark:text-immich-dark-fg/75">
{partner.email} {partner.email}

View file

@ -168,8 +168,7 @@
> >
<tr class="flex w-full place-items-center"> <tr class="flex w-full place-items-center">
<th class="w-4/12 text-center text-sm font-medium">Email</th> <th class="w-4/12 text-center text-sm font-medium">Email</th>
<th class="w-2/12 text-center text-sm font-medium">First name</th> <th class="w-2/12 text-center text-sm font-medium">Name</th>
<th class="w-2/12 text-center text-sm font-medium">Last name</th>
<th class="w-2/12 text-center text-sm font-medium">Can import</th> <th class="w-2/12 text-center text-sm font-medium">Can import</th>
<th class="w-2/12 text-center text-sm font-medium">Action</th> <th class="w-2/12 text-center text-sm font-medium">Action</th>
</tr> </tr>
@ -187,8 +186,7 @@
}`} }`}
> >
<td class="w-4/12 text-ellipsis break-all px-2 text-sm">{user.email}</td> <td class="w-4/12 text-ellipsis break-all px-2 text-sm">{user.email}</td>
<td class="w-2/12 text-ellipsis break-all px-2 text-sm">{user.firstName}</td> <td class="w-2/12 text-ellipsis break-all px-2 text-sm">{user.name}</td>
<td class="w-2/12 text-ellipsis break-all px-2 text-sm">{user.lastName}</td>
<td class="w-2/12 text-ellipsis break-all px-2 text-sm"> <td class="w-2/12 text-ellipsis break-all px-2 text-sm">
<div class="container mx-auto flex flex-wrap justify-center"> <div class="container mx-auto flex flex-wrap justify-center">
{#if user.externalPath} {#if user.externalPath}
@ -253,7 +251,7 @@
: 'bg-immich-bg dark:bg-immich-dark-gray/50' : 'bg-immich-bg dark:bg-immich-dark-gray/50'
}`} }`}
> >
<td class="w-1/4 text-ellipsis break-words px-2 text-sm">{user.firstName} {user.lastName}</td> <td class="w-1/4 text-ellipsis break-words px-2 text-sm">{user.name}</td>
<td class="w-1/2 text-ellipsis break-all px-2 text-sm">{user.email}</td> <td class="w-1/2 text-ellipsis break-all px-2 text-sm">{user.email}</td>
<td class="w-1/4 text-ellipsis px-2 text-sm"> <td class="w-1/4 text-ellipsis px-2 text-sm">
{#if !isDeleted(user)} {#if !isDeleted(user)}

View file

@ -16,8 +16,7 @@
<FullscreenContainer title={data.meta.title}> <FullscreenContainer title={data.meta.title}>
<p slot="message"> <p slot="message">
Hi {data.user.firstName} Hi {data.user.name} ({data.user.email}),
{data.user.lastName} ({data.user.email}),
<br /> <br />
<br /> <br />
This is either the first time you are signing into the system or a request has been made to change your password. Please This is either the first time you are signing into the system or a request has been made to change your password. Please

View file

@ -5,8 +5,7 @@ import { Sync } from 'factory.ts';
export const userFactory = Sync.makeFactory<UserResponseDto>({ export const userFactory = Sync.makeFactory<UserResponseDto>({
id: Sync.each(() => faker.datatype.uuid()), id: Sync.each(() => faker.datatype.uuid()),
email: Sync.each(() => faker.internet.email()), email: Sync.each(() => faker.internet.email()),
firstName: Sync.each(() => faker.name.firstName()), name: Sync.each(() => faker.name.fullName()),
lastName: Sync.each(() => faker.name.lastName()),
storageLabel: Sync.each(() => faker.random.alphaNumeric()), storageLabel: Sync.each(() => faker.random.alphaNumeric()),
externalPath: Sync.each(() => faker.random.alphaNumeric()), externalPath: Sync.each(() => faker.random.alphaNumeric()),
profileImagePath: '', profileImagePath: '',