refactor(server): user endpoints (#9730)

* refactor(server): user endpoints

* fix repos

* fix unit tests

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen 2024-05-26 18:15:52 -04:00 committed by GitHub
parent e7c8501930
commit 75830a4878
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 2453 additions and 1914 deletions

View file

@ -1,7 +1,7 @@
<script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { handleError } from '$lib/utils/handle-error';
import { deleteUser, type UserResponseDto } from '@immich/sdk';
import { deleteUserAdmin, type UserResponseDto } from '@immich/sdk';
import { serverConfig } from '$lib/stores/server-config.store';
import { createEventDispatcher } from 'svelte';
import Checkbox from '$lib/components/elements/checkbox.svelte';
@ -20,9 +20,9 @@
const handleDeleteUser = async () => {
try {
const { deletedAt } = await deleteUser({
const { deletedAt } = await deleteUserAdmin({
id: user.id,
deleteUserDto: { force: forceDelete },
userAdminDeleteDto: { force: forceDelete },
});
if (deletedAt == undefined) {

View file

@ -1,7 +1,7 @@
<script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import { handleError } from '$lib/utils/handle-error';
import { restoreUser, type UserResponseDto } from '@immich/sdk';
import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
export let user: UserResponseDto;
@ -14,7 +14,7 @@
const handleRestoreUser = async () => {
try {
const { deletedAt } = await restoreUser({ id: user.id });
const { deletedAt } = await restoreUserAdmin({ id: user.id });
if (deletedAt == undefined) {
dispatch('success');
} else {

View file

@ -1,6 +1,6 @@
<script lang="ts">
import {
getMyUserInfo,
getMyUser,
removeUserFromAlbum,
type AlbumResponseDto,
type UserResponseDto,
@ -36,7 +36,7 @@
onMount(async () => {
try {
currentUser = await getMyUserInfo();
currentUser = await getMyUser();
} catch (error) {
handleError(error, 'Unable to refresh user');
}

View file

@ -7,7 +7,7 @@
import {
AlbumUserRole,
getAllSharedLinks,
getAllUsers,
searchUsers,
type AlbumResponseDto,
type AlbumUserAddDto,
type SharedLinkResponseDto,
@ -36,10 +36,10 @@
let sharedLinks: SharedLinkResponseDto[] = [];
onMount(async () => {
await getSharedLinks();
const data = await getAllUsers({ isAll: false });
const data = await searchUsers();
// remove invalid users
users = data.filter((user) => !(user.deletedAt || user.id === album.ownerId));
// remove album owner
users = data.filter((user) => user.id !== album.ownerId);
// Remove the existed shared users from the album
for (const sharedUser of album.albumUsers) {

View file

@ -2,9 +2,8 @@
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import PasswordField from '../shared-components/password-field.svelte';
import { updateUser, type UserResponseDto } from '@immich/sdk';
import { updateMyUser } from '@immich/sdk';
export let user: UserResponseDto;
let errorMessage: string;
let success: string;
@ -31,13 +30,7 @@
if (valid) {
errorMessage = '';
await updateUser({
updateUserDto: {
id: user.id,
password: String(password),
shouldChangePassword: false,
},
});
await updateMyUser({ userUpdateMeDto: { password: String(password) } });
dispatch('success');
}

View file

@ -2,7 +2,7 @@
import { serverInfo } from '$lib/stores/server-info.store';
import { convertToBytes } from '$lib/utils/byte-converter';
import { handleError } from '$lib/utils/handle-error';
import { createUser } from '@immich/sdk';
import { createUserAdmin } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import PasswordField from '../shared-components/password-field.svelte';
@ -49,8 +49,8 @@
error = '';
try {
await createUser({
createUserDto: {
await createUserAdmin({
userAdminCreateDto: {
email,
password,
shouldChangePassword,

View file

@ -1,16 +1,16 @@
<script lang="ts">
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { AppRoute } from '$lib/constants';
import { serverInfo } from '$lib/stores/server-info.store';
import { convertFromBytes, convertToBytes } from '$lib/utils/byte-converter';
import { handleError } from '$lib/utils/handle-error';
import { updateUser, type UserResponseDto } from '@immich/sdk';
import { updateUserAdmin, type UserAdminResponseDto } from '@immich/sdk';
import { mdiAccountEditOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { mdiAccountEditOutline } from '@mdi/js';
export let user: UserResponseDto;
export let user: UserAdminResponseDto;
export let canResetPassword = true;
export let newPassword: string;
export let onClose: () => void;
@ -36,9 +36,9 @@
const editUser = async () => {
try {
const { id, email, name, storageLabel } = user;
await updateUser({
updateUserDto: {
id,
await updateUserAdmin({
id,
userAdminUpdateDto: {
email,
name,
storageLabel: storageLabel || '',
@ -56,9 +56,9 @@
try {
newPassword = generatePassword();
await updateUser({
updateUserDto: {
id: user.id,
await updateUserAdmin({
id: user.id,
userAdminUpdateDto: {
password: newPassword,
shouldChangePassword: true,
},

View file

@ -4,7 +4,7 @@
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiFolderSync } from '@mdi/js';
import { onMount } from 'svelte';
import { getAllUsers } from '@immich/sdk';
import { searchUsersAdmin } from '@immich/sdk';
import { user } from '$lib/stores/user.store';
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
@ -13,7 +13,7 @@
let userOptions: { value: string; text: string }[] = [];
onMount(async () => {
const users = await getAllUsers({ isAll: true });
const users = await searchUsersAdmin({});
userOptions = users.map((user) => ({ value: user.id, text: user.name }));
});

View file

@ -4,7 +4,7 @@
import { AppRoute } from '$lib/constants';
import { user } from '$lib/stores/user.store';
import { handleError } from '$lib/utils/handle-error';
import { deleteProfileImage, updateUser, type UserAvatarColor } from '@immich/sdk';
import { deleteProfileImage, updateMyUser, type UserAvatarColor } from '@immich/sdk';
import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
@ -27,9 +27,8 @@
await deleteProfileImage();
}
$user = await updateUser({
updateUserDto: {
id: $user.id,
$user = await updateMyUser({
userUpdateMeDto: {
email: $user.email,
name: $user.name,
avatarColor: color,

View file

@ -3,23 +3,18 @@
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { updateUser, type UserResponseDto } from '@immich/sdk';
import { updateMyUser, type UserAdminResponseDto } from '@immich/sdk';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import Button from '../elements/buttons/button.svelte';
export let user: UserResponseDto;
export let user: UserAdminResponseDto;
const handleSave = async () => {
try {
const data = await updateUser({
updateUserDto: {
id: user.id,
memoriesEnabled: user.memoriesEnabled,
},
});
const data = await updateMyUser({ userUpdateMeDto: { memoriesEnabled: user.memoriesEnabled } });
Object.assign(user, data);

View file

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { featureFlags } from '$lib/stores/server-config.store';
import { oauth } from '$lib/utils';
import { type UserResponseDto } from '@immich/sdk';
import { type UserAdminResponseDto } from '@immich/sdk';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
@ -10,7 +10,7 @@
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification';
export let user: UserResponseDto;
export let user: UserAdminResponseDto;
let loading = true;

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { getAllUsers, getPartners, type UserResponseDto } from '@immich/sdk';
import { searchUsers, getPartners, type UserResponseDto } from '@immich/sdk';
import { createEventDispatcher, onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte';
@ -14,11 +14,10 @@
const dispatch = createEventDispatcher<{ 'add-users': UserResponseDto[] }>();
onMount(async () => {
// TODO: update endpoint to have a query param for deleted users
let users = await getAllUsers({ isAll: false });
let users = await searchUsers();
// remove invalid users
users = users.filter((_user) => !(_user.deletedAt || _user.id === user.id));
// remove current user
users = users.filter((_user) => _user.id !== user.id);
// exclude partners from the list of users available for selection
const partners = await getPartners({ direction: 'shared-by' });

View file

@ -3,23 +3,22 @@
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
import { user } from '$lib/stores/user.store';
import { cloneDeep } from 'lodash-es';
import { updateUser } from '@immich/sdk';
import SettingInputField, {
SettingInputFieldType,
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
import { user } from '$lib/stores/user.store';
import { updateMyUser } from '@immich/sdk';
import { cloneDeep } from 'lodash-es';
import { fade } from 'svelte/transition';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
let editedUser = cloneDeep($user);
const handleSaveProfile = async () => {
try {
const data = await updateUser({
updateUserDto: {
id: editedUser.id,
const data = await updateMyUser({
userUpdateMeDto: {
email: editedUser.email,
name: editedUser.name,
},

View file

@ -1,12 +1,12 @@
import type { UserResponseDto } from '@immich/sdk';
import type { UserAdminResponseDto } from '@immich/sdk';
import { writable } from 'svelte/store';
export const user = writable<UserResponseDto>();
export const user = writable<UserAdminResponseDto>();
/**
* Reset the store to its initial undefined value. Make sure to
* only do this _after_ redirecting to an unauthenticated page.
*/
export const resetSavedUser = () => {
user.set(undefined as unknown as UserResponseDto);
user.set(undefined as unknown as UserAdminResponseDto);
};

View file

@ -11,7 +11,6 @@ import {
startOAuth,
unlinkOAuthAccount,
type SharedLinkResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiImageRefreshOutline } from '@mdi/js';
@ -264,7 +263,7 @@ export const oauth = {
login: (location: Location) => {
return finishOAuth({ oAuthCallbackDto: { url: location.href } });
},
link: (location: Location): Promise<UserResponseDto> => {
link: (location: Location) => {
return linkOAuthAccount({ oAuthCallbackDto: { url: location.href } });
},
unlink: () => {

View file

@ -1,7 +1,7 @@
import { browser } from '$app/environment';
import { serverInfo } from '$lib/stores/server-info.store';
import { user } from '$lib/stores/user.store';
import { getMyUserInfo, getStorage } from '@immich/sdk';
import { getMyUser, getStorage } from '@immich/sdk';
import { redirect } from '@sveltejs/kit';
import { get } from 'svelte/store';
import { AppRoute } from '../constants';
@ -15,7 +15,7 @@ export const loadUser = async () => {
try {
let loaded = get(user);
if (!loaded && hasAuthCookie()) {
loaded = await getMyUserInfo();
loaded = await getMyUser();
user.set(loaded);
}
return loaded;

View file

@ -1,12 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import { getAssetInfoFromParam } from '$lib/utils/navigation';
import { getUserById } from '@immich/sdk';
import { getUser } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
await authenticate();
const partner = await getUserById({ id: params.userId });
const partner = await getUser({ id: params.userId });
const asset = await getAssetInfoFromParam(params);
return {
asset,

View file

@ -23,7 +23,7 @@
deleteLibrary,
getAllLibraries,
getLibraryStatistics,
getUserById,
getUserAdmin,
removeOfflineFiles,
scanLibrary,
updateLibrary,
@ -99,7 +99,7 @@
const refreshStats = async (listIndex: number) => {
stats[listIndex] = await getLibraryStatistics({ id: libraries[listIndex].id });
owner[listIndex] = await getUserById({ id: libraries[listIndex].ownerId });
owner[listIndex] = await getUserAdmin({ id: libraries[listIndex].ownerId });
photos[listIndex] = stats[listIndex].photos;
videos[listIndex] = stats[listIndex].videos;
totalCount[listIndex] = stats[listIndex].total;

View file

@ -1,11 +1,11 @@
import { authenticate, requestServerInfo } from '$lib/utils/auth';
import { getAllUsers } from '@immich/sdk';
import { searchUsersAdmin } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async () => {
await authenticate({ admin: true });
await requestServerInfo();
const allUsers = await getAllUsers({ isAll: false });
const allUsers = await searchUsersAdmin({ withDeleted: false });
return {
allUsers,

View file

@ -1,14 +1,15 @@
<script lang="ts">
import { page } from '$app/stores';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import DeleteConfirmDialog from '$lib/components/admin-page/delete-confirm-dialogue.svelte';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import RestoreDialogue from '$lib/components/admin-page/restore-dialogue.svelte';
import Button from '$lib/components/elements/buttons/button.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import {
NotificationType,
notificationController,
@ -17,28 +18,27 @@
import { serverConfig } from '$lib/stores/server-config.store';
import { user } from '$lib/stores/user.store';
import { websocketEvents } from '$lib/stores/websocket';
import { asByteUnitString } from '$lib/utils/byte-units';
import { copyToClipboard } from '$lib/utils';
import { UserStatus, getAllUsers, type UserResponseDto } from '@immich/sdk';
import { asByteUnitString } from '$lib/utils/byte-units';
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
import { mdiClose, mdiContentCopy, mdiDeleteRestore, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
import { DateTime } from 'luxon';
import { onMount } from 'svelte';
import type { PageData } from './$types';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
export let data: PageData;
let allUsers: UserResponseDto[] = [];
let allUsers: UserAdminResponseDto[] = [];
let shouldShowEditUserForm = false;
let shouldShowCreateUserForm = false;
let shouldShowPasswordResetSuccess = false;
let shouldShowDeleteConfirmDialog = false;
let shouldShowRestoreDialog = false;
let selectedUser: UserResponseDto;
let selectedUser: UserAdminResponseDto;
let newPassword: string;
const refresh = async () => {
allUsers = await getAllUsers({ isAll: false });
allUsers = await searchUsersAdmin({ withDeleted: true });
};
const onDeleteSuccess = (userId: string) => {
@ -75,7 +75,7 @@
shouldShowCreateUserForm = false;
};
const editUserHandler = (user: UserResponseDto) => {
const editUserHandler = (user: UserAdminResponseDto) => {
selectedUser = user;
shouldShowEditUserForm = true;
};
@ -91,7 +91,7 @@
shouldShowPasswordResetSuccess = true;
};
const deleteUserHandler = (user: UserResponseDto) => {
const deleteUserHandler = (user: UserAdminResponseDto) => {
selectedUser = user;
shouldShowDeleteConfirmDialog = true;
};
@ -101,7 +101,7 @@
shouldShowDeleteConfirmDialog = false;
};
const restoreUserHandler = (user: UserResponseDto) => {
const restoreUserHandler = (user: UserAdminResponseDto) => {
selectedUser = user;
shouldShowRestoreDialog = true;
};

View file

@ -1,11 +1,11 @@
import { authenticate, requestServerInfo } from '$lib/utils/auth';
import { getAllUsers } from '@immich/sdk';
import { searchUsersAdmin } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async () => {
await authenticate({ admin: true });
await requestServerInfo();
const allUsers = await getAllUsers({ isAll: false });
const allUsers = await searchUsersAdmin({ withDeleted: true });
return {
allUsers,

View file

@ -25,5 +25,5 @@
enter the new password below.
</p>
<ChangePasswordForm user={$user} on:success={onSuccess} />
<ChangePasswordForm on:success={onSuccess} />
</FullscreenContainer>

View file

@ -1,22 +1,11 @@
import { faker } from '@faker-js/faker';
import { UserAvatarColor, UserStatus, type UserResponseDto } from '@immich/sdk';
import { UserAvatarColor, type UserResponseDto } from '@immich/sdk';
import { Sync } from 'factory.ts';
export const userFactory = Sync.makeFactory<UserResponseDto>({
id: Sync.each(() => faker.string.uuid()),
email: Sync.each(() => faker.internet.email()),
name: Sync.each(() => faker.person.fullName()),
storageLabel: Sync.each(() => faker.string.alphanumeric()),
profileImagePath: '',
shouldChangePassword: Sync.each(() => faker.datatype.boolean()),
isAdmin: true,
createdAt: Sync.each(() => faker.date.past().toISOString()),
deletedAt: null,
updatedAt: Sync.each(() => faker.date.past().toISOString()),
memoriesEnabled: true,
oauthId: '',
avatarColor: UserAvatarColor.Primary,
quotaUsageInBytes: 0,
quotaSizeInBytes: null,
status: UserStatus.Active,
});