mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 04:05:39 -04:00
fix(server): restore user (#15763)
This commit is contained in:
parent
9033a99587
commit
a0aea021a1
@ -356,5 +356,24 @@ describe('/admin/users', () => {
|
|||||||
expect(status).toBe(403);
|
expect(status).toBe(403);
|
||||||
expect(body).toEqual(errorDto.forbidden);
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should restore a user', async () => {
|
||||||
|
const user = await utils.userSetup(admin.accessToken, createUserDto.create('restore'));
|
||||||
|
|
||||||
|
await deleteUserAdmin({ id: user.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post(`/admin/users/${user.userId}/restore`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: user.userId,
|
||||||
|
email: user.userEmail,
|
||||||
|
status: 'active',
|
||||||
|
deletedAt: null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -539,7 +539,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"200": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -1475,7 +1475,7 @@ export function restoreUserAdmin({ id }: {
|
|||||||
id: string;
|
id: string;
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 201;
|
status: 200;
|
||||||
data: UserAdminResponseDto;
|
data: UserAdminResponseDto;
|
||||||
}>(`/admin/users/${encodeURIComponent(id)}/restore`, {
|
}>(`/admin/users/${encodeURIComponent(id)}/restore`, {
|
||||||
...opts,
|
...opts,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
|
import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
|
||||||
@ -75,6 +75,7 @@ export class UserAdminController {
|
|||||||
|
|
||||||
@Post(':id/restore')
|
@Post(':id/restore')
|
||||||
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.restore(auth, id);
|
return this.service.restore(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ export interface IUserRepository {
|
|||||||
getUserStats(): Promise<UserStatsQueryResponse[]>;
|
getUserStats(): Promise<UserStatsQueryResponse[]>;
|
||||||
create(user: Insertable<Users>): Promise<UserEntity>;
|
create(user: Insertable<Users>): Promise<UserEntity>;
|
||||||
update(id: string, user: Updateable<Users>): Promise<UserEntity>;
|
update(id: string, user: Updateable<Users>): Promise<UserEntity>;
|
||||||
|
restore(id: string): Promise<UserEntity>;
|
||||||
upsertMetadata<T extends keyof UserMetadata>(id: string, item: { key: T; value: UserMetadata[T] }): Promise<void>;
|
upsertMetadata<T extends keyof UserMetadata>(id: string, item: { key: T; value: UserMetadata[T] }): Promise<void>;
|
||||||
deleteMetadata<T extends keyof UserMetadata>(id: string, key: T): Promise<void>;
|
deleteMetadata<T extends keyof UserMetadata>(id: string, key: T): Promise<void>;
|
||||||
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
|
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
|
||||||
|
@ -5,6 +5,7 @@ import { DB, UserMetadata as DbUserMetadata, Users } from 'src/db';
|
|||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { UserMetadata } from 'src/entities/user-metadata.entity';
|
import { UserMetadata } from 'src/entities/user-metadata.entity';
|
||||||
import { UserEntity, withMetadata } from 'src/entities/user.entity';
|
import { UserEntity, withMetadata } from 'src/entities/user.entity';
|
||||||
|
import { UserStatus } from 'src/enum';
|
||||||
import {
|
import {
|
||||||
IUserRepository,
|
IUserRepository,
|
||||||
UserFindOptions,
|
UserFindOptions,
|
||||||
@ -140,6 +141,16 @@ export class UserRepository implements IUserRepository {
|
|||||||
.executeTakeFirst() as unknown as Promise<UserEntity>;
|
.executeTakeFirst() as unknown as Promise<UserEntity>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restore(id: string): Promise<UserEntity> {
|
||||||
|
return this.db
|
||||||
|
.updateTable('users')
|
||||||
|
.set({ status: UserStatus.ACTIVE, deletedAt: null })
|
||||||
|
.where('users.id', '=', asUuid(id))
|
||||||
|
.returning(columns)
|
||||||
|
.returning(withMetadata)
|
||||||
|
.executeTakeFirst() as unknown as Promise<UserEntity>;
|
||||||
|
}
|
||||||
|
|
||||||
async upsertMetadata<T extends keyof UserMetadata>(id: string, { key, value }: { key: T; value: UserMetadata[T] }) {
|
async upsertMetadata<T extends keyof UserMetadata>(id: string, { key, value }: { key: T; value: UserMetadata[T] }) {
|
||||||
await this.db
|
await this.db
|
||||||
.insertInto('user_metadata')
|
.insertInto('user_metadata')
|
||||||
|
@ -173,9 +173,9 @@ describe(UserAdminService.name, () => {
|
|||||||
|
|
||||||
it('should restore an user', async () => {
|
it('should restore an user', async () => {
|
||||||
userMock.get.mockResolvedValue(userStub.user1);
|
userMock.get.mockResolvedValue(userStub.user1);
|
||||||
userMock.update.mockResolvedValue(userStub.user1);
|
userMock.restore.mockResolvedValue(userStub.user1);
|
||||||
await expect(sut.restore(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUserAdmin(userStub.user1));
|
await expect(sut.restore(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUserAdmin(userStub.user1));
|
||||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { status: UserStatus.ACTIVE, deletedAt: null });
|
expect(userMock.restore).toHaveBeenCalledWith(userStub.user1.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,7 @@ export class UserAdminService extends BaseService {
|
|||||||
async restore(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
|
async restore(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
|
||||||
await this.findOrFail(id, { withDeleted: true });
|
await this.findOrFail(id, { withDeleted: true });
|
||||||
await this.albumRepository.restoreAll(id);
|
await this.albumRepository.restoreAll(id);
|
||||||
const user = await this.userRepository.update(id, { deletedAt: null, status: UserStatus.ACTIVE });
|
const user = await this.userRepository.restore(id);
|
||||||
return mapUserAdmin(user);
|
return mapUserAdmin(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ export const newUserRepositoryMock = (): Mocked<IUserRepository> => {
|
|||||||
create: vitest.fn(),
|
create: vitest.fn(),
|
||||||
update: vitest.fn(),
|
update: vitest.fn(),
|
||||||
delete: vitest.fn(),
|
delete: vitest.fn(),
|
||||||
|
restore: vitest.fn(),
|
||||||
getDeletedUsers: vitest.fn(),
|
getDeletedUsers: vitest.fn(),
|
||||||
hasAdmin: vitest.fn(),
|
hasAdmin: vitest.fn(),
|
||||||
updateUsage: vitest.fn(),
|
updateUsage: vitest.fn(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user