mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05: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