mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	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:
		
							parent
							
								
									e7c8501930
								
							
						
					
					
						commit
						75830a4878
					
				@ -1,4 +1,4 @@
 | 
			
		||||
import { getMyUserInfo } from '@immich/sdk';
 | 
			
		||||
import { getMyUser } from '@immich/sdk';
 | 
			
		||||
import { existsSync } from 'node:fs';
 | 
			
		||||
import { mkdir, unlink } from 'node:fs/promises';
 | 
			
		||||
import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils';
 | 
			
		||||
@ -10,13 +10,13 @@ export const login = async (url: string, key: string, options: BaseOptions) => {
 | 
			
		||||
 | 
			
		||||
  await connect(url, key);
 | 
			
		||||
 | 
			
		||||
  const [error, userInfo] = await withError(getMyUserInfo());
 | 
			
		||||
  const [error, user] = await withError(getMyUser());
 | 
			
		||||
  if (error) {
 | 
			
		||||
    logError(error, 'Failed to load user info');
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.log(`Logged in as ${userInfo.email}`);
 | 
			
		||||
  console.log(`Logged in as ${user.email}`);
 | 
			
		||||
 | 
			
		||||
  if (!existsSync(configDir)) {
 | 
			
		||||
    // Create config folder if it doesn't exist
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { getAssetStatistics, getMyUserInfo, getServerVersion, getSupportedMediaTypes } from '@immich/sdk';
 | 
			
		||||
import { getAssetStatistics, getMyUser, getServerVersion, getSupportedMediaTypes } from '@immich/sdk';
 | 
			
		||||
import { BaseOptions, authenticate } from 'src/utils';
 | 
			
		||||
 | 
			
		||||
export const serverInfo = async (options: BaseOptions) => {
 | 
			
		||||
@ -8,7 +8,7 @@ export const serverInfo = async (options: BaseOptions) => {
 | 
			
		||||
    getServerVersion(),
 | 
			
		||||
    getSupportedMediaTypes(),
 | 
			
		||||
    getAssetStatistics({}),
 | 
			
		||||
    getMyUserInfo(),
 | 
			
		||||
    getMyUser(),
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  console.log(`Server Info (via ${userInfo.email})`);
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { getMyUserInfo, init, isHttpError } from '@immich/sdk';
 | 
			
		||||
import { getMyUser, init, isHttpError } from '@immich/sdk';
 | 
			
		||||
import { glob } from 'fast-glob';
 | 
			
		||||
import { createHash } from 'node:crypto';
 | 
			
		||||
import { createReadStream } from 'node:fs';
 | 
			
		||||
@ -48,7 +48,7 @@ export const connect = async (url: string, key: string) => {
 | 
			
		||||
 | 
			
		||||
  init({ baseUrl: url, apiKey: key });
 | 
			
		||||
 | 
			
		||||
  const [error] = await withError(getMyUserInfo());
 | 
			
		||||
  const [error] = await withError(getMyUser());
 | 
			
		||||
  if (isHttpError(error)) {
 | 
			
		||||
    logError(error, 'Failed to connect to server');
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import {
 | 
			
		||||
  AlbumUserRole,
 | 
			
		||||
  AssetFileUploadResponseDto,
 | 
			
		||||
  AssetOrder,
 | 
			
		||||
  deleteUser,
 | 
			
		||||
  deleteUserAdmin,
 | 
			
		||||
  getAlbumInfo,
 | 
			
		||||
  LoginResponseDto,
 | 
			
		||||
  SharedLinkType,
 | 
			
		||||
@ -107,7 +107,7 @@ describe('/albums', () => {
 | 
			
		||||
      }),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    await deleteUser({ id: user3.userId, deleteUserDto: {} }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
    await deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /albums', () => {
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import {
 | 
			
		||||
  LoginResponseDto,
 | 
			
		||||
  SharedLinkType,
 | 
			
		||||
  getAssetInfo,
 | 
			
		||||
  getMyUserInfo,
 | 
			
		||||
  getMyUser,
 | 
			
		||||
  updateAssets,
 | 
			
		||||
} from '@immich/sdk';
 | 
			
		||||
import { exiftool } from 'exiftool-vendored';
 | 
			
		||||
@ -1162,7 +1162,7 @@ describe('/asset', () => {
 | 
			
		||||
      expect(body).toEqual({ id: expect.any(String), duplicate: false });
 | 
			
		||||
      expect(status).toBe(201);
 | 
			
		||||
 | 
			
		||||
      const user = await getMyUserInfo({ headers: asBearerAuth(quotaUser.accessToken) });
 | 
			
		||||
      const user = await getMyUser({ headers: asBearerAuth(quotaUser.accessToken) });
 | 
			
		||||
 | 
			
		||||
      expect(user).toEqual(expect.objectContaining({ quotaUsageInBytes: 70 }));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import {
 | 
			
		||||
  SharedLinkResponseDto,
 | 
			
		||||
  SharedLinkType,
 | 
			
		||||
  createAlbum,
 | 
			
		||||
  deleteUser,
 | 
			
		||||
  deleteUserAdmin,
 | 
			
		||||
} from '@immich/sdk';
 | 
			
		||||
import { createUserDto, uuidDto } from 'src/fixtures';
 | 
			
		||||
import { errorDto } from 'src/responses';
 | 
			
		||||
@ -86,7 +86,7 @@ describe('/shared-links', () => {
 | 
			
		||||
        }),
 | 
			
		||||
      ]);
 | 
			
		||||
 | 
			
		||||
    await deleteUser({ id: user2.userId, deleteUserDto: {} }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
    await deleteUserAdmin({ id: user2.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /share/${key}', () => {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										317
									
								
								e2e/src/api/specs/user-admin.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								e2e/src/api/specs/user-admin.e2e-spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,317 @@
 | 
			
		||||
import { LoginResponseDto, deleteUserAdmin, getMyUser, getUserAdmin, login } from '@immich/sdk';
 | 
			
		||||
import { Socket } from 'socket.io-client';
 | 
			
		||||
import { createUserDto, uuidDto } from 'src/fixtures';
 | 
			
		||||
import { errorDto } from 'src/responses';
 | 
			
		||||
import { app, asBearerAuth, utils } from 'src/utils';
 | 
			
		||||
import request from 'supertest';
 | 
			
		||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
 | 
			
		||||
 | 
			
		||||
describe('/admin/users', () => {
 | 
			
		||||
  let websocket: Socket;
 | 
			
		||||
 | 
			
		||||
  let admin: LoginResponseDto;
 | 
			
		||||
  let nonAdmin: LoginResponseDto;
 | 
			
		||||
  let deletedUser: LoginResponseDto;
 | 
			
		||||
  let userToDelete: LoginResponseDto;
 | 
			
		||||
  let userToHardDelete: LoginResponseDto;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    await utils.resetDatabase();
 | 
			
		||||
    admin = await utils.adminSetup({ onboarding: false });
 | 
			
		||||
 | 
			
		||||
    [websocket, nonAdmin, deletedUser, userToDelete, userToHardDelete] = await Promise.all([
 | 
			
		||||
      utils.connectWebsocket(admin.accessToken),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user1),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user2),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user3),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user4),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    await deleteUserAdmin(
 | 
			
		||||
      { id: deletedUser.userId, userAdminDeleteDto: {} },
 | 
			
		||||
      { headers: asBearerAuth(admin.accessToken) },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  afterAll(() => {
 | 
			
		||||
    utils.disconnectWebsocket(websocket);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /admin/users', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).get(`/admin/users`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require authorization', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/admin/users`)
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(403);
 | 
			
		||||
      expect(body).toEqual(errorDto.forbidden);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should hide deleted users by default', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/admin/users`)
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toHaveLength(4);
 | 
			
		||||
      expect(body).toEqual(
 | 
			
		||||
        expect.arrayContaining([
 | 
			
		||||
          expect.objectContaining({ email: admin.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: nonAdmin.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: userToDelete.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: userToHardDelete.userEmail }),
 | 
			
		||||
        ]),
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should include deleted users', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/admin/users?withDeleted=true`)
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toHaveLength(5);
 | 
			
		||||
      expect(body).toEqual(
 | 
			
		||||
        expect.arrayContaining([
 | 
			
		||||
          expect.objectContaining({ email: admin.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: nonAdmin.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: userToDelete.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: userToHardDelete.userEmail }),
 | 
			
		||||
          expect.objectContaining({ email: deletedUser.userEmail }),
 | 
			
		||||
        ]),
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /admin/users', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).post(`/admin/users`).send(createUserDto.user1);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require authorization', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .post(`/admin/users`)
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`)
 | 
			
		||||
        .send(createUserDto.user1);
 | 
			
		||||
      expect(status).toBe(403);
 | 
			
		||||
      expect(body).toEqual(errorDto.forbidden);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    for (const key of [
 | 
			
		||||
      'password',
 | 
			
		||||
      'email',
 | 
			
		||||
      'name',
 | 
			
		||||
      'quotaSizeInBytes',
 | 
			
		||||
      'shouldChangePassword',
 | 
			
		||||
      'memoriesEnabled',
 | 
			
		||||
      'notify',
 | 
			
		||||
    ]) {
 | 
			
		||||
      it(`should not allow null ${key}`, async () => {
 | 
			
		||||
        const { status, body } = await request(app)
 | 
			
		||||
          .post(`/admin/users`)
 | 
			
		||||
          .set('Authorization', `Bearer ${admin.accessToken}`)
 | 
			
		||||
          .send({ ...createUserDto.user1, [key]: null });
 | 
			
		||||
        expect(status).toBe(400);
 | 
			
		||||
        expect(body).toEqual(errorDto.badRequest());
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('should ignore `isAdmin`', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .post(`/admin/users`)
 | 
			
		||||
        .send({
 | 
			
		||||
          isAdmin: true,
 | 
			
		||||
          email: 'user5@immich.cloud',
 | 
			
		||||
          password: 'password123',
 | 
			
		||||
          name: 'Immich',
 | 
			
		||||
        })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        email: 'user5@immich.cloud',
 | 
			
		||||
        isAdmin: false,
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
      });
 | 
			
		||||
      expect(status).toBe(201);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should create a user without memories enabled', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .post(`/admin/users`)
 | 
			
		||||
        .send({
 | 
			
		||||
          email: 'no-memories@immich.cloud',
 | 
			
		||||
          password: 'Password123',
 | 
			
		||||
          name: 'No Memories',
 | 
			
		||||
          memoriesEnabled: false,
 | 
			
		||||
        })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        email: 'no-memories@immich.cloud',
 | 
			
		||||
        memoriesEnabled: false,
 | 
			
		||||
      });
 | 
			
		||||
      expect(status).toBe(201);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('PUT /admin/users/:id', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).put(`/admin/users/${uuidDto.notFound}`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require authorization', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/admin/users/${uuidDto.notFound}`)
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(403);
 | 
			
		||||
      expect(body).toEqual(errorDto.forbidden);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    for (const key of ['password', 'email', 'name', 'shouldChangePassword', 'memoriesEnabled']) {
 | 
			
		||||
      it(`should not allow null ${key}`, async () => {
 | 
			
		||||
        const { status, body } = await request(app)
 | 
			
		||||
          .put(`/admin/users/${uuidDto.notFound}`)
 | 
			
		||||
          .set('Authorization', `Bearer ${admin.accessToken}`)
 | 
			
		||||
          .send({ [key]: null });
 | 
			
		||||
        expect(status).toBe(400);
 | 
			
		||||
        expect(body).toEqual(errorDto.badRequest());
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('should not allow a non-admin to become an admin', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/admin/users/${nonAdmin.userId}`)
 | 
			
		||||
        .send({ isAdmin: true })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({ isAdmin: false });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('ignores updates to profileImagePath', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/admin/users/${admin.userId}`)
 | 
			
		||||
        .send({ profileImagePath: 'invalid.jpg' })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({ id: admin.userId, profileImagePath: '' });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should update first and last name', async () => {
 | 
			
		||||
      const before = await getUserAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/admin/users/${admin.userId}`)
 | 
			
		||||
        .send({ name: 'Name' })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toEqual({
 | 
			
		||||
        ...before,
 | 
			
		||||
        updatedAt: expect.any(String),
 | 
			
		||||
        name: 'Name',
 | 
			
		||||
      });
 | 
			
		||||
      expect(before.updatedAt).not.toEqual(body.updatedAt);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should update memories enabled', async () => {
 | 
			
		||||
      const before = await getUserAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/admin/users/${admin.userId}`)
 | 
			
		||||
        .send({ memoriesEnabled: false })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        ...before,
 | 
			
		||||
        updatedAt: expect.anything(),
 | 
			
		||||
        memoriesEnabled: false,
 | 
			
		||||
      });
 | 
			
		||||
      expect(before.updatedAt).not.toEqual(body.updatedAt);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should update password', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/admin/users/${nonAdmin.userId}`)
 | 
			
		||||
        .send({ password: 'super-secret' })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({ email: nonAdmin.userEmail });
 | 
			
		||||
 | 
			
		||||
      const token = await login({ loginCredentialDto: { email: nonAdmin.userEmail, password: 'super-secret' } });
 | 
			
		||||
      expect(token.accessToken).toBeDefined();
 | 
			
		||||
 | 
			
		||||
      const user = await getMyUser({ headers: asBearerAuth(token.accessToken) });
 | 
			
		||||
      expect(user).toMatchObject({ email: nonAdmin.userEmail });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('DELETE /admin/users/:id', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).delete(`/admin/users/${userToDelete.userId}`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require authorization', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .delete(`/admin/users/${userToDelete.userId}`)
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(403);
 | 
			
		||||
      expect(body).toEqual(errorDto.forbidden);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should delete user', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .delete(`/admin/users/${userToDelete.userId}`)
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: userToDelete.userId,
 | 
			
		||||
        updatedAt: expect.any(String),
 | 
			
		||||
        deletedAt: expect.any(String),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should hard delete a user', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .delete(`/admin/users/${userToHardDelete.userId}`)
 | 
			
		||||
        .send({ force: true })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: userToHardDelete.userId,
 | 
			
		||||
        updatedAt: expect.any(String),
 | 
			
		||||
        deletedAt: expect.any(String),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await utils.waitForWebsocketEvent({ event: 'userDelete', id: userToHardDelete.userId, timeout: 5000 });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /admin/users/:id/restore', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).post(`/admin/users/${userToDelete.userId}/restore`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require authorization', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .post(`/admin/users/${userToDelete.userId}/restore`)
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(403);
 | 
			
		||||
      expect(body).toEqual(errorDto.forbidden);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -1,37 +1,28 @@
 | 
			
		||||
import { LoginResponseDto, deleteUser, getUserById } from '@immich/sdk';
 | 
			
		||||
import { Socket } from 'socket.io-client';
 | 
			
		||||
import { createUserDto, userDto } from 'src/fixtures';
 | 
			
		||||
import { LoginResponseDto, SharedLinkType, deleteUserAdmin, getMyUser, login } from '@immich/sdk';
 | 
			
		||||
import { createUserDto } from 'src/fixtures';
 | 
			
		||||
import { errorDto } from 'src/responses';
 | 
			
		||||
import { app, asBearerAuth, utils } from 'src/utils';
 | 
			
		||||
import request from 'supertest';
 | 
			
		||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
 | 
			
		||||
import { beforeAll, describe, expect, it } from 'vitest';
 | 
			
		||||
 | 
			
		||||
describe('/users', () => {
 | 
			
		||||
  let websocket: Socket;
 | 
			
		||||
 | 
			
		||||
  let admin: LoginResponseDto;
 | 
			
		||||
  let deletedUser: LoginResponseDto;
 | 
			
		||||
  let userToDelete: LoginResponseDto;
 | 
			
		||||
  let userToHardDelete: LoginResponseDto;
 | 
			
		||||
  let nonAdmin: LoginResponseDto;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    await utils.resetDatabase();
 | 
			
		||||
    admin = await utils.adminSetup({ onboarding: false });
 | 
			
		||||
 | 
			
		||||
    [websocket, deletedUser, nonAdmin, userToDelete, userToHardDelete] = await Promise.all([
 | 
			
		||||
      utils.connectWebsocket(admin.accessToken),
 | 
			
		||||
    [deletedUser, nonAdmin] = await Promise.all([
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user1),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user2),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user3),
 | 
			
		||||
      utils.userSetup(admin.accessToken, createUserDto.user4),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    await deleteUser({ id: deletedUser.userId, deleteUserDto: {} }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  afterAll(() => {
 | 
			
		||||
    utils.disconnectWebsocket(websocket);
 | 
			
		||||
    await deleteUserAdmin(
 | 
			
		||||
      { id: deletedUser.userId, userAdminDeleteDto: {} },
 | 
			
		||||
      { headers: asBearerAuth(admin.accessToken) },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /users', () => {
 | 
			
		||||
@ -44,71 +35,14 @@ describe('/users', () => {
 | 
			
		||||
    it('should get users', async () => {
 | 
			
		||||
      const { status, body } = await request(app).get('/users').set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(status).toEqual(200);
 | 
			
		||||
      expect(body).toHaveLength(5);
 | 
			
		||||
      expect(body).toEqual(
 | 
			
		||||
        expect.arrayContaining([
 | 
			
		||||
          expect.objectContaining({ email: 'admin@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user1@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user2@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user3@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user4@immich.cloud' }),
 | 
			
		||||
        ]),
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should hide deleted users', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/users`)
 | 
			
		||||
        .query({ isAll: true })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toHaveLength(4);
 | 
			
		||||
      expect(body).toHaveLength(2);
 | 
			
		||||
      expect(body).toEqual(
 | 
			
		||||
        expect.arrayContaining([
 | 
			
		||||
          expect.objectContaining({ email: 'admin@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user2@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user3@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user4@immich.cloud' }),
 | 
			
		||||
        ]),
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should include deleted users', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/users`)
 | 
			
		||||
        .query({ isAll: false })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toHaveLength(5);
 | 
			
		||||
      expect(body).toEqual(
 | 
			
		||||
        expect.arrayContaining([
 | 
			
		||||
          expect.objectContaining({ email: 'admin@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user1@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user2@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user3@immich.cloud' }),
 | 
			
		||||
          expect.objectContaining({ email: 'user4@immich.cloud' }),
 | 
			
		||||
        ]),
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /users/:id', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status } = await request(app).get(`/users/${admin.userId}`);
 | 
			
		||||
      expect(status).toEqual(401);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should get the user info', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/users/${admin.userId}`)
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: admin.userId,
 | 
			
		||||
        email: 'admin@immich.cloud',
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /users/me', () => {
 | 
			
		||||
@ -118,154 +52,54 @@ describe('/users', () => {
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should get my info', async () => {
 | 
			
		||||
    it('should not work for shared links', async () => {
 | 
			
		||||
      const album = await utils.createAlbum(admin.accessToken, { albumName: 'Album' });
 | 
			
		||||
      const sharedLink = await utils.createSharedLink(admin.accessToken, {
 | 
			
		||||
        type: SharedLinkType.Album,
 | 
			
		||||
        albumId: album.id,
 | 
			
		||||
      });
 | 
			
		||||
      const { status, body } = await request(app).get(`/users/me?key=${sharedLink.key}`);
 | 
			
		||||
      expect(status).toBe(403);
 | 
			
		||||
      expect(body).toEqual(errorDto.forbidden);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should get my user', async () => {
 | 
			
		||||
      const { status, body } = await request(app).get(`/users/me`).set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: admin.userId,
 | 
			
		||||
        email: 'admin@immich.cloud',
 | 
			
		||||
        memoriesEnabled: true,
 | 
			
		||||
        quotaUsageInBytes: 0,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /users', () => {
 | 
			
		||||
  describe('PUT /users/me', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).post(`/users`).send(createUserDto.user1);
 | 
			
		||||
      const { status, body } = await request(app).put(`/users/me`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    for (const key of Object.keys(createUserDto.user1)) {
 | 
			
		||||
    for (const key of ['email', 'name', 'memoriesEnabled', 'avatarColor']) {
 | 
			
		||||
      it(`should not allow null ${key}`, async () => {
 | 
			
		||||
        const dto = { [key]: null };
 | 
			
		||||
        const { status, body } = await request(app)
 | 
			
		||||
          .post(`/users`)
 | 
			
		||||
          .put(`/users/me`)
 | 
			
		||||
          .set('Authorization', `Bearer ${admin.accessToken}`)
 | 
			
		||||
          .send({ ...createUserDto.user1, [key]: null });
 | 
			
		||||
          .send(dto);
 | 
			
		||||
        expect(status).toBe(400);
 | 
			
		||||
        expect(body).toEqual(errorDto.badRequest());
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('should ignore `isAdmin`', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .post(`/users`)
 | 
			
		||||
        .send({
 | 
			
		||||
          isAdmin: true,
 | 
			
		||||
          email: 'user5@immich.cloud',
 | 
			
		||||
          password: 'password123',
 | 
			
		||||
          name: 'Immich',
 | 
			
		||||
        })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        email: 'user5@immich.cloud',
 | 
			
		||||
        isAdmin: false,
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
      });
 | 
			
		||||
      expect(status).toBe(201);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should create a user without memories enabled', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .post(`/users`)
 | 
			
		||||
        .send({
 | 
			
		||||
          email: 'no-memories@immich.cloud',
 | 
			
		||||
          password: 'Password123',
 | 
			
		||||
          name: 'No Memories',
 | 
			
		||||
          memoriesEnabled: false,
 | 
			
		||||
        })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        email: 'no-memories@immich.cloud',
 | 
			
		||||
        memoriesEnabled: false,
 | 
			
		||||
      });
 | 
			
		||||
      expect(status).toBe(201);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('DELETE /users/:id', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).delete(`/users/${userToDelete.userId}`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should delete user', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .delete(`/users/${userToDelete.userId}`)
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: userToDelete.userId,
 | 
			
		||||
        updatedAt: expect.any(String),
 | 
			
		||||
        deletedAt: expect.any(String),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should hard delete user', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .delete(`/users/${userToHardDelete.userId}`)
 | 
			
		||||
        .send({ force: true })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: userToHardDelete.userId,
 | 
			
		||||
        updatedAt: expect.any(String),
 | 
			
		||||
        deletedAt: expect.any(String),
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await utils.waitForWebsocketEvent({ event: 'userDelete', id: userToHardDelete.userId, timeout: 5000 });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('PUT /users', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status, body } = await request(app).put(`/users`);
 | 
			
		||||
      expect(status).toBe(401);
 | 
			
		||||
      expect(body).toEqual(errorDto.unauthorized);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    for (const key of Object.keys(userDto.admin)) {
 | 
			
		||||
      it(`should not allow null ${key}`, async () => {
 | 
			
		||||
        const { status, body } = await request(app)
 | 
			
		||||
          .put(`/users`)
 | 
			
		||||
          .set('Authorization', `Bearer ${admin.accessToken}`)
 | 
			
		||||
          .send({ ...userDto.admin, [key]: null });
 | 
			
		||||
        expect(status).toBe(400);
 | 
			
		||||
        expect(body).toEqual(errorDto.badRequest());
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('should not allow a non-admin to become an admin', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users`)
 | 
			
		||||
        .send({ isAdmin: true, id: nonAdmin.userId })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(400);
 | 
			
		||||
      expect(body).toEqual(errorDto.alreadyHasAdmin);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('ignores updates to profileImagePath', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users`)
 | 
			
		||||
        .send({ id: admin.userId, profileImagePath: 'invalid.jpg' })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({ id: admin.userId, profileImagePath: '' });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should update first and last name', async () => {
 | 
			
		||||
      const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
      const before = await getMyUser({ headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users`)
 | 
			
		||||
        .send({
 | 
			
		||||
          id: admin.userId,
 | 
			
		||||
          name: 'Name',
 | 
			
		||||
        })
 | 
			
		||||
        .put(`/users/me`)
 | 
			
		||||
        .send({ name: 'Name' })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
@ -274,17 +108,13 @@ describe('/users', () => {
 | 
			
		||||
        updatedAt: expect.any(String),
 | 
			
		||||
        name: 'Name',
 | 
			
		||||
      });
 | 
			
		||||
      expect(before.updatedAt).not.toEqual(body.updatedAt);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should update memories enabled', async () => {
 | 
			
		||||
      const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
      const before = await getMyUser({ headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users`)
 | 
			
		||||
        .send({
 | 
			
		||||
          id: admin.userId,
 | 
			
		||||
          memoriesEnabled: false,
 | 
			
		||||
        })
 | 
			
		||||
        .put(`/users/me`)
 | 
			
		||||
        .send({ memoriesEnabled: false })
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
@ -293,7 +123,80 @@ describe('/users', () => {
 | 
			
		||||
        updatedAt: expect.anything(),
 | 
			
		||||
        memoriesEnabled: false,
 | 
			
		||||
      });
 | 
			
		||||
      expect(before.updatedAt).not.toEqual(body.updatedAt);
 | 
			
		||||
 | 
			
		||||
      const after = await getMyUser({ headers: asBearerAuth(admin.accessToken) });
 | 
			
		||||
      expect(after.memoriesEnabled).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    /** @deprecated */
 | 
			
		||||
    it('should allow a user to change their password (deprecated)', async () => {
 | 
			
		||||
      const user = await getMyUser({ headers: asBearerAuth(nonAdmin.accessToken) });
 | 
			
		||||
 | 
			
		||||
      expect(user.shouldChangePassword).toBe(true);
 | 
			
		||||
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users/me`)
 | 
			
		||||
        .send({ password: 'super-secret' })
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        email: nonAdmin.userEmail,
 | 
			
		||||
        shouldChangePassword: false,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const token = await login({ loginCredentialDto: { email: nonAdmin.userEmail, password: 'super-secret' } });
 | 
			
		||||
 | 
			
		||||
      expect(token.accessToken).toBeDefined();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not allow user to change to a taken email', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users/me`)
 | 
			
		||||
        .send({ email: 'admin@immich.cloud' })
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(400);
 | 
			
		||||
      expect(body).toMatchObject(errorDto.badRequest('Email already in use by another account'));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should update my email', async () => {
 | 
			
		||||
      const before = await getMyUser({ headers: asBearerAuth(nonAdmin.accessToken) });
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .put(`/users/me`)
 | 
			
		||||
        .send({ email: 'non-admin@immich.cloud' })
 | 
			
		||||
        .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
 | 
			
		||||
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        ...before,
 | 
			
		||||
        email: 'non-admin@immich.cloud',
 | 
			
		||||
        updatedAt: expect.anything(),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /users/:id', () => {
 | 
			
		||||
    it('should require authentication', async () => {
 | 
			
		||||
      const { status } = await request(app).get(`/users/${admin.userId}`);
 | 
			
		||||
      expect(status).toEqual(401);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should get the user', async () => {
 | 
			
		||||
      const { status, body } = await request(app)
 | 
			
		||||
        .get(`/users/${admin.userId}`)
 | 
			
		||||
        .set('Authorization', `Bearer ${admin.accessToken}`);
 | 
			
		||||
      expect(status).toBe(200);
 | 
			
		||||
      expect(body).toMatchObject({
 | 
			
		||||
        id: admin.userId,
 | 
			
		||||
        email: 'admin@immich.cloud',
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      expect(body).not.toMatchObject({
 | 
			
		||||
        shouldChangePassword: expect.anything(),
 | 
			
		||||
        memoriesEnabled: expect.anything(),
 | 
			
		||||
        storageLabel: expect.anything(),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -5,10 +5,10 @@ import {
 | 
			
		||||
  CreateAlbumDto,
 | 
			
		||||
  CreateAssetDto,
 | 
			
		||||
  CreateLibraryDto,
 | 
			
		||||
  CreateUserDto,
 | 
			
		||||
  MetadataSearchDto,
 | 
			
		||||
  PersonCreateDto,
 | 
			
		||||
  SharedLinkCreateDto,
 | 
			
		||||
  UserAdminCreateDto,
 | 
			
		||||
  ValidateLibraryDto,
 | 
			
		||||
  createAlbum,
 | 
			
		||||
  createApiKey,
 | 
			
		||||
@ -16,7 +16,7 @@ import {
 | 
			
		||||
  createPartner,
 | 
			
		||||
  createPerson,
 | 
			
		||||
  createSharedLink,
 | 
			
		||||
  createUser,
 | 
			
		||||
  createUserAdmin,
 | 
			
		||||
  deleteAssets,
 | 
			
		||||
  getAllJobsStatus,
 | 
			
		||||
  getAssetInfo,
 | 
			
		||||
@ -273,8 +273,8 @@ export const utils = {
 | 
			
		||||
    return response;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  userSetup: async (accessToken: string, dto: CreateUserDto) => {
 | 
			
		||||
    await createUser({ createUserDto: dto }, { headers: asBearerAuth(accessToken) });
 | 
			
		||||
  userSetup: async (accessToken: string, dto: UserAdminCreateDto) => {
 | 
			
		||||
    await createUserAdmin({ userAdminCreateDto: dto }, { headers: asBearerAuth(accessToken) });
 | 
			
		||||
    return login({
 | 
			
		||||
      loginCredentialDto: { email: dto.email, password: dto.password },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ class User {
 | 
			
		||||
 | 
			
		||||
  Id get isarId => fastHash(id);
 | 
			
		||||
 | 
			
		||||
  User.fromUserDto(UserResponseDto dto)
 | 
			
		||||
  User.fromUserDto(UserAdminResponseDto dto)
 | 
			
		||||
      : id = dto.id,
 | 
			
		||||
        updatedAt = dto.updatedAt,
 | 
			
		||||
        email = dto.email,
 | 
			
		||||
@ -44,21 +44,21 @@ class User {
 | 
			
		||||
 | 
			
		||||
  User.fromPartnerDto(PartnerResponseDto dto)
 | 
			
		||||
      : id = dto.id,
 | 
			
		||||
        updatedAt = dto.updatedAt,
 | 
			
		||||
        updatedAt = DateTime.now(),
 | 
			
		||||
        email = dto.email,
 | 
			
		||||
        name = dto.name,
 | 
			
		||||
        isPartnerSharedBy = false,
 | 
			
		||||
        isPartnerSharedWith = false,
 | 
			
		||||
        profileImagePath = dto.profileImagePath,
 | 
			
		||||
        isAdmin = dto.isAdmin,
 | 
			
		||||
        memoryEnabled = dto.memoriesEnabled ?? false,
 | 
			
		||||
        isAdmin = false,
 | 
			
		||||
        memoryEnabled = false,
 | 
			
		||||
        avatarColor = dto.avatarColor.toAvatarColor(),
 | 
			
		||||
        inTimeline = dto.inTimeline ?? false,
 | 
			
		||||
        quotaUsageInBytes = dto.quotaUsageInBytes ?? 0,
 | 
			
		||||
        quotaSizeInBytes = dto.quotaSizeInBytes ?? 0;
 | 
			
		||||
        quotaUsageInBytes = 0,
 | 
			
		||||
        quotaSizeInBytes = 0;
 | 
			
		||||
 | 
			
		||||
  /// Base user dto used where the complete user object is not required
 | 
			
		||||
  User.fromSimpleUserDto(UserDto dto)
 | 
			
		||||
  User.fromSimpleUserDto(UserResponseDto dto)
 | 
			
		||||
      : id = dto.id,
 | 
			
		||||
        email = dto.email,
 | 
			
		||||
        name = dto.name,
 | 
			
		||||
 | 
			
		||||
@ -138,11 +138,9 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
 | 
			
		||||
  Future<bool> changePassword(String newPassword) async {
 | 
			
		||||
    try {
 | 
			
		||||
      await _apiService.userApi.updateUser(
 | 
			
		||||
        UpdateUserDto(
 | 
			
		||||
          id: state.userId,
 | 
			
		||||
      await _apiService.userApi.updateMyUser(
 | 
			
		||||
        UserUpdateMeDto(
 | 
			
		||||
          password: newPassword,
 | 
			
		||||
          shouldChangePassword: false,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@ -178,9 +176,9 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | 
			
		||||
      user = offlineUser;
 | 
			
		||||
      retResult = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      UserResponseDto? userResponseDto;
 | 
			
		||||
      UserAdminResponseDto? userResponseDto;
 | 
			
		||||
      try {
 | 
			
		||||
        userResponseDto = await _apiService.userApi.getMyUserInfo();
 | 
			
		||||
        userResponseDto = await _apiService.userApi.getMyUser();
 | 
			
		||||
      } on ApiException catch (error, stackTrace) {
 | 
			
		||||
        _log.severe(
 | 
			
		||||
          "Error getting user information from the server [API EXCEPTION]",
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ class CurrentUserProvider extends StateNotifier<User?> {
 | 
			
		||||
 | 
			
		||||
  refresh() async {
 | 
			
		||||
    try {
 | 
			
		||||
      final user = await _apiService.userApi.getMyUserInfo();
 | 
			
		||||
      final user = await _apiService.userApi.getMyUser();
 | 
			
		||||
      if (user != null) {
 | 
			
		||||
        Store.put(
 | 
			
		||||
          StoreKey.currentUser,
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,7 @@ class TabNavigationObserver extends AutoRouterObserver {
 | 
			
		||||
      // Update user info
 | 
			
		||||
      try {
 | 
			
		||||
        final userResponseDto =
 | 
			
		||||
            await ref.read(apiServiceProvider).userApi.getMyUserInfo();
 | 
			
		||||
            await ref.read(apiServiceProvider).userApi.getMyUser();
 | 
			
		||||
 | 
			
		||||
        if (userResponseDto == null) {
 | 
			
		||||
          return;
 | 
			
		||||
 | 
			
		||||
@ -37,10 +37,10 @@ class UserService {
 | 
			
		||||
    this._partnerService,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  Future<List<User>?> _getAllUsers({required bool isAll}) async {
 | 
			
		||||
  Future<List<User>?> _getAllUsers() async {
 | 
			
		||||
    try {
 | 
			
		||||
      final dto = await _apiService.userApi.getAllUsers(isAll);
 | 
			
		||||
      return dto?.map(User.fromUserDto).toList();
 | 
			
		||||
      final dto = await _apiService.userApi.searchUsers();
 | 
			
		||||
      return dto?.map(User.fromSimpleUserDto).toList();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      _log.warning("Failed get all users", e);
 | 
			
		||||
      return null;
 | 
			
		||||
@ -71,7 +71,7 @@ class UserService {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<List<User>?> getUsersFromServer() async {
 | 
			
		||||
    final List<User>? users = await _getAllUsers(isAll: true);
 | 
			
		||||
    final List<User>? users = await _getAllUsers();
 | 
			
		||||
    final List<User>? sharedBy =
 | 
			
		||||
        await _partnerService.getPartners(PartnerDirection.sharedBy);
 | 
			
		||||
    final List<User>? sharedWith =
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										26
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										26
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							@ -212,15 +212,18 @@ Class | Method | HTTP request | Description
 | 
			
		||||
*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets | 
 | 
			
		||||
*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore | 
 | 
			
		||||
*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /users/profile-image | 
 | 
			
		||||
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /users | 
 | 
			
		||||
*UserApi* | [**createUserAdmin**](doc//UserApi.md#createuseradmin) | **POST** /admin/users | 
 | 
			
		||||
*UserApi* | [**deleteProfileImage**](doc//UserApi.md#deleteprofileimage) | **DELETE** /users/profile-image | 
 | 
			
		||||
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /users/{id} | 
 | 
			
		||||
*UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /users | 
 | 
			
		||||
*UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /users/me | 
 | 
			
		||||
*UserApi* | [**deleteUserAdmin**](doc//UserApi.md#deleteuseradmin) | **DELETE** /admin/users/{id} | 
 | 
			
		||||
*UserApi* | [**getMyUser**](doc//UserApi.md#getmyuser) | **GET** /users/me | 
 | 
			
		||||
*UserApi* | [**getProfileImage**](doc//UserApi.md#getprofileimage) | **GET** /users/{id}/profile-image | 
 | 
			
		||||
*UserApi* | [**getUserById**](doc//UserApi.md#getuserbyid) | **GET** /users/{id} | 
 | 
			
		||||
*UserApi* | [**restoreUser**](doc//UserApi.md#restoreuser) | **POST** /users/{id}/restore | 
 | 
			
		||||
*UserApi* | [**updateUser**](doc//UserApi.md#updateuser) | **PUT** /users | 
 | 
			
		||||
*UserApi* | [**getUser**](doc//UserApi.md#getuser) | **GET** /users/{id} | 
 | 
			
		||||
*UserApi* | [**getUserAdmin**](doc//UserApi.md#getuseradmin) | **GET** /admin/users/{id} | 
 | 
			
		||||
*UserApi* | [**restoreUserAdmin**](doc//UserApi.md#restoreuseradmin) | **POST** /admin/users/{id}/restore | 
 | 
			
		||||
*UserApi* | [**searchUsers**](doc//UserApi.md#searchusers) | **GET** /users | 
 | 
			
		||||
*UserApi* | [**searchUsersAdmin**](doc//UserApi.md#searchusersadmin) | **GET** /admin/users | 
 | 
			
		||||
*UserApi* | [**updateMyUser**](doc//UserApi.md#updatemyuser) | **PUT** /users/me | 
 | 
			
		||||
*UserApi* | [**updateUserAdmin**](doc//UserApi.md#updateuseradmin) | **PUT** /admin/users/{id} | 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Documentation For Models
 | 
			
		||||
@ -280,8 +283,6 @@ Class | Method | HTTP request | Description
 | 
			
		||||
 - [CreateLibraryDto](doc//CreateLibraryDto.md)
 | 
			
		||||
 - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
 | 
			
		||||
 - [CreateTagDto](doc//CreateTagDto.md)
 | 
			
		||||
 - [CreateUserDto](doc//CreateUserDto.md)
 | 
			
		||||
 - [DeleteUserDto](doc//DeleteUserDto.md)
 | 
			
		||||
 - [DownloadArchiveInfo](doc//DownloadArchiveInfo.md)
 | 
			
		||||
 - [DownloadInfoDto](doc//DownloadInfoDto.md)
 | 
			
		||||
 - [DownloadResponseDto](doc//DownloadResponseDto.md)
 | 
			
		||||
@ -402,12 +403,15 @@ Class | Method | HTTP request | Description
 | 
			
		||||
 - [UpdatePartnerDto](doc//UpdatePartnerDto.md)
 | 
			
		||||
 - [UpdateStackParentDto](doc//UpdateStackParentDto.md)
 | 
			
		||||
 - [UpdateTagDto](doc//UpdateTagDto.md)
 | 
			
		||||
 - [UpdateUserDto](doc//UpdateUserDto.md)
 | 
			
		||||
 - [UsageByUserDto](doc//UsageByUserDto.md)
 | 
			
		||||
 - [UserAdminCreateDto](doc//UserAdminCreateDto.md)
 | 
			
		||||
 - [UserAdminDeleteDto](doc//UserAdminDeleteDto.md)
 | 
			
		||||
 - [UserAdminResponseDto](doc//UserAdminResponseDto.md)
 | 
			
		||||
 - [UserAdminUpdateDto](doc//UserAdminUpdateDto.md)
 | 
			
		||||
 - [UserAvatarColor](doc//UserAvatarColor.md)
 | 
			
		||||
 - [UserDto](doc//UserDto.md)
 | 
			
		||||
 - [UserResponseDto](doc//UserResponseDto.md)
 | 
			
		||||
 - [UserStatus](doc//UserStatus.md)
 | 
			
		||||
 - [UserUpdateMeDto](doc//UserUpdateMeDto.md)
 | 
			
		||||
 - [ValidateAccessTokenResponseDto](doc//ValidateAccessTokenResponseDto.md)
 | 
			
		||||
 - [ValidateLibraryDto](doc//ValidateLibraryDto.md)
 | 
			
		||||
 - [ValidateLibraryImportPathResponseDto](doc//ValidateLibraryImportPathResponseDto.md)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							@ -112,8 +112,6 @@ part 'model/create_album_dto.dart';
 | 
			
		||||
part 'model/create_library_dto.dart';
 | 
			
		||||
part 'model/create_profile_image_response_dto.dart';
 | 
			
		||||
part 'model/create_tag_dto.dart';
 | 
			
		||||
part 'model/create_user_dto.dart';
 | 
			
		||||
part 'model/delete_user_dto.dart';
 | 
			
		||||
part 'model/download_archive_info.dart';
 | 
			
		||||
part 'model/download_info_dto.dart';
 | 
			
		||||
part 'model/download_response_dto.dart';
 | 
			
		||||
@ -234,12 +232,15 @@ part 'model/update_library_dto.dart';
 | 
			
		||||
part 'model/update_partner_dto.dart';
 | 
			
		||||
part 'model/update_stack_parent_dto.dart';
 | 
			
		||||
part 'model/update_tag_dto.dart';
 | 
			
		||||
part 'model/update_user_dto.dart';
 | 
			
		||||
part 'model/usage_by_user_dto.dart';
 | 
			
		||||
part 'model/user_admin_create_dto.dart';
 | 
			
		||||
part 'model/user_admin_delete_dto.dart';
 | 
			
		||||
part 'model/user_admin_response_dto.dart';
 | 
			
		||||
part 'model/user_admin_update_dto.dart';
 | 
			
		||||
part 'model/user_avatar_color.dart';
 | 
			
		||||
part 'model/user_dto.dart';
 | 
			
		||||
part 'model/user_response_dto.dart';
 | 
			
		||||
part 'model/user_status.dart';
 | 
			
		||||
part 'model/user_update_me_dto.dart';
 | 
			
		||||
part 'model/validate_access_token_response_dto.dart';
 | 
			
		||||
part 'model/validate_library_dto.dart';
 | 
			
		||||
part 'model/validate_library_import_path_response_dto.dart';
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								mobile/openapi/lib/api/authentication_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								mobile/openapi/lib/api/authentication_api.dart
									
									
									
										generated
									
									
									
								
							@ -48,7 +48,7 @@ class AuthenticationApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [ChangePasswordDto] changePasswordDto (required):
 | 
			
		||||
  Future<UserResponseDto?> changePassword(ChangePasswordDto changePasswordDto,) async {
 | 
			
		||||
  Future<UserAdminResponseDto?> changePassword(ChangePasswordDto changePasswordDto,) async {
 | 
			
		||||
    final response = await changePasswordWithHttpInfo(changePasswordDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
@ -57,7 +57,7 @@ class AuthenticationApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@ -183,7 +183,7 @@ class AuthenticationApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [SignUpDto] signUpDto (required):
 | 
			
		||||
  Future<UserResponseDto?> signUpAdmin(SignUpDto signUpDto,) async {
 | 
			
		||||
  Future<UserAdminResponseDto?> signUpAdmin(SignUpDto signUpDto,) async {
 | 
			
		||||
    final response = await signUpAdminWithHttpInfo(signUpDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
@ -192,7 +192,7 @@ class AuthenticationApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								mobile/openapi/lib/api/o_auth_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								mobile/openapi/lib/api/o_auth_api.dart
									
									
									
										generated
									
									
									
								
							@ -95,7 +95,7 @@ class OAuthApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [OAuthCallbackDto] oAuthCallbackDto (required):
 | 
			
		||||
  Future<UserResponseDto?> linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async {
 | 
			
		||||
  Future<UserAdminResponseDto?> linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async {
 | 
			
		||||
    final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
@ -104,7 +104,7 @@ class OAuthApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@ -216,7 +216,7 @@ class OAuthApi {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<UserResponseDto?> unlinkOAuthAccount() async {
 | 
			
		||||
  Future<UserAdminResponseDto?> unlinkOAuthAccount() async {
 | 
			
		||||
    final response = await unlinkOAuthAccountWithHttpInfo();
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
@ -225,7 +225,7 @@ class OAuthApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										332
									
								
								mobile/openapi/lib/api/user_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										332
									
								
								mobile/openapi/lib/api/user_api.dart
									
									
									
										generated
									
									
									
								
							@ -73,16 +73,16 @@ class UserApi {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'POST /users' operation and returns the [Response].
 | 
			
		||||
  /// Performs an HTTP 'POST /admin/users' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [CreateUserDto] createUserDto (required):
 | 
			
		||||
  Future<Response> createUserWithHttpInfo(CreateUserDto createUserDto,) async {
 | 
			
		||||
  /// * [UserAdminCreateDto] userAdminCreateDto (required):
 | 
			
		||||
  Future<Response> createUserAdminWithHttpInfo(UserAdminCreateDto userAdminCreateDto,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users';
 | 
			
		||||
    final path = r'/admin/users';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody = createUserDto;
 | 
			
		||||
    Object? postBody = userAdminCreateDto;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
@ -104,9 +104,9 @@ class UserApi {
 | 
			
		||||
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [CreateUserDto] createUserDto (required):
 | 
			
		||||
  Future<UserResponseDto?> createUser(CreateUserDto createUserDto,) async {
 | 
			
		||||
    final response = await createUserWithHttpInfo(createUserDto,);
 | 
			
		||||
  /// * [UserAdminCreateDto] userAdminCreateDto (required):
 | 
			
		||||
  Future<UserAdminResponseDto?> createUserAdmin(UserAdminCreateDto userAdminCreateDto,) async {
 | 
			
		||||
    final response = await createUserAdminWithHttpInfo(userAdminCreateDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
@ -114,7 +114,7 @@ class UserApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@ -153,19 +153,19 @@ class UserApi {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'DELETE /users/{id}' operation and returns the [Response].
 | 
			
		||||
  /// Performs an HTTP 'DELETE /admin/users/{id}' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [DeleteUserDto] deleteUserDto (required):
 | 
			
		||||
  Future<Response> deleteUserWithHttpInfo(String id, DeleteUserDto deleteUserDto,) async {
 | 
			
		||||
  /// * [UserAdminDeleteDto] userAdminDeleteDto (required):
 | 
			
		||||
  Future<Response> deleteUserAdminWithHttpInfo(String id, UserAdminDeleteDto userAdminDeleteDto,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users/{id}'
 | 
			
		||||
    final path = r'/admin/users/{id}'
 | 
			
		||||
      .replaceAll('{id}', id);
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody = deleteUserDto;
 | 
			
		||||
    Object? postBody = userAdminDeleteDto;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
@ -189,9 +189,9 @@ class UserApi {
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [DeleteUserDto] deleteUserDto (required):
 | 
			
		||||
  Future<UserResponseDto?> deleteUser(String id, DeleteUserDto deleteUserDto,) async {
 | 
			
		||||
    final response = await deleteUserWithHttpInfo(id, deleteUserDto,);
 | 
			
		||||
  /// * [UserAdminDeleteDto] userAdminDeleteDto (required):
 | 
			
		||||
  Future<UserAdminResponseDto?> deleteUserAdmin(String id, UserAdminDeleteDto userAdminDeleteDto,) async {
 | 
			
		||||
    final response = await deleteUserAdminWithHttpInfo(id, userAdminDeleteDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
@ -199,66 +199,14 @@ class UserApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'GET /users' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] isAll (required):
 | 
			
		||||
  Future<Response> getAllUsersWithHttpInfo(bool isAll,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
      queryParams.addAll(_queryParams('', 'isAll', isAll));
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return apiClient.invokeAPI(
 | 
			
		||||
      path,
 | 
			
		||||
      'GET',
 | 
			
		||||
      queryParams,
 | 
			
		||||
      postBody,
 | 
			
		||||
      headerParams,
 | 
			
		||||
      formParams,
 | 
			
		||||
      contentTypes.isEmpty ? null : contentTypes.first,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] isAll (required):
 | 
			
		||||
  Future<List<UserResponseDto>?> getAllUsers(bool isAll,) async {
 | 
			
		||||
    final response = await getAllUsersWithHttpInfo(isAll,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
    // When a remote server returns no body with a status of 204, we shall not decode it.
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      final responseBody = await _decodeBodyBytes(response);
 | 
			
		||||
      return (await apiClient.deserializeAsync(responseBody, 'List<UserResponseDto>') as List)
 | 
			
		||||
        .cast<UserResponseDto>()
 | 
			
		||||
        .toList(growable: false);
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'GET /users/me' operation and returns the [Response].
 | 
			
		||||
  Future<Response> getMyUserInfoWithHttpInfo() async {
 | 
			
		||||
  Future<Response> getMyUserWithHttpInfo() async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users/me';
 | 
			
		||||
 | 
			
		||||
@ -283,8 +231,8 @@ class UserApi {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<UserResponseDto?> getMyUserInfo() async {
 | 
			
		||||
    final response = await getMyUserInfoWithHttpInfo();
 | 
			
		||||
  Future<UserAdminResponseDto?> getMyUser() async {
 | 
			
		||||
    final response = await getMyUserWithHttpInfo();
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
@ -292,7 +240,7 @@ class UserApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@ -350,7 +298,7 @@ class UserApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<Response> getUserByIdWithHttpInfo(String id,) async {
 | 
			
		||||
  Future<Response> getUserWithHttpInfo(String id,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users/{id}'
 | 
			
		||||
      .replaceAll('{id}', id);
 | 
			
		||||
@ -379,8 +327,8 @@ class UserApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<UserResponseDto?> getUserById(String id,) async {
 | 
			
		||||
    final response = await getUserByIdWithHttpInfo(id,);
 | 
			
		||||
  Future<UserResponseDto?> getUser(String id,) async {
 | 
			
		||||
    final response = await getUserWithHttpInfo(id,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
@ -394,13 +342,61 @@ class UserApi {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'POST /users/{id}/restore' operation and returns the [Response].
 | 
			
		||||
  /// Performs an HTTP 'GET /admin/users/{id}' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<Response> restoreUserWithHttpInfo(String id,) async {
 | 
			
		||||
  Future<Response> getUserAdminWithHttpInfo(String id,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users/{id}/restore'
 | 
			
		||||
    final path = r'/admin/users/{id}'
 | 
			
		||||
      .replaceAll('{id}', id);
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return apiClient.invokeAPI(
 | 
			
		||||
      path,
 | 
			
		||||
      'GET',
 | 
			
		||||
      queryParams,
 | 
			
		||||
      postBody,
 | 
			
		||||
      headerParams,
 | 
			
		||||
      formParams,
 | 
			
		||||
      contentTypes.isEmpty ? null : contentTypes.first,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<UserAdminResponseDto?> getUserAdmin(String id,) async {
 | 
			
		||||
    final response = await getUserAdminWithHttpInfo(id,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
    // When a remote server returns no body with a status of 204, we shall not decode it.
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'POST /admin/users/{id}/restore' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<Response> restoreUserAdminWithHttpInfo(String id,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/admin/users/{id}/restore'
 | 
			
		||||
      .replaceAll('{id}', id);
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
@ -427,8 +423,8 @@ class UserApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  Future<UserResponseDto?> restoreUser(String id,) async {
 | 
			
		||||
    final response = await restoreUserWithHttpInfo(id,);
 | 
			
		||||
  Future<UserAdminResponseDto?> restoreUserAdmin(String id,) async {
 | 
			
		||||
    final response = await restoreUserAdminWithHttpInfo(id,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
@ -436,22 +432,120 @@ class UserApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'PUT /users' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [UpdateUserDto] updateUserDto (required):
 | 
			
		||||
  Future<Response> updateUserWithHttpInfo(UpdateUserDto updateUserDto,) async {
 | 
			
		||||
  /// Performs an HTTP 'GET /users' operation and returns the [Response].
 | 
			
		||||
  Future<Response> searchUsersWithHttpInfo() async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody = updateUserDto;
 | 
			
		||||
    Object? postBody;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return apiClient.invokeAPI(
 | 
			
		||||
      path,
 | 
			
		||||
      'GET',
 | 
			
		||||
      queryParams,
 | 
			
		||||
      postBody,
 | 
			
		||||
      headerParams,
 | 
			
		||||
      formParams,
 | 
			
		||||
      contentTypes.isEmpty ? null : contentTypes.first,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<List<UserResponseDto>?> searchUsers() async {
 | 
			
		||||
    final response = await searchUsersWithHttpInfo();
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
    // When a remote server returns no body with a status of 204, we shall not decode it.
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      final responseBody = await _decodeBodyBytes(response);
 | 
			
		||||
      return (await apiClient.deserializeAsync(responseBody, 'List<UserResponseDto>') as List)
 | 
			
		||||
        .cast<UserResponseDto>()
 | 
			
		||||
        .toList(growable: false);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'GET /admin/users' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] withDeleted:
 | 
			
		||||
  Future<Response> searchUsersAdminWithHttpInfo({ bool? withDeleted, }) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/admin/users';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
    if (withDeleted != null) {
 | 
			
		||||
      queryParams.addAll(_queryParams('', 'withDeleted', withDeleted));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return apiClient.invokeAPI(
 | 
			
		||||
      path,
 | 
			
		||||
      'GET',
 | 
			
		||||
      queryParams,
 | 
			
		||||
      postBody,
 | 
			
		||||
      headerParams,
 | 
			
		||||
      formParams,
 | 
			
		||||
      contentTypes.isEmpty ? null : contentTypes.first,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] withDeleted:
 | 
			
		||||
  Future<List<UserAdminResponseDto>?> searchUsersAdmin({ bool? withDeleted, }) async {
 | 
			
		||||
    final response = await searchUsersAdminWithHttpInfo( withDeleted: withDeleted, );
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
    // When a remote server returns no body with a status of 204, we shall not decode it.
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      final responseBody = await _decodeBodyBytes(response);
 | 
			
		||||
      return (await apiClient.deserializeAsync(responseBody, 'List<UserAdminResponseDto>') as List)
 | 
			
		||||
        .cast<UserAdminResponseDto>()
 | 
			
		||||
        .toList(growable: false);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'PUT /users/me' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [UserUpdateMeDto] userUpdateMeDto (required):
 | 
			
		||||
  Future<Response> updateMyUserWithHttpInfo(UserUpdateMeDto userUpdateMeDto,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/users/me';
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody = userUpdateMeDto;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
@ -473,9 +567,9 @@ class UserApi {
 | 
			
		||||
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [UpdateUserDto] updateUserDto (required):
 | 
			
		||||
  Future<UserResponseDto?> updateUser(UpdateUserDto updateUserDto,) async {
 | 
			
		||||
    final response = await updateUserWithHttpInfo(updateUserDto,);
 | 
			
		||||
  /// * [UserUpdateMeDto] userUpdateMeDto (required):
 | 
			
		||||
  Future<UserAdminResponseDto?> updateMyUser(UserUpdateMeDto userUpdateMeDto,) async {
 | 
			
		||||
    final response = await updateMyUserWithHttpInfo(userUpdateMeDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
@ -483,7 +577,59 @@ class UserApi {
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Performs an HTTP 'PUT /admin/users/{id}' operation and returns the [Response].
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [UserAdminUpdateDto] userAdminUpdateDto (required):
 | 
			
		||||
  Future<Response> updateUserAdminWithHttpInfo(String id, UserAdminUpdateDto userAdminUpdateDto,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/admin/users/{id}'
 | 
			
		||||
      .replaceAll('{id}', id);
 | 
			
		||||
 | 
			
		||||
    // ignore: prefer_final_locals
 | 
			
		||||
    Object? postBody = userAdminUpdateDto;
 | 
			
		||||
 | 
			
		||||
    final queryParams = <QueryParam>[];
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>['application/json'];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return apiClient.invokeAPI(
 | 
			
		||||
      path,
 | 
			
		||||
      'PUT',
 | 
			
		||||
      queryParams,
 | 
			
		||||
      postBody,
 | 
			
		||||
      headerParams,
 | 
			
		||||
      formParams,
 | 
			
		||||
      contentTypes.isEmpty ? null : contentTypes.first,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] id (required):
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [UserAdminUpdateDto] userAdminUpdateDto (required):
 | 
			
		||||
  Future<UserAdminResponseDto?> updateUserAdmin(String id, UserAdminUpdateDto userAdminUpdateDto,) async {
 | 
			
		||||
    final response = await updateUserAdminWithHttpInfo(id, userAdminUpdateDto,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
    // When a remote server returns no body with a status of 204, we shall not decode it.
 | 
			
		||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
			
		||||
    // FormatException when trying to decode an empty string.
 | 
			
		||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
			
		||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto;
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							@ -292,10 +292,6 @@ class ApiClient {
 | 
			
		||||
          return CreateProfileImageResponseDto.fromJson(value);
 | 
			
		||||
        case 'CreateTagDto':
 | 
			
		||||
          return CreateTagDto.fromJson(value);
 | 
			
		||||
        case 'CreateUserDto':
 | 
			
		||||
          return CreateUserDto.fromJson(value);
 | 
			
		||||
        case 'DeleteUserDto':
 | 
			
		||||
          return DeleteUserDto.fromJson(value);
 | 
			
		||||
        case 'DownloadArchiveInfo':
 | 
			
		||||
          return DownloadArchiveInfo.fromJson(value);
 | 
			
		||||
        case 'DownloadInfoDto':
 | 
			
		||||
@ -536,18 +532,24 @@ class ApiClient {
 | 
			
		||||
          return UpdateStackParentDto.fromJson(value);
 | 
			
		||||
        case 'UpdateTagDto':
 | 
			
		||||
          return UpdateTagDto.fromJson(value);
 | 
			
		||||
        case 'UpdateUserDto':
 | 
			
		||||
          return UpdateUserDto.fromJson(value);
 | 
			
		||||
        case 'UsageByUserDto':
 | 
			
		||||
          return UsageByUserDto.fromJson(value);
 | 
			
		||||
        case 'UserAdminCreateDto':
 | 
			
		||||
          return UserAdminCreateDto.fromJson(value);
 | 
			
		||||
        case 'UserAdminDeleteDto':
 | 
			
		||||
          return UserAdminDeleteDto.fromJson(value);
 | 
			
		||||
        case 'UserAdminResponseDto':
 | 
			
		||||
          return UserAdminResponseDto.fromJson(value);
 | 
			
		||||
        case 'UserAdminUpdateDto':
 | 
			
		||||
          return UserAdminUpdateDto.fromJson(value);
 | 
			
		||||
        case 'UserAvatarColor':
 | 
			
		||||
          return UserAvatarColorTypeTransformer().decode(value);
 | 
			
		||||
        case 'UserDto':
 | 
			
		||||
          return UserDto.fromJson(value);
 | 
			
		||||
        case 'UserResponseDto':
 | 
			
		||||
          return UserResponseDto.fromJson(value);
 | 
			
		||||
        case 'UserStatus':
 | 
			
		||||
          return UserStatusTypeTransformer().decode(value);
 | 
			
		||||
        case 'UserUpdateMeDto':
 | 
			
		||||
          return UserUpdateMeDto.fromJson(value);
 | 
			
		||||
        case 'ValidateAccessTokenResponseDto':
 | 
			
		||||
          return ValidateAccessTokenResponseDto.fromJson(value);
 | 
			
		||||
        case 'ValidateLibraryDto':
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ class ActivityResponseDto {
 | 
			
		||||
 | 
			
		||||
  ActivityResponseDtoTypeEnum type;
 | 
			
		||||
 | 
			
		||||
  UserDto user;
 | 
			
		||||
  UserResponseDto user;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is ActivityResponseDto &&
 | 
			
		||||
@ -87,7 +87,7 @@ class ActivityResponseDto {
 | 
			
		||||
        createdAt: mapDateTime(json, r'createdAt', r'')!,
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        type: ActivityResponseDtoTypeEnum.fromJson(json[r'type'])!,
 | 
			
		||||
        user: UserDto.fromJson(json[r'user'])!,
 | 
			
		||||
        user: UserResponseDto.fromJson(json[r'user'])!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								mobile/openapi/lib/model/partner_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										119
									
								
								mobile/openapi/lib/model/partner_response_dto.dart
									
									
									
										generated
									
									
									
								
							@ -14,30 +14,15 @@ class PartnerResponseDto {
 | 
			
		||||
  /// Returns a new [PartnerResponseDto] instance.
 | 
			
		||||
  PartnerResponseDto({
 | 
			
		||||
    required this.avatarColor,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.deletedAt,
 | 
			
		||||
    required this.email,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    this.inTimeline,
 | 
			
		||||
    required this.isAdmin,
 | 
			
		||||
    this.memoriesEnabled,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.oauthId,
 | 
			
		||||
    required this.profileImagePath,
 | 
			
		||||
    required this.quotaSizeInBytes,
 | 
			
		||||
    required this.quotaUsageInBytes,
 | 
			
		||||
    required this.shouldChangePassword,
 | 
			
		||||
    required this.status,
 | 
			
		||||
    required this.storageLabel,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  UserAvatarColor avatarColor;
 | 
			
		||||
 | 
			
		||||
  DateTime createdAt;
 | 
			
		||||
 | 
			
		||||
  DateTime? deletedAt;
 | 
			
		||||
 | 
			
		||||
  String email;
 | 
			
		||||
 | 
			
		||||
  String id;
 | 
			
		||||
@ -50,121 +35,44 @@ class PartnerResponseDto {
 | 
			
		||||
  ///
 | 
			
		||||
  bool? inTimeline;
 | 
			
		||||
 | 
			
		||||
  bool isAdmin;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  bool? memoriesEnabled;
 | 
			
		||||
 | 
			
		||||
  String name;
 | 
			
		||||
 | 
			
		||||
  String oauthId;
 | 
			
		||||
 | 
			
		||||
  String profileImagePath;
 | 
			
		||||
 | 
			
		||||
  int? quotaSizeInBytes;
 | 
			
		||||
 | 
			
		||||
  int? quotaUsageInBytes;
 | 
			
		||||
 | 
			
		||||
  bool shouldChangePassword;
 | 
			
		||||
 | 
			
		||||
  UserStatus status;
 | 
			
		||||
 | 
			
		||||
  String? storageLabel;
 | 
			
		||||
 | 
			
		||||
  DateTime updatedAt;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is PartnerResponseDto &&
 | 
			
		||||
    other.avatarColor == avatarColor &&
 | 
			
		||||
    other.createdAt == createdAt &&
 | 
			
		||||
    other.deletedAt == deletedAt &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.id == id &&
 | 
			
		||||
    other.inTimeline == inTimeline &&
 | 
			
		||||
    other.isAdmin == isAdmin &&
 | 
			
		||||
    other.memoriesEnabled == memoriesEnabled &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
    other.oauthId == oauthId &&
 | 
			
		||||
    other.profileImagePath == profileImagePath &&
 | 
			
		||||
    other.quotaSizeInBytes == quotaSizeInBytes &&
 | 
			
		||||
    other.quotaUsageInBytes == quotaUsageInBytes &&
 | 
			
		||||
    other.shouldChangePassword == shouldChangePassword &&
 | 
			
		||||
    other.status == status &&
 | 
			
		||||
    other.storageLabel == storageLabel &&
 | 
			
		||||
    other.updatedAt == updatedAt;
 | 
			
		||||
    other.profileImagePath == profileImagePath;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (avatarColor.hashCode) +
 | 
			
		||||
    (createdAt.hashCode) +
 | 
			
		||||
    (deletedAt == null ? 0 : deletedAt!.hashCode) +
 | 
			
		||||
    (email.hashCode) +
 | 
			
		||||
    (id.hashCode) +
 | 
			
		||||
    (inTimeline == null ? 0 : inTimeline!.hashCode) +
 | 
			
		||||
    (isAdmin.hashCode) +
 | 
			
		||||
    (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) +
 | 
			
		||||
    (name.hashCode) +
 | 
			
		||||
    (oauthId.hashCode) +
 | 
			
		||||
    (profileImagePath.hashCode) +
 | 
			
		||||
    (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) +
 | 
			
		||||
    (quotaUsageInBytes == null ? 0 : quotaUsageInBytes!.hashCode) +
 | 
			
		||||
    (shouldChangePassword.hashCode) +
 | 
			
		||||
    (status.hashCode) +
 | 
			
		||||
    (storageLabel == null ? 0 : storageLabel!.hashCode) +
 | 
			
		||||
    (updatedAt.hashCode);
 | 
			
		||||
    (profileImagePath.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'PartnerResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, quotaSizeInBytes=$quotaSizeInBytes, quotaUsageInBytes=$quotaUsageInBytes, shouldChangePassword=$shouldChangePassword, status=$status, storageLabel=$storageLabel, updatedAt=$updatedAt]';
 | 
			
		||||
  String toString() => 'PartnerResponseDto[avatarColor=$avatarColor, email=$email, id=$id, inTimeline=$inTimeline, name=$name, profileImagePath=$profileImagePath]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
      json[r'avatarColor'] = this.avatarColor;
 | 
			
		||||
      json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
 | 
			
		||||
    if (this.deletedAt != null) {
 | 
			
		||||
      json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String();
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'deletedAt'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'email'] = this.email;
 | 
			
		||||
      json[r'id'] = this.id;
 | 
			
		||||
    if (this.inTimeline != null) {
 | 
			
		||||
      json[r'inTimeline'] = this.inTimeline;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'inTimeline'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'isAdmin'] = this.isAdmin;
 | 
			
		||||
    if (this.memoriesEnabled != null) {
 | 
			
		||||
      json[r'memoriesEnabled'] = this.memoriesEnabled;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'memoriesEnabled'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'name'] = this.name;
 | 
			
		||||
      json[r'oauthId'] = this.oauthId;
 | 
			
		||||
      json[r'profileImagePath'] = this.profileImagePath;
 | 
			
		||||
    if (this.quotaSizeInBytes != null) {
 | 
			
		||||
      json[r'quotaSizeInBytes'] = this.quotaSizeInBytes;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'quotaSizeInBytes'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.quotaUsageInBytes != null) {
 | 
			
		||||
      json[r'quotaUsageInBytes'] = this.quotaUsageInBytes;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'quotaUsageInBytes'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'shouldChangePassword'] = this.shouldChangePassword;
 | 
			
		||||
      json[r'status'] = this.status;
 | 
			
		||||
    if (this.storageLabel != null) {
 | 
			
		||||
      json[r'storageLabel'] = this.storageLabel;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'storageLabel'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -177,22 +85,11 @@ class PartnerResponseDto {
 | 
			
		||||
 | 
			
		||||
      return PartnerResponseDto(
 | 
			
		||||
        avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!,
 | 
			
		||||
        createdAt: mapDateTime(json, r'createdAt', r'')!,
 | 
			
		||||
        deletedAt: mapDateTime(json, r'deletedAt', r''),
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email')!,
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        inTimeline: mapValueOfType<bool>(json, r'inTimeline'),
 | 
			
		||||
        isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
 | 
			
		||||
        memoriesEnabled: mapValueOfType<bool>(json, r'memoriesEnabled'),
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name')!,
 | 
			
		||||
        oauthId: mapValueOfType<String>(json, r'oauthId')!,
 | 
			
		||||
        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
 | 
			
		||||
        quotaSizeInBytes: mapValueOfType<int>(json, r'quotaSizeInBytes'),
 | 
			
		||||
        quotaUsageInBytes: mapValueOfType<int>(json, r'quotaUsageInBytes'),
 | 
			
		||||
        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
 | 
			
		||||
        status: UserStatus.fromJson(json[r'status'])!,
 | 
			
		||||
        storageLabel: mapValueOfType<String>(json, r'storageLabel'),
 | 
			
		||||
        updatedAt: mapDateTime(json, r'updatedAt', r'')!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@ -241,20 +138,10 @@ class PartnerResponseDto {
 | 
			
		||||
  /// The list of required keys that must be present in a JSON.
 | 
			
		||||
  static const requiredKeys = <String>{
 | 
			
		||||
    'avatarColor',
 | 
			
		||||
    'createdAt',
 | 
			
		||||
    'deletedAt',
 | 
			
		||||
    'email',
 | 
			
		||||
    'id',
 | 
			
		||||
    'isAdmin',
 | 
			
		||||
    'name',
 | 
			
		||||
    'oauthId',
 | 
			
		||||
    'profileImagePath',
 | 
			
		||||
    'quotaSizeInBytes',
 | 
			
		||||
    'quotaUsageInBytes',
 | 
			
		||||
    'shouldChangePassword',
 | 
			
		||||
    'status',
 | 
			
		||||
    'storageLabel',
 | 
			
		||||
    'updatedAt',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,9 @@
 | 
			
		||||
 | 
			
		||||
part of openapi.api;
 | 
			
		||||
 | 
			
		||||
class CreateUserDto {
 | 
			
		||||
  /// Returns a new [CreateUserDto] instance.
 | 
			
		||||
  CreateUserDto({
 | 
			
		||||
class UserAdminCreateDto {
 | 
			
		||||
  /// Returns a new [UserAdminCreateDto] instance.
 | 
			
		||||
  UserAdminCreateDto({
 | 
			
		||||
    required this.email,
 | 
			
		||||
    this.memoriesEnabled,
 | 
			
		||||
    required this.name,
 | 
			
		||||
@ -59,7 +59,7 @@ class CreateUserDto {
 | 
			
		||||
  String? storageLabel;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is CreateUserDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserAdminCreateDto &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.memoriesEnabled == memoriesEnabled &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
@ -82,7 +82,7 @@ class CreateUserDto {
 | 
			
		||||
    (storageLabel == null ? 0 : storageLabel!.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'CreateUserDto[email=$email, memoriesEnabled=$memoriesEnabled, name=$name, notify=$notify, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]';
 | 
			
		||||
  String toString() => 'UserAdminCreateDto[email=$email, memoriesEnabled=$memoriesEnabled, name=$name, notify=$notify, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@ -117,14 +117,14 @@ class CreateUserDto {
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a new [CreateUserDto] instance and imports its values from
 | 
			
		||||
  /// Returns a new [UserAdminCreateDto] instance and imports its values from
 | 
			
		||||
  /// [value] if it's a [Map], null otherwise.
 | 
			
		||||
  // ignore: prefer_constructors_over_static_methods
 | 
			
		||||
  static CreateUserDto? fromJson(dynamic value) {
 | 
			
		||||
  static UserAdminCreateDto? fromJson(dynamic value) {
 | 
			
		||||
    if (value is Map) {
 | 
			
		||||
      final json = value.cast<String, dynamic>();
 | 
			
		||||
 | 
			
		||||
      return CreateUserDto(
 | 
			
		||||
      return UserAdminCreateDto(
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email')!,
 | 
			
		||||
        memoriesEnabled: mapValueOfType<bool>(json, r'memoriesEnabled'),
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name')!,
 | 
			
		||||
@ -138,11 +138,11 @@ class CreateUserDto {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<CreateUserDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <CreateUserDto>[];
 | 
			
		||||
  static List<UserAdminCreateDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UserAdminCreateDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
        final value = CreateUserDto.fromJson(row);
 | 
			
		||||
        final value = UserAdminCreateDto.fromJson(row);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          result.add(value);
 | 
			
		||||
        }
 | 
			
		||||
@ -151,12 +151,12 @@ class CreateUserDto {
 | 
			
		||||
    return result.toList(growable: growable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Map<String, CreateUserDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, CreateUserDto>{};
 | 
			
		||||
  static Map<String, UserAdminCreateDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UserAdminCreateDto>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = CreateUserDto.fromJson(entry.value);
 | 
			
		||||
        final value = UserAdminCreateDto.fromJson(entry.value);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
@ -165,14 +165,14 @@ class CreateUserDto {
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of CreateUserDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<CreateUserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<CreateUserDto>>{};
 | 
			
		||||
  // maps a json object with a list of UserAdminCreateDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UserAdminCreateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UserAdminCreateDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      // ignore: parameter_assignments
 | 
			
		||||
      json = json.cast<String, dynamic>();
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        map[entry.key] = CreateUserDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
        map[entry.key] = UserAdminCreateDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
@ -10,9 +10,9 @@
 | 
			
		||||
 | 
			
		||||
part of openapi.api;
 | 
			
		||||
 | 
			
		||||
class DeleteUserDto {
 | 
			
		||||
  /// Returns a new [DeleteUserDto] instance.
 | 
			
		||||
  DeleteUserDto({
 | 
			
		||||
class UserAdminDeleteDto {
 | 
			
		||||
  /// Returns a new [UserAdminDeleteDto] instance.
 | 
			
		||||
  UserAdminDeleteDto({
 | 
			
		||||
    this.force,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ class DeleteUserDto {
 | 
			
		||||
  bool? force;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is DeleteUserDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserAdminDeleteDto &&
 | 
			
		||||
    other.force == force;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@ -34,7 +34,7 @@ class DeleteUserDto {
 | 
			
		||||
    (force == null ? 0 : force!.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'DeleteUserDto[force=$force]';
 | 
			
		||||
  String toString() => 'UserAdminDeleteDto[force=$force]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@ -46,25 +46,25 @@ class DeleteUserDto {
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a new [DeleteUserDto] instance and imports its values from
 | 
			
		||||
  /// Returns a new [UserAdminDeleteDto] instance and imports its values from
 | 
			
		||||
  /// [value] if it's a [Map], null otherwise.
 | 
			
		||||
  // ignore: prefer_constructors_over_static_methods
 | 
			
		||||
  static DeleteUserDto? fromJson(dynamic value) {
 | 
			
		||||
  static UserAdminDeleteDto? fromJson(dynamic value) {
 | 
			
		||||
    if (value is Map) {
 | 
			
		||||
      final json = value.cast<String, dynamic>();
 | 
			
		||||
 | 
			
		||||
      return DeleteUserDto(
 | 
			
		||||
      return UserAdminDeleteDto(
 | 
			
		||||
        force: mapValueOfType<bool>(json, r'force'),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<DeleteUserDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <DeleteUserDto>[];
 | 
			
		||||
  static List<UserAdminDeleteDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UserAdminDeleteDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
        final value = DeleteUserDto.fromJson(row);
 | 
			
		||||
        final value = UserAdminDeleteDto.fromJson(row);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          result.add(value);
 | 
			
		||||
        }
 | 
			
		||||
@ -73,12 +73,12 @@ class DeleteUserDto {
 | 
			
		||||
    return result.toList(growable: growable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Map<String, DeleteUserDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, DeleteUserDto>{};
 | 
			
		||||
  static Map<String, UserAdminDeleteDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UserAdminDeleteDto>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = DeleteUserDto.fromJson(entry.value);
 | 
			
		||||
        final value = UserAdminDeleteDto.fromJson(entry.value);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
@ -87,14 +87,14 @@ class DeleteUserDto {
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of DeleteUserDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<DeleteUserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<DeleteUserDto>>{};
 | 
			
		||||
  // maps a json object with a list of UserAdminDeleteDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UserAdminDeleteDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UserAdminDeleteDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      // ignore: parameter_assignments
 | 
			
		||||
      json = json.cast<String, dynamic>();
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        map[entry.key] = DeleteUserDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
        map[entry.key] = UserAdminDeleteDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
							
								
								
									
										243
									
								
								mobile/openapi/lib/model/user_admin_response_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								mobile/openapi/lib/model/user_admin_response_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,243 @@
 | 
			
		||||
//
 | 
			
		||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
 | 
			
		||||
//
 | 
			
		||||
// @dart=2.18
 | 
			
		||||
 | 
			
		||||
// ignore_for_file: unused_element, unused_import
 | 
			
		||||
// ignore_for_file: always_put_required_named_parameters_first
 | 
			
		||||
// ignore_for_file: constant_identifier_names
 | 
			
		||||
// ignore_for_file: lines_longer_than_80_chars
 | 
			
		||||
 | 
			
		||||
part of openapi.api;
 | 
			
		||||
 | 
			
		||||
class UserAdminResponseDto {
 | 
			
		||||
  /// Returns a new [UserAdminResponseDto] instance.
 | 
			
		||||
  UserAdminResponseDto({
 | 
			
		||||
    required this.avatarColor,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.deletedAt,
 | 
			
		||||
    required this.email,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.isAdmin,
 | 
			
		||||
    this.memoriesEnabled,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.oauthId,
 | 
			
		||||
    required this.profileImagePath,
 | 
			
		||||
    required this.quotaSizeInBytes,
 | 
			
		||||
    required this.quotaUsageInBytes,
 | 
			
		||||
    required this.shouldChangePassword,
 | 
			
		||||
    required this.status,
 | 
			
		||||
    required this.storageLabel,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  UserAvatarColor avatarColor;
 | 
			
		||||
 | 
			
		||||
  DateTime createdAt;
 | 
			
		||||
 | 
			
		||||
  DateTime? deletedAt;
 | 
			
		||||
 | 
			
		||||
  String email;
 | 
			
		||||
 | 
			
		||||
  String id;
 | 
			
		||||
 | 
			
		||||
  bool isAdmin;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  bool? memoriesEnabled;
 | 
			
		||||
 | 
			
		||||
  String name;
 | 
			
		||||
 | 
			
		||||
  String oauthId;
 | 
			
		||||
 | 
			
		||||
  String profileImagePath;
 | 
			
		||||
 | 
			
		||||
  int? quotaSizeInBytes;
 | 
			
		||||
 | 
			
		||||
  int? quotaUsageInBytes;
 | 
			
		||||
 | 
			
		||||
  bool shouldChangePassword;
 | 
			
		||||
 | 
			
		||||
  UserStatus status;
 | 
			
		||||
 | 
			
		||||
  String? storageLabel;
 | 
			
		||||
 | 
			
		||||
  DateTime updatedAt;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserAdminResponseDto &&
 | 
			
		||||
    other.avatarColor == avatarColor &&
 | 
			
		||||
    other.createdAt == createdAt &&
 | 
			
		||||
    other.deletedAt == deletedAt &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.id == id &&
 | 
			
		||||
    other.isAdmin == isAdmin &&
 | 
			
		||||
    other.memoriesEnabled == memoriesEnabled &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
    other.oauthId == oauthId &&
 | 
			
		||||
    other.profileImagePath == profileImagePath &&
 | 
			
		||||
    other.quotaSizeInBytes == quotaSizeInBytes &&
 | 
			
		||||
    other.quotaUsageInBytes == quotaUsageInBytes &&
 | 
			
		||||
    other.shouldChangePassword == shouldChangePassword &&
 | 
			
		||||
    other.status == status &&
 | 
			
		||||
    other.storageLabel == storageLabel &&
 | 
			
		||||
    other.updatedAt == updatedAt;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (avatarColor.hashCode) +
 | 
			
		||||
    (createdAt.hashCode) +
 | 
			
		||||
    (deletedAt == null ? 0 : deletedAt!.hashCode) +
 | 
			
		||||
    (email.hashCode) +
 | 
			
		||||
    (id.hashCode) +
 | 
			
		||||
    (isAdmin.hashCode) +
 | 
			
		||||
    (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) +
 | 
			
		||||
    (name.hashCode) +
 | 
			
		||||
    (oauthId.hashCode) +
 | 
			
		||||
    (profileImagePath.hashCode) +
 | 
			
		||||
    (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) +
 | 
			
		||||
    (quotaUsageInBytes == null ? 0 : quotaUsageInBytes!.hashCode) +
 | 
			
		||||
    (shouldChangePassword.hashCode) +
 | 
			
		||||
    (status.hashCode) +
 | 
			
		||||
    (storageLabel == null ? 0 : storageLabel!.hashCode) +
 | 
			
		||||
    (updatedAt.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'UserAdminResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, quotaSizeInBytes=$quotaSizeInBytes, quotaUsageInBytes=$quotaUsageInBytes, shouldChangePassword=$shouldChangePassword, status=$status, storageLabel=$storageLabel, updatedAt=$updatedAt]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
      json[r'avatarColor'] = this.avatarColor;
 | 
			
		||||
      json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
 | 
			
		||||
    if (this.deletedAt != null) {
 | 
			
		||||
      json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String();
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'deletedAt'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'email'] = this.email;
 | 
			
		||||
      json[r'id'] = this.id;
 | 
			
		||||
      json[r'isAdmin'] = this.isAdmin;
 | 
			
		||||
    if (this.memoriesEnabled != null) {
 | 
			
		||||
      json[r'memoriesEnabled'] = this.memoriesEnabled;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'memoriesEnabled'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'name'] = this.name;
 | 
			
		||||
      json[r'oauthId'] = this.oauthId;
 | 
			
		||||
      json[r'profileImagePath'] = this.profileImagePath;
 | 
			
		||||
    if (this.quotaSizeInBytes != null) {
 | 
			
		||||
      json[r'quotaSizeInBytes'] = this.quotaSizeInBytes;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'quotaSizeInBytes'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.quotaUsageInBytes != null) {
 | 
			
		||||
      json[r'quotaUsageInBytes'] = this.quotaUsageInBytes;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'quotaUsageInBytes'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'shouldChangePassword'] = this.shouldChangePassword;
 | 
			
		||||
      json[r'status'] = this.status;
 | 
			
		||||
    if (this.storageLabel != null) {
 | 
			
		||||
      json[r'storageLabel'] = this.storageLabel;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'storageLabel'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a new [UserAdminResponseDto] instance and imports its values from
 | 
			
		||||
  /// [value] if it's a [Map], null otherwise.
 | 
			
		||||
  // ignore: prefer_constructors_over_static_methods
 | 
			
		||||
  static UserAdminResponseDto? fromJson(dynamic value) {
 | 
			
		||||
    if (value is Map) {
 | 
			
		||||
      final json = value.cast<String, dynamic>();
 | 
			
		||||
 | 
			
		||||
      return UserAdminResponseDto(
 | 
			
		||||
        avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!,
 | 
			
		||||
        createdAt: mapDateTime(json, r'createdAt', r'')!,
 | 
			
		||||
        deletedAt: mapDateTime(json, r'deletedAt', r''),
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email')!,
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
 | 
			
		||||
        memoriesEnabled: mapValueOfType<bool>(json, r'memoriesEnabled'),
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name')!,
 | 
			
		||||
        oauthId: mapValueOfType<String>(json, r'oauthId')!,
 | 
			
		||||
        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
 | 
			
		||||
        quotaSizeInBytes: mapValueOfType<int>(json, r'quotaSizeInBytes'),
 | 
			
		||||
        quotaUsageInBytes: mapValueOfType<int>(json, r'quotaUsageInBytes'),
 | 
			
		||||
        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
 | 
			
		||||
        status: UserStatus.fromJson(json[r'status'])!,
 | 
			
		||||
        storageLabel: mapValueOfType<String>(json, r'storageLabel'),
 | 
			
		||||
        updatedAt: mapDateTime(json, r'updatedAt', r'')!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<UserAdminResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UserAdminResponseDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
        final value = UserAdminResponseDto.fromJson(row);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          result.add(value);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return result.toList(growable: growable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Map<String, UserAdminResponseDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UserAdminResponseDto>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = UserAdminResponseDto.fromJson(entry.value);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of UserAdminResponseDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UserAdminResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UserAdminResponseDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      // ignore: parameter_assignments
 | 
			
		||||
      json = json.cast<String, dynamic>();
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        map[entry.key] = UserAdminResponseDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// The list of required keys that must be present in a JSON.
 | 
			
		||||
  static const requiredKeys = <String>{
 | 
			
		||||
    'avatarColor',
 | 
			
		||||
    'createdAt',
 | 
			
		||||
    'deletedAt',
 | 
			
		||||
    'email',
 | 
			
		||||
    'id',
 | 
			
		||||
    'isAdmin',
 | 
			
		||||
    'name',
 | 
			
		||||
    'oauthId',
 | 
			
		||||
    'profileImagePath',
 | 
			
		||||
    'quotaSizeInBytes',
 | 
			
		||||
    'quotaUsageInBytes',
 | 
			
		||||
    'shouldChangePassword',
 | 
			
		||||
    'status',
 | 
			
		||||
    'storageLabel',
 | 
			
		||||
    'updatedAt',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,13 +10,11 @@
 | 
			
		||||
 | 
			
		||||
part of openapi.api;
 | 
			
		||||
 | 
			
		||||
class UpdateUserDto {
 | 
			
		||||
  /// Returns a new [UpdateUserDto] instance.
 | 
			
		||||
  UpdateUserDto({
 | 
			
		||||
class UserAdminUpdateDto {
 | 
			
		||||
  /// Returns a new [UserAdminUpdateDto] instance.
 | 
			
		||||
  UserAdminUpdateDto({
 | 
			
		||||
    this.avatarColor,
 | 
			
		||||
    this.email,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    this.isAdmin,
 | 
			
		||||
    this.memoriesEnabled,
 | 
			
		||||
    this.name,
 | 
			
		||||
    this.password,
 | 
			
		||||
@ -41,16 +39,6 @@ class UpdateUserDto {
 | 
			
		||||
  ///
 | 
			
		||||
  String? email;
 | 
			
		||||
 | 
			
		||||
  String id;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  bool? isAdmin;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
@ -86,20 +74,12 @@ class UpdateUserDto {
 | 
			
		||||
  ///
 | 
			
		||||
  bool? shouldChangePassword;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  String? storageLabel;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserAdminUpdateDto &&
 | 
			
		||||
    other.avatarColor == avatarColor &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.id == id &&
 | 
			
		||||
    other.isAdmin == isAdmin &&
 | 
			
		||||
    other.memoriesEnabled == memoriesEnabled &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
    other.password == password &&
 | 
			
		||||
@ -112,8 +92,6 @@ class UpdateUserDto {
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (avatarColor == null ? 0 : avatarColor!.hashCode) +
 | 
			
		||||
    (email == null ? 0 : email!.hashCode) +
 | 
			
		||||
    (id.hashCode) +
 | 
			
		||||
    (isAdmin == null ? 0 : isAdmin!.hashCode) +
 | 
			
		||||
    (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) +
 | 
			
		||||
    (name == null ? 0 : name!.hashCode) +
 | 
			
		||||
    (password == null ? 0 : password!.hashCode) +
 | 
			
		||||
@ -122,7 +100,7 @@ class UpdateUserDto {
 | 
			
		||||
    (storageLabel == null ? 0 : storageLabel!.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'UpdateUserDto[avatarColor=$avatarColor, email=$email, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]';
 | 
			
		||||
  String toString() => 'UserAdminUpdateDto[avatarColor=$avatarColor, email=$email, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@ -135,12 +113,6 @@ class UpdateUserDto {
 | 
			
		||||
      json[r'email'] = this.email;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'email'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'id'] = this.id;
 | 
			
		||||
    if (this.isAdmin != null) {
 | 
			
		||||
      json[r'isAdmin'] = this.isAdmin;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'isAdmin'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.memoriesEnabled != null) {
 | 
			
		||||
      json[r'memoriesEnabled'] = this.memoriesEnabled;
 | 
			
		||||
@ -175,18 +147,16 @@ class UpdateUserDto {
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a new [UpdateUserDto] instance and imports its values from
 | 
			
		||||
  /// Returns a new [UserAdminUpdateDto] instance and imports its values from
 | 
			
		||||
  /// [value] if it's a [Map], null otherwise.
 | 
			
		||||
  // ignore: prefer_constructors_over_static_methods
 | 
			
		||||
  static UpdateUserDto? fromJson(dynamic value) {
 | 
			
		||||
  static UserAdminUpdateDto? fromJson(dynamic value) {
 | 
			
		||||
    if (value is Map) {
 | 
			
		||||
      final json = value.cast<String, dynamic>();
 | 
			
		||||
 | 
			
		||||
      return UpdateUserDto(
 | 
			
		||||
      return UserAdminUpdateDto(
 | 
			
		||||
        avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']),
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email'),
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        isAdmin: mapValueOfType<bool>(json, r'isAdmin'),
 | 
			
		||||
        memoriesEnabled: mapValueOfType<bool>(json, r'memoriesEnabled'),
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name'),
 | 
			
		||||
        password: mapValueOfType<String>(json, r'password'),
 | 
			
		||||
@ -198,11 +168,11 @@ class UpdateUserDto {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<UpdateUserDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UpdateUserDto>[];
 | 
			
		||||
  static List<UserAdminUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UserAdminUpdateDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
        final value = UpdateUserDto.fromJson(row);
 | 
			
		||||
        final value = UserAdminUpdateDto.fromJson(row);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          result.add(value);
 | 
			
		||||
        }
 | 
			
		||||
@ -211,12 +181,12 @@ class UpdateUserDto {
 | 
			
		||||
    return result.toList(growable: growable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Map<String, UpdateUserDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UpdateUserDto>{};
 | 
			
		||||
  static Map<String, UserAdminUpdateDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UserAdminUpdateDto>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = UpdateUserDto.fromJson(entry.value);
 | 
			
		||||
        final value = UserAdminUpdateDto.fromJson(entry.value);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
@ -225,14 +195,14 @@ class UpdateUserDto {
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of UpdateUserDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UpdateUserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UpdateUserDto>>{};
 | 
			
		||||
  // maps a json object with a list of UserAdminUpdateDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UserAdminUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UserAdminUpdateDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      // ignore: parameter_assignments
 | 
			
		||||
      json = json.cast<String, dynamic>();
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        map[entry.key] = UpdateUserDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
        map[entry.key] = UserAdminUpdateDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
@ -240,7 +210,6 @@ class UpdateUserDto {
 | 
			
		||||
 | 
			
		||||
  /// The list of required keys that must be present in a JSON.
 | 
			
		||||
  static const requiredKeys = <String>{
 | 
			
		||||
    'id',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										130
									
								
								mobile/openapi/lib/model/user_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										130
									
								
								mobile/openapi/lib/model/user_dto.dart
									
									
									
										generated
									
									
									
								
							@ -1,130 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
 | 
			
		||||
//
 | 
			
		||||
// @dart=2.18
 | 
			
		||||
 | 
			
		||||
// ignore_for_file: unused_element, unused_import
 | 
			
		||||
// ignore_for_file: always_put_required_named_parameters_first
 | 
			
		||||
// ignore_for_file: constant_identifier_names
 | 
			
		||||
// ignore_for_file: lines_longer_than_80_chars
 | 
			
		||||
 | 
			
		||||
part of openapi.api;
 | 
			
		||||
 | 
			
		||||
class UserDto {
 | 
			
		||||
  /// Returns a new [UserDto] instance.
 | 
			
		||||
  UserDto({
 | 
			
		||||
    required this.avatarColor,
 | 
			
		||||
    required this.email,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.profileImagePath,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  UserAvatarColor avatarColor;
 | 
			
		||||
 | 
			
		||||
  String email;
 | 
			
		||||
 | 
			
		||||
  String id;
 | 
			
		||||
 | 
			
		||||
  String name;
 | 
			
		||||
 | 
			
		||||
  String profileImagePath;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserDto &&
 | 
			
		||||
    other.avatarColor == avatarColor &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.id == id &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
    other.profileImagePath == profileImagePath;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (avatarColor.hashCode) +
 | 
			
		||||
    (email.hashCode) +
 | 
			
		||||
    (id.hashCode) +
 | 
			
		||||
    (name.hashCode) +
 | 
			
		||||
    (profileImagePath.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'UserDto[avatarColor=$avatarColor, email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
      json[r'avatarColor'] = this.avatarColor;
 | 
			
		||||
      json[r'email'] = this.email;
 | 
			
		||||
      json[r'id'] = this.id;
 | 
			
		||||
      json[r'name'] = this.name;
 | 
			
		||||
      json[r'profileImagePath'] = this.profileImagePath;
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a new [UserDto] instance and imports its values from
 | 
			
		||||
  /// [value] if it's a [Map], null otherwise.
 | 
			
		||||
  // ignore: prefer_constructors_over_static_methods
 | 
			
		||||
  static UserDto? fromJson(dynamic value) {
 | 
			
		||||
    if (value is Map) {
 | 
			
		||||
      final json = value.cast<String, dynamic>();
 | 
			
		||||
 | 
			
		||||
      return UserDto(
 | 
			
		||||
        avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!,
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email')!,
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name')!,
 | 
			
		||||
        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<UserDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UserDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
        final value = UserDto.fromJson(row);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          result.add(value);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return result.toList(growable: growable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Map<String, UserDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UserDto>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = UserDto.fromJson(entry.value);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of UserDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UserDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      // ignore: parameter_assignments
 | 
			
		||||
      json = json.cast<String, dynamic>();
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        map[entry.key] = UserDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// The list of required keys that must be present in a JSON.
 | 
			
		||||
  static const requiredKeys = <String>{
 | 
			
		||||
    'avatarColor',
 | 
			
		||||
    'email',
 | 
			
		||||
    'id',
 | 
			
		||||
    'name',
 | 
			
		||||
    'profileImagePath',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								mobile/openapi/lib/model/user_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										119
									
								
								mobile/openapi/lib/model/user_response_dto.dart
									
									
									
										generated
									
									
									
								
							@ -14,141 +14,49 @@ class UserResponseDto {
 | 
			
		||||
  /// Returns a new [UserResponseDto] instance.
 | 
			
		||||
  UserResponseDto({
 | 
			
		||||
    required this.avatarColor,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.deletedAt,
 | 
			
		||||
    required this.email,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.isAdmin,
 | 
			
		||||
    this.memoriesEnabled,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.oauthId,
 | 
			
		||||
    required this.profileImagePath,
 | 
			
		||||
    required this.quotaSizeInBytes,
 | 
			
		||||
    required this.quotaUsageInBytes,
 | 
			
		||||
    required this.shouldChangePassword,
 | 
			
		||||
    required this.status,
 | 
			
		||||
    required this.storageLabel,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  UserAvatarColor avatarColor;
 | 
			
		||||
 | 
			
		||||
  DateTime createdAt;
 | 
			
		||||
 | 
			
		||||
  DateTime? deletedAt;
 | 
			
		||||
 | 
			
		||||
  String email;
 | 
			
		||||
 | 
			
		||||
  String id;
 | 
			
		||||
 | 
			
		||||
  bool isAdmin;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  bool? memoriesEnabled;
 | 
			
		||||
 | 
			
		||||
  String name;
 | 
			
		||||
 | 
			
		||||
  String oauthId;
 | 
			
		||||
 | 
			
		||||
  String profileImagePath;
 | 
			
		||||
 | 
			
		||||
  int? quotaSizeInBytes;
 | 
			
		||||
 | 
			
		||||
  int? quotaUsageInBytes;
 | 
			
		||||
 | 
			
		||||
  bool shouldChangePassword;
 | 
			
		||||
 | 
			
		||||
  UserStatus status;
 | 
			
		||||
 | 
			
		||||
  String? storageLabel;
 | 
			
		||||
 | 
			
		||||
  DateTime updatedAt;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserResponseDto &&
 | 
			
		||||
    other.avatarColor == avatarColor &&
 | 
			
		||||
    other.createdAt == createdAt &&
 | 
			
		||||
    other.deletedAt == deletedAt &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.id == id &&
 | 
			
		||||
    other.isAdmin == isAdmin &&
 | 
			
		||||
    other.memoriesEnabled == memoriesEnabled &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
    other.oauthId == oauthId &&
 | 
			
		||||
    other.profileImagePath == profileImagePath &&
 | 
			
		||||
    other.quotaSizeInBytes == quotaSizeInBytes &&
 | 
			
		||||
    other.quotaUsageInBytes == quotaUsageInBytes &&
 | 
			
		||||
    other.shouldChangePassword == shouldChangePassword &&
 | 
			
		||||
    other.status == status &&
 | 
			
		||||
    other.storageLabel == storageLabel &&
 | 
			
		||||
    other.updatedAt == updatedAt;
 | 
			
		||||
    other.profileImagePath == profileImagePath;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (avatarColor.hashCode) +
 | 
			
		||||
    (createdAt.hashCode) +
 | 
			
		||||
    (deletedAt == null ? 0 : deletedAt!.hashCode) +
 | 
			
		||||
    (email.hashCode) +
 | 
			
		||||
    (id.hashCode) +
 | 
			
		||||
    (isAdmin.hashCode) +
 | 
			
		||||
    (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) +
 | 
			
		||||
    (name.hashCode) +
 | 
			
		||||
    (oauthId.hashCode) +
 | 
			
		||||
    (profileImagePath.hashCode) +
 | 
			
		||||
    (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) +
 | 
			
		||||
    (quotaUsageInBytes == null ? 0 : quotaUsageInBytes!.hashCode) +
 | 
			
		||||
    (shouldChangePassword.hashCode) +
 | 
			
		||||
    (status.hashCode) +
 | 
			
		||||
    (storageLabel == null ? 0 : storageLabel!.hashCode) +
 | 
			
		||||
    (updatedAt.hashCode);
 | 
			
		||||
    (profileImagePath.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'UserResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, quotaSizeInBytes=$quotaSizeInBytes, quotaUsageInBytes=$quotaUsageInBytes, shouldChangePassword=$shouldChangePassword, status=$status, storageLabel=$storageLabel, updatedAt=$updatedAt]';
 | 
			
		||||
  String toString() => 'UserResponseDto[avatarColor=$avatarColor, email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
      json[r'avatarColor'] = this.avatarColor;
 | 
			
		||||
      json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
 | 
			
		||||
    if (this.deletedAt != null) {
 | 
			
		||||
      json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String();
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'deletedAt'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'email'] = this.email;
 | 
			
		||||
      json[r'id'] = this.id;
 | 
			
		||||
      json[r'isAdmin'] = this.isAdmin;
 | 
			
		||||
    if (this.memoriesEnabled != null) {
 | 
			
		||||
      json[r'memoriesEnabled'] = this.memoriesEnabled;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'memoriesEnabled'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'name'] = this.name;
 | 
			
		||||
      json[r'oauthId'] = this.oauthId;
 | 
			
		||||
      json[r'profileImagePath'] = this.profileImagePath;
 | 
			
		||||
    if (this.quotaSizeInBytes != null) {
 | 
			
		||||
      json[r'quotaSizeInBytes'] = this.quotaSizeInBytes;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'quotaSizeInBytes'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.quotaUsageInBytes != null) {
 | 
			
		||||
      json[r'quotaUsageInBytes'] = this.quotaUsageInBytes;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'quotaUsageInBytes'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'shouldChangePassword'] = this.shouldChangePassword;
 | 
			
		||||
      json[r'status'] = this.status;
 | 
			
		||||
    if (this.storageLabel != null) {
 | 
			
		||||
      json[r'storageLabel'] = this.storageLabel;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'storageLabel'] = null;
 | 
			
		||||
    }
 | 
			
		||||
      json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -161,21 +69,10 @@ class UserResponseDto {
 | 
			
		||||
 | 
			
		||||
      return UserResponseDto(
 | 
			
		||||
        avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!,
 | 
			
		||||
        createdAt: mapDateTime(json, r'createdAt', r'')!,
 | 
			
		||||
        deletedAt: mapDateTime(json, r'deletedAt', r''),
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email')!,
 | 
			
		||||
        id: mapValueOfType<String>(json, r'id')!,
 | 
			
		||||
        isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
 | 
			
		||||
        memoriesEnabled: mapValueOfType<bool>(json, r'memoriesEnabled'),
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name')!,
 | 
			
		||||
        oauthId: mapValueOfType<String>(json, r'oauthId')!,
 | 
			
		||||
        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
 | 
			
		||||
        quotaSizeInBytes: mapValueOfType<int>(json, r'quotaSizeInBytes'),
 | 
			
		||||
        quotaUsageInBytes: mapValueOfType<int>(json, r'quotaUsageInBytes'),
 | 
			
		||||
        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
 | 
			
		||||
        status: UserStatus.fromJson(json[r'status'])!,
 | 
			
		||||
        storageLabel: mapValueOfType<String>(json, r'storageLabel'),
 | 
			
		||||
        updatedAt: mapDateTime(json, r'updatedAt', r'')!,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@ -224,20 +121,10 @@ class UserResponseDto {
 | 
			
		||||
  /// The list of required keys that must be present in a JSON.
 | 
			
		||||
  static const requiredKeys = <String>{
 | 
			
		||||
    'avatarColor',
 | 
			
		||||
    'createdAt',
 | 
			
		||||
    'deletedAt',
 | 
			
		||||
    'email',
 | 
			
		||||
    'id',
 | 
			
		||||
    'isAdmin',
 | 
			
		||||
    'name',
 | 
			
		||||
    'oauthId',
 | 
			
		||||
    'profileImagePath',
 | 
			
		||||
    'quotaSizeInBytes',
 | 
			
		||||
    'quotaUsageInBytes',
 | 
			
		||||
    'shouldChangePassword',
 | 
			
		||||
    'status',
 | 
			
		||||
    'storageLabel',
 | 
			
		||||
    'updatedAt',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										175
									
								
								mobile/openapi/lib/model/user_update_me_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								mobile/openapi/lib/model/user_update_me_dto.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,175 @@
 | 
			
		||||
//
 | 
			
		||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
 | 
			
		||||
//
 | 
			
		||||
// @dart=2.18
 | 
			
		||||
 | 
			
		||||
// ignore_for_file: unused_element, unused_import
 | 
			
		||||
// ignore_for_file: always_put_required_named_parameters_first
 | 
			
		||||
// ignore_for_file: constant_identifier_names
 | 
			
		||||
// ignore_for_file: lines_longer_than_80_chars
 | 
			
		||||
 | 
			
		||||
part of openapi.api;
 | 
			
		||||
 | 
			
		||||
class UserUpdateMeDto {
 | 
			
		||||
  /// Returns a new [UserUpdateMeDto] instance.
 | 
			
		||||
  UserUpdateMeDto({
 | 
			
		||||
    this.avatarColor,
 | 
			
		||||
    this.email,
 | 
			
		||||
    this.memoriesEnabled,
 | 
			
		||||
    this.name,
 | 
			
		||||
    this.password,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  UserAvatarColor? avatarColor;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  String? email;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  bool? memoriesEnabled;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  String? name;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  String? password;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is UserUpdateMeDto &&
 | 
			
		||||
    other.avatarColor == avatarColor &&
 | 
			
		||||
    other.email == email &&
 | 
			
		||||
    other.memoriesEnabled == memoriesEnabled &&
 | 
			
		||||
    other.name == name &&
 | 
			
		||||
    other.password == password;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
    // ignore: unnecessary_parenthesis
 | 
			
		||||
    (avatarColor == null ? 0 : avatarColor!.hashCode) +
 | 
			
		||||
    (email == null ? 0 : email!.hashCode) +
 | 
			
		||||
    (memoriesEnabled == null ? 0 : memoriesEnabled!.hashCode) +
 | 
			
		||||
    (name == null ? 0 : name!.hashCode) +
 | 
			
		||||
    (password == null ? 0 : password!.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'UserUpdateMeDto[avatarColor=$avatarColor, email=$email, memoriesEnabled=$memoriesEnabled, name=$name, password=$password]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
    if (this.avatarColor != null) {
 | 
			
		||||
      json[r'avatarColor'] = this.avatarColor;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'avatarColor'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.email != null) {
 | 
			
		||||
      json[r'email'] = this.email;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'email'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.memoriesEnabled != null) {
 | 
			
		||||
      json[r'memoriesEnabled'] = this.memoriesEnabled;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'memoriesEnabled'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.name != null) {
 | 
			
		||||
      json[r'name'] = this.name;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'name'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.password != null) {
 | 
			
		||||
      json[r'password'] = this.password;
 | 
			
		||||
    } else {
 | 
			
		||||
    //  json[r'password'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a new [UserUpdateMeDto] instance and imports its values from
 | 
			
		||||
  /// [value] if it's a [Map], null otherwise.
 | 
			
		||||
  // ignore: prefer_constructors_over_static_methods
 | 
			
		||||
  static UserUpdateMeDto? fromJson(dynamic value) {
 | 
			
		||||
    if (value is Map) {
 | 
			
		||||
      final json = value.cast<String, dynamic>();
 | 
			
		||||
 | 
			
		||||
      return UserUpdateMeDto(
 | 
			
		||||
        avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']),
 | 
			
		||||
        email: mapValueOfType<String>(json, r'email'),
 | 
			
		||||
        memoriesEnabled: mapValueOfType<bool>(json, r'memoriesEnabled'),
 | 
			
		||||
        name: mapValueOfType<String>(json, r'name'),
 | 
			
		||||
        password: mapValueOfType<String>(json, r'password'),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<UserUpdateMeDto> listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <UserUpdateMeDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
        final value = UserUpdateMeDto.fromJson(row);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          result.add(value);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return result.toList(growable: growable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Map<String, UserUpdateMeDto> mapFromJson(dynamic json) {
 | 
			
		||||
    final map = <String, UserUpdateMeDto>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = UserUpdateMeDto.fromJson(entry.value);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of UserUpdateMeDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<UserUpdateMeDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<UserUpdateMeDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      // ignore: parameter_assignments
 | 
			
		||||
      json = json.cast<String, dynamic>();
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        map[entry.key] = UserUpdateMeDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return map;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// The list of required keys that must be present in a JSON.
 | 
			
		||||
  static const requiredKeys = <String>{
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -206,6 +206,274 @@
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/admin/users": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "searchUsersAdmin",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "withDeleted",
 | 
			
		||||
            "required": false,
 | 
			
		||||
            "in": "query",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "type": "boolean"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "items": {
 | 
			
		||||
                    "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                  },
 | 
			
		||||
                  "type": "array"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "createUserAdmin",
 | 
			
		||||
        "parameters": [],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UserAdminCreateDto"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/admin/users/{id}": {
 | 
			
		||||
      "delete": {
 | 
			
		||||
        "operationId": "deleteUserAdmin",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "format": "uuid",
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UserAdminDeleteDto"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "getUserAdmin",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "format": "uuid",
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "put": {
 | 
			
		||||
        "operationId": "updateUserAdmin",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "format": "uuid",
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UserAdminUpdateDto"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/admin/users/{id}/restore": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "restoreUserAdmin",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "format": "uuid",
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/albums": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "getAllAlbums",
 | 
			
		||||
@ -1879,7 +2147,7 @@
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
@ -1910,7 +2178,7 @@
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
@ -3160,7 +3428,7 @@
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
@ -3206,7 +3474,7 @@
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
@ -6030,17 +6298,8 @@
 | 
			
		||||
    },
 | 
			
		||||
    "/users": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "getAllUsers",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "isAll",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "query",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "type": "boolean"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "operationId": "searchUsers",
 | 
			
		||||
        "parameters": [],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
@ -6070,26 +6329,18 @@
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "createUser",
 | 
			
		||||
    "/users/me": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "getMyUser",
 | 
			
		||||
        "parameters": [],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/CreateUserDto"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
@ -6112,13 +6363,13 @@
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "put": {
 | 
			
		||||
        "operationId": "updateUser",
 | 
			
		||||
        "operationId": "updateMyUser",
 | 
			
		||||
        "parameters": [],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/UpdateUserDto"
 | 
			
		||||
                "$ref": "#/components/schemas/UserUpdateMeDto"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
@ -6129,39 +6380,7 @@
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/users/me": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "getMyUserInfo",
 | 
			
		||||
        "parameters": [],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                  "$ref": "#/components/schemas/UserAdminResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
@ -6251,58 +6470,8 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/users/{id}": {
 | 
			
		||||
      "delete": {
 | 
			
		||||
        "operationId": "deleteUser",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "format": "uuid",
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "requestBody": {
 | 
			
		||||
          "content": {
 | 
			
		||||
            "application/json": {
 | 
			
		||||
              "schema": {
 | 
			
		||||
                "$ref": "#/components/schemas/DeleteUserDto"
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "required": true
 | 
			
		||||
        },
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "get": {
 | 
			
		||||
        "operationId": "getUserById",
 | 
			
		||||
        "operationId": "getUser",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
@ -6384,48 +6553,6 @@
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/users/{id}/restore": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "operationId": "restoreUser",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "format": "uuid",
 | 
			
		||||
              "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "content": {
 | 
			
		||||
              "application/json": {
 | 
			
		||||
                "schema": {
 | 
			
		||||
                  "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "description": ""
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "security": [
 | 
			
		||||
          {
 | 
			
		||||
            "bearer": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "cookie": []
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "api_key": []
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "User"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "info": {
 | 
			
		||||
@ -6567,7 +6694,7 @@
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "user": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserDto"
 | 
			
		||||
            "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
@ -7775,52 +7902,6 @@
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "CreateUserDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "memoriesEnabled": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "notify": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "password": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "quotaSizeInBytes": {
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "minimum": 1,
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "integer"
 | 
			
		||||
          },
 | 
			
		||||
          "shouldChangePassword": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "storageLabel": {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
          "email",
 | 
			
		||||
          "name",
 | 
			
		||||
          "password"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "DeleteUserDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "force": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "DownloadArchiveInfo": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "assetIds": {
 | 
			
		||||
@ -8803,15 +8884,6 @@
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
          },
 | 
			
		||||
          "createdAt": {
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "deletedAt": {
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
@ -8821,62 +8893,19 @@
 | 
			
		||||
          "inTimeline": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "isAdmin": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "memoriesEnabled": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "oauthId": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "profileImagePath": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "quotaSizeInBytes": {
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "integer"
 | 
			
		||||
          },
 | 
			
		||||
          "quotaUsageInBytes": {
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "integer"
 | 
			
		||||
          },
 | 
			
		||||
          "shouldChangePassword": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "status": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserStatus"
 | 
			
		||||
          },
 | 
			
		||||
          "storageLabel": {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "updatedAt": {
 | 
			
		||||
            "format": "date-time",
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
          "avatarColor",
 | 
			
		||||
          "createdAt",
 | 
			
		||||
          "deletedAt",
 | 
			
		||||
          "email",
 | 
			
		||||
          "id",
 | 
			
		||||
          "isAdmin",
 | 
			
		||||
          "name",
 | 
			
		||||
          "oauthId",
 | 
			
		||||
          "profileImagePath",
 | 
			
		||||
          "quotaSizeInBytes",
 | 
			
		||||
          "quotaUsageInBytes",
 | 
			
		||||
          "shouldChangePassword",
 | 
			
		||||
          "status",
 | 
			
		||||
          "storageLabel",
 | 
			
		||||
          "updatedAt"
 | 
			
		||||
          "profileImagePath"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
@ -10810,48 +10839,6 @@
 | 
			
		||||
        },
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UpdateUserDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
          },
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "id": {
 | 
			
		||||
            "format": "uuid",
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "isAdmin": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "memoriesEnabled": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "password": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "quotaSizeInBytes": {
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "minimum": 1,
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "integer"
 | 
			
		||||
          },
 | 
			
		||||
          "shouldChangePassword": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "storageLabel": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
          "id"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UsageByUserDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "photos": {
 | 
			
		||||
@ -10886,49 +10873,53 @@
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UserAvatarColor": {
 | 
			
		||||
        "enum": [
 | 
			
		||||
          "primary",
 | 
			
		||||
          "pink",
 | 
			
		||||
          "red",
 | 
			
		||||
          "yellow",
 | 
			
		||||
          "blue",
 | 
			
		||||
          "green",
 | 
			
		||||
          "purple",
 | 
			
		||||
          "orange",
 | 
			
		||||
          "gray",
 | 
			
		||||
          "amber"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "string"
 | 
			
		||||
      },
 | 
			
		||||
      "UserDto": {
 | 
			
		||||
      "UserAdminCreateDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
          },
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "id": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          "memoriesEnabled": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "profileImagePath": {
 | 
			
		||||
          "notify": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "password": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "quotaSizeInBytes": {
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "minimum": 1,
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "integer"
 | 
			
		||||
          },
 | 
			
		||||
          "shouldChangePassword": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "storageLabel": {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
          "avatarColor",
 | 
			
		||||
          "email",
 | 
			
		||||
          "id",
 | 
			
		||||
          "name",
 | 
			
		||||
          "profileImagePath"
 | 
			
		||||
          "password"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UserResponseDto": {
 | 
			
		||||
      "UserAdminDeleteDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "force": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UserAdminResponseDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
@ -11007,6 +10998,81 @@
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UserAdminUpdateDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
          },
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "memoriesEnabled": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "password": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "quotaSizeInBytes": {
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "minimum": 1,
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "integer"
 | 
			
		||||
          },
 | 
			
		||||
          "shouldChangePassword": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "storageLabel": {
 | 
			
		||||
            "nullable": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UserAvatarColor": {
 | 
			
		||||
        "enum": [
 | 
			
		||||
          "primary",
 | 
			
		||||
          "pink",
 | 
			
		||||
          "red",
 | 
			
		||||
          "yellow",
 | 
			
		||||
          "blue",
 | 
			
		||||
          "green",
 | 
			
		||||
          "purple",
 | 
			
		||||
          "orange",
 | 
			
		||||
          "gray",
 | 
			
		||||
          "amber"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "string"
 | 
			
		||||
      },
 | 
			
		||||
      "UserResponseDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
          },
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "id": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "profileImagePath": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
          "avatarColor",
 | 
			
		||||
          "email",
 | 
			
		||||
          "id",
 | 
			
		||||
          "name",
 | 
			
		||||
          "profileImagePath"
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "UserStatus": {
 | 
			
		||||
        "enum": [
 | 
			
		||||
          "active",
 | 
			
		||||
@ -11015,6 +11081,26 @@
 | 
			
		||||
        ],
 | 
			
		||||
        "type": "string"
 | 
			
		||||
      },
 | 
			
		||||
      "UserUpdateMeDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "avatarColor": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserAvatarColor"
 | 
			
		||||
          },
 | 
			
		||||
          "email": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "memoriesEnabled": {
 | 
			
		||||
            "type": "boolean"
 | 
			
		||||
          },
 | 
			
		||||
          "name": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          },
 | 
			
		||||
          "password": {
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "type": "object"
 | 
			
		||||
      },
 | 
			
		||||
      "ValidateAccessTokenResponseDto": {
 | 
			
		||||
        "properties": {
 | 
			
		||||
          "authStatus": {
 | 
			
		||||
 | 
			
		||||
@ -13,13 +13,22 @@ npm i --save @immich/sdk
 | 
			
		||||
For a more detailed example, check out the [`@immich/cli`](https://github.com/immich-app/immich/tree/main/cli).
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
<<<<<<< HEAD
 | 
			
		||||
import { getAllAlbums, getAllAssets, getMyUser, init } from "@immich/sdk";
 | 
			
		||||
=======
 | 
			
		||||
import { getAllAlbums, getMyUserInfo, init } from "@immich/sdk";
 | 
			
		||||
>>>>>>> e7c8501930a988dfb6c23ce1c48b0beb076a58c2
 | 
			
		||||
 | 
			
		||||
const API_KEY = "<API_KEY>"; // process.env.IMMICH_API_KEY
 | 
			
		||||
 | 
			
		||||
init({ baseUrl: "https://demo.immich.app/api", apiKey: API_KEY });
 | 
			
		||||
 | 
			
		||||
<<<<<<< HEAD
 | 
			
		||||
const user = await getMyUser();
 | 
			
		||||
const assets = await getAllAssets({ take: 1000 });
 | 
			
		||||
=======
 | 
			
		||||
const user = await getMyUserInfo();
 | 
			
		||||
>>>>>>> e7c8501930a988dfb6c23ce1c48b0beb076a58c2
 | 
			
		||||
const albums = await getAllAlbums({});
 | 
			
		||||
 | 
			
		||||
console.log({ user, albums });
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ const oazapfts = Oazapfts.runtime(defaults);
 | 
			
		||||
export const servers = {
 | 
			
		||||
    server1: "/api"
 | 
			
		||||
};
 | 
			
		||||
export type UserDto = {
 | 
			
		||||
export type UserResponseDto = {
 | 
			
		||||
    avatarColor: UserAvatarColor;
 | 
			
		||||
    email: string;
 | 
			
		||||
    id: string;
 | 
			
		||||
@ -27,7 +27,7 @@ export type ActivityResponseDto = {
 | 
			
		||||
    createdAt: string;
 | 
			
		||||
    id: string;
 | 
			
		||||
    "type": Type;
 | 
			
		||||
    user: UserDto;
 | 
			
		||||
    user: UserResponseDto;
 | 
			
		||||
};
 | 
			
		||||
export type ActivityCreateDto = {
 | 
			
		||||
    albumId: string;
 | 
			
		||||
@ -38,7 +38,7 @@ export type ActivityCreateDto = {
 | 
			
		||||
export type ActivityStatisticsResponseDto = {
 | 
			
		||||
    comments: number;
 | 
			
		||||
};
 | 
			
		||||
export type UserResponseDto = {
 | 
			
		||||
export type UserAdminResponseDto = {
 | 
			
		||||
    avatarColor: UserAvatarColor;
 | 
			
		||||
    createdAt: string;
 | 
			
		||||
    deletedAt: string | null;
 | 
			
		||||
@ -56,6 +56,29 @@ export type UserResponseDto = {
 | 
			
		||||
    storageLabel: string | null;
 | 
			
		||||
    updatedAt: string;
 | 
			
		||||
};
 | 
			
		||||
export type UserAdminCreateDto = {
 | 
			
		||||
    email: string;
 | 
			
		||||
    memoriesEnabled?: boolean;
 | 
			
		||||
    name: string;
 | 
			
		||||
    notify?: boolean;
 | 
			
		||||
    password: string;
 | 
			
		||||
    quotaSizeInBytes?: number | null;
 | 
			
		||||
    shouldChangePassword?: boolean;
 | 
			
		||||
    storageLabel?: string | null;
 | 
			
		||||
};
 | 
			
		||||
export type UserAdminDeleteDto = {
 | 
			
		||||
    force?: boolean;
 | 
			
		||||
};
 | 
			
		||||
export type UserAdminUpdateDto = {
 | 
			
		||||
    avatarColor?: UserAvatarColor;
 | 
			
		||||
    email?: string;
 | 
			
		||||
    memoriesEnabled?: boolean;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    password?: string;
 | 
			
		||||
    quotaSizeInBytes?: number | null;
 | 
			
		||||
    shouldChangePassword?: boolean;
 | 
			
		||||
    storageLabel?: string | null;
 | 
			
		||||
};
 | 
			
		||||
export type AlbumUserResponseDto = {
 | 
			
		||||
    role: AlbumUserRole;
 | 
			
		||||
    user: UserResponseDto;
 | 
			
		||||
@ -517,22 +540,11 @@ export type OAuthCallbackDto = {
 | 
			
		||||
};
 | 
			
		||||
export type PartnerResponseDto = {
 | 
			
		||||
    avatarColor: UserAvatarColor;
 | 
			
		||||
    createdAt: string;
 | 
			
		||||
    deletedAt: string | null;
 | 
			
		||||
    email: string;
 | 
			
		||||
    id: string;
 | 
			
		||||
    inTimeline?: boolean;
 | 
			
		||||
    isAdmin: boolean;
 | 
			
		||||
    memoriesEnabled?: boolean;
 | 
			
		||||
    name: string;
 | 
			
		||||
    oauthId: string;
 | 
			
		||||
    profileImagePath: string;
 | 
			
		||||
    quotaSizeInBytes: number | null;
 | 
			
		||||
    quotaUsageInBytes: number | null;
 | 
			
		||||
    shouldChangePassword: boolean;
 | 
			
		||||
    status: UserStatus;
 | 
			
		||||
    storageLabel: string | null;
 | 
			
		||||
    updatedAt: string;
 | 
			
		||||
};
 | 
			
		||||
export type UpdatePartnerDto = {
 | 
			
		||||
    inTimeline: boolean;
 | 
			
		||||
@ -1060,27 +1072,12 @@ export type TimeBucketResponseDto = {
 | 
			
		||||
    count: number;
 | 
			
		||||
    timeBucket: string;
 | 
			
		||||
};
 | 
			
		||||
export type CreateUserDto = {
 | 
			
		||||
    email: string;
 | 
			
		||||
    memoriesEnabled?: boolean;
 | 
			
		||||
    name: string;
 | 
			
		||||
    notify?: boolean;
 | 
			
		||||
    password: string;
 | 
			
		||||
    quotaSizeInBytes?: number | null;
 | 
			
		||||
    shouldChangePassword?: boolean;
 | 
			
		||||
    storageLabel?: string | null;
 | 
			
		||||
};
 | 
			
		||||
export type UpdateUserDto = {
 | 
			
		||||
export type UserUpdateMeDto = {
 | 
			
		||||
    avatarColor?: UserAvatarColor;
 | 
			
		||||
    email?: string;
 | 
			
		||||
    id: string;
 | 
			
		||||
    isAdmin?: boolean;
 | 
			
		||||
    memoriesEnabled?: boolean;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    password?: string;
 | 
			
		||||
    quotaSizeInBytes?: number | null;
 | 
			
		||||
    shouldChangePassword?: boolean;
 | 
			
		||||
    storageLabel?: string;
 | 
			
		||||
};
 | 
			
		||||
export type CreateProfileImageDto = {
 | 
			
		||||
    file: Blob;
 | 
			
		||||
@ -1089,9 +1086,6 @@ export type CreateProfileImageResponseDto = {
 | 
			
		||||
    profileImagePath: string;
 | 
			
		||||
    userId: string;
 | 
			
		||||
};
 | 
			
		||||
export type DeleteUserDto = {
 | 
			
		||||
    force?: boolean;
 | 
			
		||||
};
 | 
			
		||||
export function getActivities({ albumId, assetId, level, $type, userId }: {
 | 
			
		||||
    albumId: string;
 | 
			
		||||
    assetId?: string;
 | 
			
		||||
@ -1146,6 +1140,77 @@ export function deleteActivity({ id }: {
 | 
			
		||||
        method: "DELETE"
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function searchUsersAdmin({ withDeleted }: {
 | 
			
		||||
    withDeleted?: boolean;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserAdminResponseDto[];
 | 
			
		||||
    }>(`/admin/users${QS.query(QS.explode({
 | 
			
		||||
        withDeleted
 | 
			
		||||
    }))}`, {
 | 
			
		||||
        ...opts
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function createUserAdmin({ userAdminCreateDto }: {
 | 
			
		||||
    userAdminCreateDto: UserAdminCreateDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/admin/users", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        body: userAdminCreateDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function deleteUserAdmin({ id, userAdminDeleteDto }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
    userAdminDeleteDto: UserAdminDeleteDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>(`/admin/users/${encodeURIComponent(id)}`, oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "DELETE",
 | 
			
		||||
        body: userAdminDeleteDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function getUserAdmin({ id }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>(`/admin/users/${encodeURIComponent(id)}`, {
 | 
			
		||||
        ...opts
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function updateUserAdmin({ id, userAdminUpdateDto }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
    userAdminUpdateDto: UserAdminUpdateDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>(`/admin/users/${encodeURIComponent(id)}`, oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "PUT",
 | 
			
		||||
        body: userAdminUpdateDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function restoreUserAdmin({ id }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>(`/admin/users/${encodeURIComponent(id)}/restore`, {
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST"
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function getAllAlbums({ assetId, shared }: {
 | 
			
		||||
    assetId?: string;
 | 
			
		||||
    shared?: boolean;
 | 
			
		||||
@ -1589,7 +1654,7 @@ export function signUpAdmin({ signUpDto }: {
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/auth/admin-sign-up", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST",
 | 
			
		||||
@ -1601,7 +1666,7 @@ export function changePassword({ changePasswordDto }: {
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/auth/change-password", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST",
 | 
			
		||||
@ -1934,7 +1999,7 @@ export function linkOAuthAccount({ oAuthCallbackDto }: {
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/oauth/link", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST",
 | 
			
		||||
@ -1949,7 +2014,7 @@ export function redirectOAuthToMobile(opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
export function unlinkOAuthAccount(opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/oauth/unlink", {
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST"
 | 
			
		||||
@ -2687,50 +2752,34 @@ export function restoreAssets({ bulkIdsDto }: {
 | 
			
		||||
        body: bulkIdsDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function getAllUsers({ isAll }: {
 | 
			
		||||
    isAll: boolean;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
export function searchUsers(opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserResponseDto[];
 | 
			
		||||
    }>(`/users${QS.query(QS.explode({
 | 
			
		||||
        isAll
 | 
			
		||||
    }))}`, {
 | 
			
		||||
    }>("/users", {
 | 
			
		||||
        ...opts
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function createUser({ createUserDto }: {
 | 
			
		||||
    createUserDto: CreateUserDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
    }>("/users", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        body: createUserDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function updateUser({ updateUserDto }: {
 | 
			
		||||
    updateUserDto: UpdateUserDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
export function getMyUser(opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
    }>("/users", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "PUT",
 | 
			
		||||
        body: updateUserDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function getMyUserInfo(opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/users/me", {
 | 
			
		||||
        ...opts
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function updateMyUser({ userUpdateMeDto }: {
 | 
			
		||||
    userUpdateMeDto: UserUpdateMeDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserAdminResponseDto;
 | 
			
		||||
    }>("/users/me", oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "PUT",
 | 
			
		||||
        body: userUpdateMeDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function deleteProfileImage(opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchText("/users/profile-image", {
 | 
			
		||||
        ...opts,
 | 
			
		||||
@ -2749,20 +2798,7 @@ export function createProfileImage({ createProfileImageDto }: {
 | 
			
		||||
        body: createProfileImageDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function deleteUser({ id, deleteUserDto }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
    deleteUserDto: DeleteUserDto;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 200;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
    }>(`/users/${encodeURIComponent(id)}`, oazapfts.json({
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "DELETE",
 | 
			
		||||
        body: deleteUserDto
 | 
			
		||||
    })));
 | 
			
		||||
}
 | 
			
		||||
export function getUserById({ id }: {
 | 
			
		||||
export function getUser({ id }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
@ -2782,17 +2818,6 @@ export function getProfileImage({ id }: {
 | 
			
		||||
        ...opts
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export function restoreUser({ id }: {
 | 
			
		||||
    id: string;
 | 
			
		||||
}, opts?: Oazapfts.RequestOpts) {
 | 
			
		||||
    return oazapfts.ok(oazapfts.fetchJson<{
 | 
			
		||||
        status: 201;
 | 
			
		||||
        data: UserResponseDto;
 | 
			
		||||
    }>(`/users/${encodeURIComponent(id)}/restore`, {
 | 
			
		||||
        ...opts,
 | 
			
		||||
        method: "POST"
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
export enum ReactionLevel {
 | 
			
		||||
    Album = "album",
 | 
			
		||||
    Asset = "asset"
 | 
			
		||||
@ -2817,15 +2842,15 @@ export enum UserAvatarColor {
 | 
			
		||||
    Gray = "gray",
 | 
			
		||||
    Amber = "amber"
 | 
			
		||||
}
 | 
			
		||||
export enum AlbumUserRole {
 | 
			
		||||
    Editor = "editor",
 | 
			
		||||
    Viewer = "viewer"
 | 
			
		||||
}
 | 
			
		||||
export enum UserStatus {
 | 
			
		||||
    Active = "active",
 | 
			
		||||
    Removing = "removing",
 | 
			
		||||
    Deleted = "deleted"
 | 
			
		||||
}
 | 
			
		||||
export enum AlbumUserRole {
 | 
			
		||||
    Editor = "editor",
 | 
			
		||||
    Viewer = "viewer"
 | 
			
		||||
}
 | 
			
		||||
export enum TagTypeEnum {
 | 
			
		||||
    Object = "OBJECT",
 | 
			
		||||
    Face = "FACE",
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander';
 | 
			
		||||
import { UserResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { CliService } from 'src/services/cli.service';
 | 
			
		||||
 | 
			
		||||
const prompt = (inquirer: InquirerService) => {
 | 
			
		||||
  return function ask(admin: UserResponseDto) {
 | 
			
		||||
  return function ask(admin: UserAdminResponseDto) {
 | 
			
		||||
    const { id, oauthId, email, name } = admin;
 | 
			
		||||
    console.log(`Found Admin:
 | 
			
		||||
- ID=${id}
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import {
 | 
			
		||||
  SignUpDto,
 | 
			
		||||
  ValidateAccessTokenResponseDto,
 | 
			
		||||
} from 'src/dtos/auth.dto';
 | 
			
		||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
 | 
			
		||||
import { AuthService, LoginDetails } from 'src/services/auth.service';
 | 
			
		||||
import { respondWithCookie, respondWithoutCookie } from 'src/utils/response';
 | 
			
		||||
@ -40,7 +40,7 @@ export class AuthController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post('admin-sign-up')
 | 
			
		||||
  signUpAdmin(@Body() dto: SignUpDto): Promise<UserResponseDto> {
 | 
			
		||||
  signUpAdmin(@Body() dto: SignUpDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.adminSignUp(dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -54,8 +54,8 @@ export class AuthController {
 | 
			
		||||
  @Post('change-password')
 | 
			
		||||
  @HttpCode(HttpStatus.OK)
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> {
 | 
			
		||||
    return this.service.changePassword(auth, dto).then(mapUser);
 | 
			
		||||
  changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.changePassword(auth, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post('logout')
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ import { SystemMetadataController } from 'src/controllers/system-metadata.contro
 | 
			
		||||
import { TagController } from 'src/controllers/tag.controller';
 | 
			
		||||
import { TimelineController } from 'src/controllers/timeline.controller';
 | 
			
		||||
import { TrashController } from 'src/controllers/trash.controller';
 | 
			
		||||
import { UserAdminController } from 'src/controllers/user-admin.controller';
 | 
			
		||||
import { UserController } from 'src/controllers/user.controller';
 | 
			
		||||
 | 
			
		||||
export const controllers = [
 | 
			
		||||
@ -59,5 +60,6 @@ export const controllers = [
 | 
			
		||||
  TagController,
 | 
			
		||||
  TimelineController,
 | 
			
		||||
  TrashController,
 | 
			
		||||
  UserAdminController,
 | 
			
		||||
  UserController,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import {
 | 
			
		||||
  OAuthCallbackDto,
 | 
			
		||||
  OAuthConfigDto,
 | 
			
		||||
} from 'src/dtos/auth.dto';
 | 
			
		||||
import { UserResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
 | 
			
		||||
import { AuthService, LoginDetails } from 'src/services/auth.service';
 | 
			
		||||
import { respondWithCookie } from 'src/utils/response';
 | 
			
		||||
@ -53,13 +53,13 @@ export class OAuthController {
 | 
			
		||||
 | 
			
		||||
  @Post('link')
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  linkOAuthAccount(@Auth() auth: AuthDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> {
 | 
			
		||||
  linkOAuthAccount(@Auth() auth: AuthDto, @Body() dto: OAuthCallbackDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.link(auth, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post('unlink')
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserResponseDto> {
 | 
			
		||||
  unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.unlink(auth);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								server/src/controllers/user-admin.controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								server/src/controllers/user-admin.controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
 | 
			
		||||
import { ApiTags } from '@nestjs/swagger';
 | 
			
		||||
import { AuthDto } from 'src/dtos/auth.dto';
 | 
			
		||||
import {
 | 
			
		||||
  UserAdminCreateDto,
 | 
			
		||||
  UserAdminDeleteDto,
 | 
			
		||||
  UserAdminResponseDto,
 | 
			
		||||
  UserAdminSearchDto,
 | 
			
		||||
  UserAdminUpdateDto,
 | 
			
		||||
} from 'src/dtos/user.dto';
 | 
			
		||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
 | 
			
		||||
import { UserAdminService } from 'src/services/user-admin.service';
 | 
			
		||||
import { UUIDParamDto } from 'src/validation';
 | 
			
		||||
 | 
			
		||||
@ApiTags('User')
 | 
			
		||||
@Controller('admin/users')
 | 
			
		||||
export class UserAdminController {
 | 
			
		||||
  constructor(private service: UserAdminService) {}
 | 
			
		||||
 | 
			
		||||
  @Get()
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
 | 
			
		||||
    return this.service.search(auth, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post()
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.create(createUserDto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Get(':id')
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.get(auth, id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Put(':id')
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  updateUserAdmin(
 | 
			
		||||
    @Auth() auth: AuthDto,
 | 
			
		||||
    @Param() { id }: UUIDParamDto,
 | 
			
		||||
    @Body() dto: UserAdminUpdateDto,
 | 
			
		||||
  ): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.update(auth, id, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Delete(':id')
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  deleteUserAdmin(
 | 
			
		||||
    @Auth() auth: AuthDto,
 | 
			
		||||
    @Param() { id }: UUIDParamDto,
 | 
			
		||||
    @Body() dto: UserAdminDeleteDto,
 | 
			
		||||
  ): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.delete(auth, id, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post(':id/restore')
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.restore(auth, id);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -10,7 +10,6 @@ import {
 | 
			
		||||
  Param,
 | 
			
		||||
  Post,
 | 
			
		||||
  Put,
 | 
			
		||||
  Query,
 | 
			
		||||
  Res,
 | 
			
		||||
  UploadedFile,
 | 
			
		||||
  UseInterceptors,
 | 
			
		||||
@ -19,7 +18,7 @@ import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
 | 
			
		||||
import { NextFunction, Response } from 'express';
 | 
			
		||||
import { AuthDto } from 'src/dtos/auth.dto';
 | 
			
		||||
import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
 | 
			
		||||
import { CreateUserDto, DeleteUserDto, UpdateUserDto, UserResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
			
		||||
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
 | 
			
		||||
import { FileUploadInterceptor, Route } from 'src/middleware/file-upload.interceptor';
 | 
			
		||||
@ -37,58 +36,28 @@ export class UserController {
 | 
			
		||||
 | 
			
		||||
  @Get()
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  getAllUsers(@Auth() auth: AuthDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> {
 | 
			
		||||
    return this.service.getAll(auth, isAll);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post()
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
 | 
			
		||||
    return this.service.create(createUserDto);
 | 
			
		||||
  searchUsers(): Promise<UserResponseDto[]> {
 | 
			
		||||
    return this.service.search();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Get('me')
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  getMyUserInfo(@Auth() auth: AuthDto): Promise<UserResponseDto> {
 | 
			
		||||
  getMyUser(@Auth() auth: AuthDto): UserAdminResponseDto {
 | 
			
		||||
    return this.service.getMe(auth);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Put('me')
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  updateMyUser(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    return this.service.updateMe(auth, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Get(':id')
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  getUserById(@Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
 | 
			
		||||
  getUser(@Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
 | 
			
		||||
    return this.service.get(id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Delete('profile-image')
 | 
			
		||||
  @HttpCode(HttpStatus.NO_CONTENT)
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  deleteProfileImage(@Auth() auth: AuthDto): Promise<void> {
 | 
			
		||||
    return this.service.deleteProfileImage(auth);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Delete(':id')
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  deleteUser(
 | 
			
		||||
    @Auth() auth: AuthDto,
 | 
			
		||||
    @Param() { id }: UUIDParamDto,
 | 
			
		||||
    @Body() dto: DeleteUserDto,
 | 
			
		||||
  ): Promise<UserResponseDto> {
 | 
			
		||||
    return this.service.delete(auth, id, dto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Post(':id/restore')
 | 
			
		||||
  @Authenticated({ admin: true })
 | 
			
		||||
  restoreUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
 | 
			
		||||
    return this.service.restore(auth, id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: replace with @Put(':id')
 | 
			
		||||
  @Put()
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
 | 
			
		||||
    return this.service.update(auth, updateUserDto);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @UseInterceptors(FileUploadInterceptor)
 | 
			
		||||
  @ApiConsumes('multipart/form-data')
 | 
			
		||||
  @ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto })
 | 
			
		||||
@ -101,6 +70,13 @@ export class UserController {
 | 
			
		||||
    return this.service.createProfileImage(auth, fileInfo);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Delete('profile-image')
 | 
			
		||||
  @HttpCode(HttpStatus.NO_CONTENT)
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
  deleteProfileImage(@Auth() auth: AuthDto): Promise<void> {
 | 
			
		||||
    return this.service.deleteProfileImage(auth);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Get(':id/profile-image')
 | 
			
		||||
  @FileResponse()
 | 
			
		||||
  @Authenticated()
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
 | 
			
		||||
import { BadRequestException } from '@nestjs/common';
 | 
			
		||||
import sanitize from 'sanitize-filename';
 | 
			
		||||
import { SALT_ROUNDS } from 'src/constants';
 | 
			
		||||
import { UserResponseDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserEntity } from 'src/entities/user.entity';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
			
		||||
@ -26,46 +25,6 @@ export class UserCore {
 | 
			
		||||
    instance = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: move auth related checks to the service layer
 | 
			
		||||
  async updateUser(user: UserEntity | UserResponseDto, id: string, dto: Partial<UserEntity>): Promise<UserEntity> {
 | 
			
		||||
    if (!user.isAdmin && user.id !== id) {
 | 
			
		||||
      throw new ForbiddenException('You are not allowed to update this user');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!user.isAdmin) {
 | 
			
		||||
      // Users can never update the isAdmin property.
 | 
			
		||||
      delete dto.isAdmin;
 | 
			
		||||
      delete dto.storageLabel;
 | 
			
		||||
    } else if (dto.isAdmin && user.id !== id) {
 | 
			
		||||
      // Admin cannot create another admin.
 | 
			
		||||
      throw new BadRequestException('The server already has an admin');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.email) {
 | 
			
		||||
      const duplicate = await this.userRepository.getByEmail(dto.email);
 | 
			
		||||
      if (duplicate && duplicate.id !== id) {
 | 
			
		||||
        throw new BadRequestException('Email already in use by another account');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.storageLabel) {
 | 
			
		||||
      const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel);
 | 
			
		||||
      if (duplicate && duplicate.id !== id) {
 | 
			
		||||
        throw new BadRequestException('Storage label already in use by another account');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.password) {
 | 
			
		||||
      dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.storageLabel === '') {
 | 
			
		||||
      dto.storageLabel = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.userRepository.update(id, { ...dto, updatedAt: new Date() });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> {
 | 
			
		||||
    const user = await this.userRepository.getByEmail(dto.email);
 | 
			
		||||
    if (user) {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { ApiProperty } from '@nestjs/swagger';
 | 
			
		||||
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
 | 
			
		||||
import { UserDto, mapSimpleUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { ActivityEntity } from 'src/entities/activity.entity';
 | 
			
		||||
import { Optional, ValidateUUID } from 'src/validation';
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ export class ActivityResponseDto {
 | 
			
		||||
  id!: string;
 | 
			
		||||
  createdAt!: Date;
 | 
			
		||||
  type!: ReactionType;
 | 
			
		||||
  user!: UserDto;
 | 
			
		||||
  user!: UserResponseDto;
 | 
			
		||||
  assetId!: string | null;
 | 
			
		||||
  comment?: string | null;
 | 
			
		||||
}
 | 
			
		||||
@ -73,6 +73,6 @@ export function mapActivity(activity: ActivityEntity): ActivityResponseDto {
 | 
			
		||||
    createdAt: activity.createdAt,
 | 
			
		||||
    comment: activity.comment,
 | 
			
		||||
    type: activity.isLiked ? ReactionType.LIKE : ReactionType.COMMENT,
 | 
			
		||||
    user: mapSimpleUser(activity.user),
 | 
			
		||||
    user: mapUser(activity.user),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
import { plainToInstance } from 'class-transformer';
 | 
			
		||||
import { validate } from 'class-validator';
 | 
			
		||||
import { CreateUserDto, CreateUserOAuthDto, UpdateUserDto } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminCreateDto, UserUpdateMeDto } from 'src/dtos/user.dto';
 | 
			
		||||
 | 
			
		||||
describe('update user DTO', () => {
 | 
			
		||||
  it('should allow emails without a tld', async () => {
 | 
			
		||||
    const someEmail = 'test@test';
 | 
			
		||||
 | 
			
		||||
    const dto = plainToInstance(UpdateUserDto, {
 | 
			
		||||
    const dto = plainToInstance(UserUpdateMeDto, {
 | 
			
		||||
      email: someEmail,
 | 
			
		||||
      id: '3fe388e4-2078-44d7-b36c-39d9dee3a657',
 | 
			
		||||
    });
 | 
			
		||||
@ -18,22 +18,22 @@ describe('update user DTO', () => {
 | 
			
		||||
 | 
			
		||||
describe('create user DTO', () => {
 | 
			
		||||
  it('validates the email', async () => {
 | 
			
		||||
    const params: Partial<CreateUserDto> = {
 | 
			
		||||
    const params: Partial<UserAdminCreateDto> = {
 | 
			
		||||
      email: undefined,
 | 
			
		||||
      password: 'password',
 | 
			
		||||
      name: 'name',
 | 
			
		||||
    };
 | 
			
		||||
    let dto: CreateUserDto = plainToInstance(CreateUserDto, params);
 | 
			
		||||
    let dto: UserAdminCreateDto = plainToInstance(UserAdminCreateDto, params);
 | 
			
		||||
    let errors = await validate(dto);
 | 
			
		||||
    expect(errors).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
    params.email = 'invalid email';
 | 
			
		||||
    dto = plainToInstance(CreateUserDto, params);
 | 
			
		||||
    dto = plainToInstance(UserAdminCreateDto, params);
 | 
			
		||||
    errors = await validate(dto);
 | 
			
		||||
    expect(errors).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
    params.email = 'valid@email.com';
 | 
			
		||||
    dto = plainToInstance(CreateUserDto, params);
 | 
			
		||||
    dto = plainToInstance(UserAdminCreateDto, params);
 | 
			
		||||
    errors = await validate(dto);
 | 
			
		||||
    expect(errors).toHaveLength(0);
 | 
			
		||||
  });
 | 
			
		||||
@ -41,7 +41,7 @@ describe('create user DTO', () => {
 | 
			
		||||
  it('should allow emails without a tld', async () => {
 | 
			
		||||
    const someEmail = 'test@test';
 | 
			
		||||
 | 
			
		||||
    const dto = plainToInstance(CreateUserDto, {
 | 
			
		||||
    const dto = plainToInstance(UserAdminCreateDto, {
 | 
			
		||||
      email: someEmail,
 | 
			
		||||
      password: 'some password',
 | 
			
		||||
      name: 'some name',
 | 
			
		||||
@ -51,18 +51,3 @@ describe('create user DTO', () => {
 | 
			
		||||
    expect(dto.email).toEqual(someEmail);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('create user oauth DTO', () => {
 | 
			
		||||
  it('should allow emails without a tld', async () => {
 | 
			
		||||
    const someEmail = 'test@test';
 | 
			
		||||
 | 
			
		||||
    const dto = plainToInstance(CreateUserOAuthDto, {
 | 
			
		||||
      email: someEmail,
 | 
			
		||||
      oauthId: 'some oauth id',
 | 
			
		||||
      name: 'some name',
 | 
			
		||||
    });
 | 
			
		||||
    const errors = await validate(dto);
 | 
			
		||||
    expect(errors).toHaveLength(0);
 | 
			
		||||
    expect(dto.email).toEqual(someEmail);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,63 @@
 | 
			
		||||
import { ApiProperty } from '@nestjs/swagger';
 | 
			
		||||
import { Transform } from 'class-transformer';
 | 
			
		||||
import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString, IsUUID } from 'class-validator';
 | 
			
		||||
import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator';
 | 
			
		||||
import { UserAvatarColor } from 'src/entities/user-metadata.entity';
 | 
			
		||||
import { UserEntity, UserStatus } from 'src/entities/user.entity';
 | 
			
		||||
import { getPreferences } from 'src/utils/preferences';
 | 
			
		||||
import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation';
 | 
			
		||||
 | 
			
		||||
export class CreateUserDto {
 | 
			
		||||
export class UserUpdateMeDto {
 | 
			
		||||
  @Optional()
 | 
			
		||||
  @IsEmail({ require_tld: false })
 | 
			
		||||
  @Transform(toEmail)
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  // TODO: migrate to the other change password endpoint
 | 
			
		||||
  @Optional()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  password?: string;
 | 
			
		||||
 | 
			
		||||
  @Optional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  name?: string;
 | 
			
		||||
 | 
			
		||||
  @ValidateBoolean({ optional: true })
 | 
			
		||||
  memoriesEnabled?: boolean;
 | 
			
		||||
 | 
			
		||||
  @Optional()
 | 
			
		||||
  @IsEnum(UserAvatarColor)
 | 
			
		||||
  @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
 | 
			
		||||
  avatarColor?: UserAvatarColor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UserResponseDto {
 | 
			
		||||
  id!: string;
 | 
			
		||||
  name!: string;
 | 
			
		||||
  email!: string;
 | 
			
		||||
  profileImagePath!: string;
 | 
			
		||||
  @IsEnum(UserAvatarColor)
 | 
			
		||||
  @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
 | 
			
		||||
  avatarColor!: UserAvatarColor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const mapUser = (entity: UserEntity): UserResponseDto => {
 | 
			
		||||
  return {
 | 
			
		||||
    id: entity.id,
 | 
			
		||||
    email: entity.email,
 | 
			
		||||
    name: entity.name,
 | 
			
		||||
    profileImagePath: entity.profileImagePath,
 | 
			
		||||
    avatarColor: getPreferences(entity).avatar.color,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class UserAdminSearchDto {
 | 
			
		||||
  @ValidateBoolean({ optional: true })
 | 
			
		||||
  withDeleted?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UserAdminCreateDto {
 | 
			
		||||
  @IsEmail({ require_tld: false })
 | 
			
		||||
  @Transform(toEmail)
 | 
			
		||||
  email!: string;
 | 
			
		||||
@ -41,23 +92,7 @@ export class CreateUserDto {
 | 
			
		||||
  notify?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CreateUserOAuthDto {
 | 
			
		||||
  @IsEmail({ require_tld: false })
 | 
			
		||||
  @Transform(({ value }) => value?.toLowerCase())
 | 
			
		||||
  email!: string;
 | 
			
		||||
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  oauthId!: string;
 | 
			
		||||
 | 
			
		||||
  name?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class DeleteUserDto {
 | 
			
		||||
  @ValidateBoolean({ optional: true })
 | 
			
		||||
  force?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UpdateUserDto {
 | 
			
		||||
export class UserAdminUpdateDto {
 | 
			
		||||
  @Optional()
 | 
			
		||||
  @IsEmail({ require_tld: false })
 | 
			
		||||
  @Transform(toEmail)
 | 
			
		||||
@ -73,18 +108,10 @@ export class UpdateUserDto {
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  name?: string;
 | 
			
		||||
 | 
			
		||||
  @Optional()
 | 
			
		||||
  @Optional({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @Transform(toSanitized)
 | 
			
		||||
  storageLabel?: string;
 | 
			
		||||
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsUUID('4')
 | 
			
		||||
  @ApiProperty({ format: 'uuid' })
 | 
			
		||||
  id!: string;
 | 
			
		||||
 | 
			
		||||
  @ValidateBoolean({ optional: true })
 | 
			
		||||
  isAdmin?: boolean;
 | 
			
		||||
  storageLabel?: string | null;
 | 
			
		||||
 | 
			
		||||
  @ValidateBoolean({ optional: true })
 | 
			
		||||
  shouldChangePassword?: boolean;
 | 
			
		||||
@ -104,17 +131,12 @@ export class UpdateUserDto {
 | 
			
		||||
  quotaSizeInBytes?: number | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UserDto {
 | 
			
		||||
  id!: string;
 | 
			
		||||
  name!: string;
 | 
			
		||||
  email!: string;
 | 
			
		||||
  profileImagePath!: string;
 | 
			
		||||
  @IsEnum(UserAvatarColor)
 | 
			
		||||
  @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
 | 
			
		||||
  avatarColor!: UserAvatarColor;
 | 
			
		||||
export class UserAdminDeleteDto {
 | 
			
		||||
  @ValidateBoolean({ optional: true })
 | 
			
		||||
  force?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UserResponseDto extends UserDto {
 | 
			
		||||
export class UserAdminResponseDto extends UserResponseDto {
 | 
			
		||||
  storageLabel!: string | null;
 | 
			
		||||
  shouldChangePassword!: boolean;
 | 
			
		||||
  isAdmin!: boolean;
 | 
			
		||||
@ -131,19 +153,9 @@ export class UserResponseDto extends UserDto {
 | 
			
		||||
  status!: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const mapSimpleUser = (entity: UserEntity): UserDto => {
 | 
			
		||||
export function mapUserAdmin(entity: UserEntity): UserAdminResponseDto {
 | 
			
		||||
  return {
 | 
			
		||||
    id: entity.id,
 | 
			
		||||
    email: entity.email,
 | 
			
		||||
    name: entity.name,
 | 
			
		||||
    profileImagePath: entity.profileImagePath,
 | 
			
		||||
    avatarColor: getPreferences(entity).avatar.color,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function mapUser(entity: UserEntity): UserResponseDto {
 | 
			
		||||
  return {
 | 
			
		||||
    ...mapSimpleUser(entity),
 | 
			
		||||
    ...mapUser(entity),
 | 
			
		||||
    storageLabel: entity.storageLabel,
 | 
			
		||||
    shouldChangePassword: entity.shouldChangePassword,
 | 
			
		||||
    isAdmin: entity.isAdmin,
 | 
			
		||||
 | 
			
		||||
@ -22,13 +22,17 @@ FROM
 | 
			
		||||
      "APIKeyEntity__APIKeyEntity_user"."status" AS "APIKeyEntity__APIKeyEntity_user_status",
 | 
			
		||||
      "APIKeyEntity__APIKeyEntity_user"."updatedAt" AS "APIKeyEntity__APIKeyEntity_user_updatedAt",
 | 
			
		||||
      "APIKeyEntity__APIKeyEntity_user"."quotaSizeInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaSizeInBytes",
 | 
			
		||||
      "APIKeyEntity__APIKeyEntity_user"."quotaUsageInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaUsageInBytes"
 | 
			
		||||
      "APIKeyEntity__APIKeyEntity_user"."quotaUsageInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaUsageInBytes",
 | 
			
		||||
      "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."userId" AS "7f5f7a38bf327bfbbf826778460704c9a50fe6f4_userId",
 | 
			
		||||
      "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."key" AS "7f5f7a38bf327bfbbf826778460704c9a50fe6f4_key",
 | 
			
		||||
      "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."value" AS "7f5f7a38bf327bfbbf826778460704c9a50fe6f4_value"
 | 
			
		||||
    FROM
 | 
			
		||||
      "api_keys" "APIKeyEntity"
 | 
			
		||||
      LEFT JOIN "users" "APIKeyEntity__APIKeyEntity_user" ON "APIKeyEntity__APIKeyEntity_user"."id" = "APIKeyEntity"."userId"
 | 
			
		||||
      AND (
 | 
			
		||||
        "APIKeyEntity__APIKeyEntity_user"."deletedAt" IS NULL
 | 
			
		||||
      )
 | 
			
		||||
      LEFT JOIN "user_metadata" "7f5f7a38bf327bfbbf826778460704c9a50fe6f4" ON "7f5f7a38bf327bfbbf826778460704c9a50fe6f4"."userId" = "APIKeyEntity__APIKeyEntity_user"."id"
 | 
			
		||||
    WHERE
 | 
			
		||||
      (("APIKeyEntity"."key" = $1))
 | 
			
		||||
  ) "distinctAlias"
 | 
			
		||||
 | 
			
		||||
@ -38,13 +38,17 @@ FROM
 | 
			
		||||
      "SessionEntity__SessionEntity_user"."status" AS "SessionEntity__SessionEntity_user_status",
 | 
			
		||||
      "SessionEntity__SessionEntity_user"."updatedAt" AS "SessionEntity__SessionEntity_user_updatedAt",
 | 
			
		||||
      "SessionEntity__SessionEntity_user"."quotaSizeInBytes" AS "SessionEntity__SessionEntity_user_quotaSizeInBytes",
 | 
			
		||||
      "SessionEntity__SessionEntity_user"."quotaUsageInBytes" AS "SessionEntity__SessionEntity_user_quotaUsageInBytes"
 | 
			
		||||
      "SessionEntity__SessionEntity_user"."quotaUsageInBytes" AS "SessionEntity__SessionEntity_user_quotaUsageInBytes",
 | 
			
		||||
      "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."userId" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_userId",
 | 
			
		||||
      "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."key" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_key",
 | 
			
		||||
      "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."value" AS "469e6aa7ff79eff78f8441f91ba15bb07d3634dd_value"
 | 
			
		||||
    FROM
 | 
			
		||||
      "sessions" "SessionEntity"
 | 
			
		||||
      LEFT JOIN "users" "SessionEntity__SessionEntity_user" ON "SessionEntity__SessionEntity_user"."id" = "SessionEntity"."userId"
 | 
			
		||||
      AND (
 | 
			
		||||
        "SessionEntity__SessionEntity_user"."deletedAt" IS NULL
 | 
			
		||||
      )
 | 
			
		||||
      LEFT JOIN "user_metadata" "469e6aa7ff79eff78f8441f91ba15bb07d3634dd" ON "469e6aa7ff79eff78f8441f91ba15bb07d3634dd"."userId" = "SessionEntity__SessionEntity_user"."id"
 | 
			
		||||
    WHERE
 | 
			
		||||
      (("SessionEntity"."token" = $1))
 | 
			
		||||
  ) "distinctAlias"
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,9 @@ export class ApiKeyRepository implements IKeyRepository {
 | 
			
		||||
      },
 | 
			
		||||
      where: { key: hashedToken },
 | 
			
		||||
      relations: {
 | 
			
		||||
        user: true,
 | 
			
		||||
        user: {
 | 
			
		||||
          metadata: true,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,14 @@ export class SessionRepository implements ISessionRepository {
 | 
			
		||||
 | 
			
		||||
  @GenerateSql({ params: [DummyValue.STRING] })
 | 
			
		||||
  getByToken(token: string): Promise<SessionEntity | null> {
 | 
			
		||||
    return this.repository.findOne({ where: { token }, relations: { user: true } });
 | 
			
		||||
    return this.repository.findOne({
 | 
			
		||||
      where: { token },
 | 
			
		||||
      relations: {
 | 
			
		||||
        user: {
 | 
			
		||||
          metadata: true,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getByUserId(userId: string): Promise<SessionEntity[]> {
 | 
			
		||||
 | 
			
		||||
@ -138,6 +138,7 @@ describe('AuthService', () => {
 | 
			
		||||
        email: 'test@immich.com',
 | 
			
		||||
        password: 'hash-password',
 | 
			
		||||
      } as UserEntity);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await sut.changePassword(auth, dto);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ import { DateTime } from 'luxon';
 | 
			
		||||
import { IncomingHttpHeaders } from 'node:http';
 | 
			
		||||
import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client';
 | 
			
		||||
import { SystemConfig } from 'src/config';
 | 
			
		||||
import { AuthType, LOGIN_URL, MOBILE_REDIRECT } from 'src/constants';
 | 
			
		||||
import { AuthType, LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
 | 
			
		||||
import { SystemConfigCore } from 'src/cores/system-config.core';
 | 
			
		||||
import { UserCore } from 'src/cores/user.core';
 | 
			
		||||
import {
 | 
			
		||||
@ -27,7 +27,7 @@ import {
 | 
			
		||||
  SignUpDto,
 | 
			
		||||
  mapLoginResponse,
 | 
			
		||||
} from 'src/dtos/auth.dto';
 | 
			
		||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserEntity } from 'src/entities/user.entity';
 | 
			
		||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
@ -109,7 +109,7 @@ export class AuthService {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async changePassword(auth: AuthDto, dto: ChangePasswordDto) {
 | 
			
		||||
  async changePassword(auth: AuthDto, dto: ChangePasswordDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const { password, newPassword } = dto;
 | 
			
		||||
    const user = await this.userRepository.getByEmail(auth.user.email, true);
 | 
			
		||||
    if (!user) {
 | 
			
		||||
@ -121,10 +121,14 @@ export class AuthService {
 | 
			
		||||
      throw new BadRequestException('Wrong password');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this.userCore.updateUser(auth.user, auth.user.id, { password: newPassword });
 | 
			
		||||
    const hashedPassword = await this.cryptoRepository.hashBcrypt(newPassword, SALT_ROUNDS);
 | 
			
		||||
 | 
			
		||||
    const updatedUser = await this.userRepository.update(user.id, { password: hashedPassword });
 | 
			
		||||
 | 
			
		||||
    return mapUserAdmin(updatedUser);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async adminSignUp(dto: SignUpDto): Promise<UserResponseDto> {
 | 
			
		||||
  async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const adminUser = await this.userRepository.getAdmin();
 | 
			
		||||
    if (adminUser) {
 | 
			
		||||
      throw new BadRequestException('The server already has an admin');
 | 
			
		||||
@ -138,7 +142,7 @@ export class AuthService {
 | 
			
		||||
      storageLabel: 'admin',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return mapUser(admin);
 | 
			
		||||
    return mapUserAdmin(admin);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthDto> {
 | 
			
		||||
@ -237,7 +241,7 @@ export class AuthService {
 | 
			
		||||
    return this.createLoginResponse(user, loginDetails);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserResponseDto> {
 | 
			
		||||
  async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const config = await this.configCore.getConfig();
 | 
			
		||||
    const { sub: oauthId } = await this.getOAuthProfile(config, dto.url);
 | 
			
		||||
    const duplicate = await this.userRepository.getByOAuthId(oauthId);
 | 
			
		||||
@ -245,11 +249,14 @@ export class AuthService {
 | 
			
		||||
      this.logger.warn(`OAuth link account failed: sub is already linked to another user (${duplicate.email}).`);
 | 
			
		||||
      throw new BadRequestException('This OAuth account has already been linked to another user.');
 | 
			
		||||
    }
 | 
			
		||||
    return mapUser(await this.userRepository.update(auth.user.id, { oauthId }));
 | 
			
		||||
 | 
			
		||||
    const user = await this.userRepository.update(auth.user.id, { oauthId });
 | 
			
		||||
    return mapUserAdmin(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async unlink(auth: AuthDto): Promise<UserResponseDto> {
 | 
			
		||||
    return mapUser(await this.userRepository.update(auth.user.id, { oauthId: '' }));
 | 
			
		||||
  async unlink(auth: AuthDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const user = await this.userRepository.update(auth.user.id, { oauthId: '' });
 | 
			
		||||
    return mapUserAdmin(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async getLogoutEndpoint(authType: AuthType): Promise<string> {
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import { SALT_ROUNDS } from 'src/constants';
 | 
			
		||||
import { SystemConfigCore } from 'src/cores/system-config.core';
 | 
			
		||||
import { UserCore } from 'src/cores/user.core';
 | 
			
		||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
			
		||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
 | 
			
		||||
@ -10,7 +10,6 @@ import { IUserRepository } from 'src/interfaces/user.interface';
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class CliService {
 | 
			
		||||
  private configCore: SystemConfigCore;
 | 
			
		||||
  private userCore: UserCore;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
 | 
			
		||||
@ -18,26 +17,26 @@ export class CliService {
 | 
			
		||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
			
		||||
    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.userCore = UserCore.create(cryptoRepository, userRepository);
 | 
			
		||||
    this.logger.setContext(CliService.name);
 | 
			
		||||
    this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async listUsers(): Promise<UserResponseDto[]> {
 | 
			
		||||
  async listUsers(): Promise<UserAdminResponseDto[]> {
 | 
			
		||||
    const users = await this.userRepository.getList({ withDeleted: true });
 | 
			
		||||
    return users.map((user) => mapUser(user));
 | 
			
		||||
    return users.map((user) => mapUserAdmin(user));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async resetAdminPassword(ask: (admin: UserResponseDto) => Promise<string | undefined>) {
 | 
			
		||||
  async resetAdminPassword(ask: (admin: UserAdminResponseDto) => Promise<string | undefined>) {
 | 
			
		||||
    const admin = await this.userRepository.getAdmin();
 | 
			
		||||
    if (!admin) {
 | 
			
		||||
      throw new Error('Admin account does not exist');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const providedPassword = await ask(mapUser(admin));
 | 
			
		||||
    const providedPassword = await ask(mapUserAdmin(admin));
 | 
			
		||||
    const password = providedPassword || this.cryptoRepository.newPassword(24);
 | 
			
		||||
    const hashedPassword = await this.cryptoRepository.hashBcrypt(password, SALT_ROUNDS);
 | 
			
		||||
 | 
			
		||||
    await this.userCore.updateUser(admin, admin.id, { password });
 | 
			
		||||
    await this.userRepository.update(admin.id, { password: hashedPassword });
 | 
			
		||||
 | 
			
		||||
    return { admin, password, provided: !!providedPassword };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ import { SystemMetadataService } from 'src/services/system-metadata.service';
 | 
			
		||||
import { TagService } from 'src/services/tag.service';
 | 
			
		||||
import { TimelineService } from 'src/services/timeline.service';
 | 
			
		||||
import { TrashService } from 'src/services/trash.service';
 | 
			
		||||
import { UserAdminService } from 'src/services/user-admin.service';
 | 
			
		||||
import { UserService } from 'src/services/user.service';
 | 
			
		||||
import { VersionService } from 'src/services/version.service';
 | 
			
		||||
 | 
			
		||||
@ -73,5 +74,6 @@ export const services = [
 | 
			
		||||
  TimelineService,
 | 
			
		||||
  TrashService,
 | 
			
		||||
  UserService,
 | 
			
		||||
  UserAdminService,
 | 
			
		||||
  VersionService,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,4 @@
 | 
			
		||||
import { BadRequestException } from '@nestjs/common';
 | 
			
		||||
import { PartnerResponseDto } from 'src/dtos/partner.dto';
 | 
			
		||||
import { UserAvatarColor } from 'src/entities/user-metadata.entity';
 | 
			
		||||
import { IAccessRepository } from 'src/interfaces/access.interface';
 | 
			
		||||
import { IPartnerRepository, PartnerDirection } from 'src/interfaces/partner.interface';
 | 
			
		||||
import { PartnerService } from 'src/services/partner.service';
 | 
			
		||||
@ -9,45 +7,6 @@ import { partnerStub } from 'test/fixtures/partner.stub';
 | 
			
		||||
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
 | 
			
		||||
import { Mocked } from 'vitest';
 | 
			
		||||
 | 
			
		||||
const responseDto = {
 | 
			
		||||
  admin: <PartnerResponseDto>{
 | 
			
		||||
    email: 'admin@test.com',
 | 
			
		||||
    name: 'admin_name',
 | 
			
		||||
    id: 'admin_id',
 | 
			
		||||
    isAdmin: true,
 | 
			
		||||
    oauthId: '',
 | 
			
		||||
    profileImagePath: '',
 | 
			
		||||
    shouldChangePassword: false,
 | 
			
		||||
    storageLabel: 'admin',
 | 
			
		||||
    createdAt: new Date('2021-01-01'),
 | 
			
		||||
    deletedAt: null,
 | 
			
		||||
    updatedAt: new Date('2021-01-01'),
 | 
			
		||||
    memoriesEnabled: true,
 | 
			
		||||
    avatarColor: UserAvatarColor.GRAY,
 | 
			
		||||
    quotaSizeInBytes: null,
 | 
			
		||||
    inTimeline: true,
 | 
			
		||||
    quotaUsageInBytes: 0,
 | 
			
		||||
  },
 | 
			
		||||
  user1: <PartnerResponseDto>{
 | 
			
		||||
    email: 'immich@test.com',
 | 
			
		||||
    name: 'immich_name',
 | 
			
		||||
    id: 'user-id',
 | 
			
		||||
    isAdmin: false,
 | 
			
		||||
    oauthId: '',
 | 
			
		||||
    profileImagePath: '',
 | 
			
		||||
    shouldChangePassword: false,
 | 
			
		||||
    storageLabel: null,
 | 
			
		||||
    createdAt: new Date('2021-01-01'),
 | 
			
		||||
    deletedAt: null,
 | 
			
		||||
    updatedAt: new Date('2021-01-01'),
 | 
			
		||||
    memoriesEnabled: true,
 | 
			
		||||
    avatarColor: UserAvatarColor.PRIMARY,
 | 
			
		||||
    inTimeline: true,
 | 
			
		||||
    quotaSizeInBytes: null,
 | 
			
		||||
    quotaUsageInBytes: 0,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe(PartnerService.name, () => {
 | 
			
		||||
  let sut: PartnerService;
 | 
			
		||||
  let partnerMock: Mocked<IPartnerRepository>;
 | 
			
		||||
@ -65,13 +24,13 @@ describe(PartnerService.name, () => {
 | 
			
		||||
  describe('getAll', () => {
 | 
			
		||||
    it("should return a list of partners with whom I've shared my library", async () => {
 | 
			
		||||
      partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]);
 | 
			
		||||
      await expect(sut.getAll(authStub.user1, PartnerDirection.SharedBy)).resolves.toEqual([responseDto.admin]);
 | 
			
		||||
      await expect(sut.getAll(authStub.user1, PartnerDirection.SharedBy)).resolves.toBeDefined();
 | 
			
		||||
      expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should return a list of partners who have shared their libraries with me', async () => {
 | 
			
		||||
      partnerMock.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]);
 | 
			
		||||
      await expect(sut.getAll(authStub.user1, PartnerDirection.SharedWith)).resolves.toEqual([responseDto.admin]);
 | 
			
		||||
      await expect(sut.getAll(authStub.user1, PartnerDirection.SharedWith)).resolves.toBeDefined();
 | 
			
		||||
      expect(partnerMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
@ -81,7 +40,7 @@ describe(PartnerService.name, () => {
 | 
			
		||||
      partnerMock.get.mockResolvedValue(null);
 | 
			
		||||
      partnerMock.create.mockResolvedValue(partnerStub.adminToUser1);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toEqual(responseDto.user1);
 | 
			
		||||
      await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toBeDefined();
 | 
			
		||||
 | 
			
		||||
      expect(partnerMock.create).toHaveBeenCalledWith({
 | 
			
		||||
        sharedById: authStub.admin.user.id,
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ export class PartnerService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const partner = await this.repository.create(partnerId);
 | 
			
		||||
    return this.mapToPartnerEntity(partner, PartnerDirection.SharedBy);
 | 
			
		||||
    return this.mapPartner(partner, PartnerDirection.SharedBy);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async remove(auth: AuthDto, sharedWithId: string): Promise<void> {
 | 
			
		||||
@ -44,7 +44,7 @@ export class PartnerService {
 | 
			
		||||
    return partners
 | 
			
		||||
      .filter((partner) => partner.sharedBy && partner.sharedWith) // Filter out soft deleted users
 | 
			
		||||
      .filter((partner) => partner[key] === auth.user.id)
 | 
			
		||||
      .map((partner) => this.mapToPartnerEntity(partner, direction));
 | 
			
		||||
      .map((partner) => this.mapPartner(partner, direction));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async update(auth: AuthDto, sharedById: string, dto: UpdatePartnerDto): Promise<PartnerResponseDto> {
 | 
			
		||||
@ -52,10 +52,10 @@ export class PartnerService {
 | 
			
		||||
    const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id };
 | 
			
		||||
 | 
			
		||||
    const entity = await this.repository.update({ ...partnerId, inTimeline: dto.inTimeline });
 | 
			
		||||
    return this.mapToPartnerEntity(entity, PartnerDirection.SharedWith);
 | 
			
		||||
    return this.mapPartner(entity, PartnerDirection.SharedWith);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private mapToPartnerEntity(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto {
 | 
			
		||||
  private mapPartner(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto {
 | 
			
		||||
    // this is opposite to return the non-me user of the "partner"
 | 
			
		||||
    const user = mapUser(
 | 
			
		||||
      direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										197
									
								
								server/src/services/user-admin.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								server/src/services/user-admin.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,197 @@
 | 
			
		||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
 | 
			
		||||
import { mapUserAdmin } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserStatus } from 'src/entities/user.entity';
 | 
			
		||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
			
		||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
			
		||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
			
		||||
import { UserAdminService } from 'src/services/user-admin.service';
 | 
			
		||||
import { authStub } from 'test/fixtures/auth.stub';
 | 
			
		||||
import { userStub } from 'test/fixtures/user.stub';
 | 
			
		||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
 | 
			
		||||
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
 | 
			
		||||
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
 | 
			
		||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
 | 
			
		||||
import { newUserRepositoryMock } from 'test/repositories/user.repository.mock';
 | 
			
		||||
import { Mocked, describe } from 'vitest';
 | 
			
		||||
 | 
			
		||||
describe(UserAdminService.name, () => {
 | 
			
		||||
  let sut: UserAdminService;
 | 
			
		||||
  let userMock: Mocked<IUserRepository>;
 | 
			
		||||
  let cryptoRepositoryMock: Mocked<ICryptoRepository>;
 | 
			
		||||
 | 
			
		||||
  let albumMock: Mocked<IAlbumRepository>;
 | 
			
		||||
  let jobMock: Mocked<IJobRepository>;
 | 
			
		||||
  let loggerMock: Mocked<ILoggerRepository>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    albumMock = newAlbumRepositoryMock();
 | 
			
		||||
    cryptoRepositoryMock = newCryptoRepositoryMock();
 | 
			
		||||
    jobMock = newJobRepositoryMock();
 | 
			
		||||
    userMock = newUserRepositoryMock();
 | 
			
		||||
    loggerMock = newLoggerRepositoryMock();
 | 
			
		||||
 | 
			
		||||
    sut = new UserAdminService(albumMock, cryptoRepositoryMock, jobMock, userMock, loggerMock);
 | 
			
		||||
 | 
			
		||||
    userMock.get.mockImplementation((userId) =>
 | 
			
		||||
      Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? null),
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('create', () => {
 | 
			
		||||
    it('should not create a user if there is no local admin account', async () => {
 | 
			
		||||
      userMock.getAdmin.mockResolvedValueOnce(null);
 | 
			
		||||
 | 
			
		||||
      await expect(
 | 
			
		||||
        sut.create({
 | 
			
		||||
          email: 'john_smith@email.com',
 | 
			
		||||
          name: 'John Smith',
 | 
			
		||||
          password: 'password',
 | 
			
		||||
        }),
 | 
			
		||||
      ).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should create user', async () => {
 | 
			
		||||
      userMock.getAdmin.mockResolvedValue(userStub.admin);
 | 
			
		||||
      userMock.create.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(
 | 
			
		||||
        sut.create({
 | 
			
		||||
          email: userStub.user1.email,
 | 
			
		||||
          name: userStub.user1.name,
 | 
			
		||||
          password: 'password',
 | 
			
		||||
          storageLabel: 'label',
 | 
			
		||||
        }),
 | 
			
		||||
      ).resolves.toEqual(mapUserAdmin(userStub.user1));
 | 
			
		||||
 | 
			
		||||
      expect(userMock.getAdmin).toBeCalled();
 | 
			
		||||
      expect(userMock.create).toBeCalledWith({
 | 
			
		||||
        email: userStub.user1.email,
 | 
			
		||||
        name: userStub.user1.name,
 | 
			
		||||
        storageLabel: 'label',
 | 
			
		||||
        password: expect.anything(),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('update', () => {
 | 
			
		||||
    it('should update the user', async () => {
 | 
			
		||||
      const update = {
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
        email: 'immich@test.com',
 | 
			
		||||
        storageLabel: 'storage_label',
 | 
			
		||||
      };
 | 
			
		||||
      userMock.getByEmail.mockResolvedValue(null);
 | 
			
		||||
      userMock.getByStorageLabel.mockResolvedValue(null);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await sut.update(authStub.user1, userStub.user1.id, update);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.getByEmail).toHaveBeenCalledWith(update.email);
 | 
			
		||||
      expect(userMock.getByStorageLabel).toHaveBeenCalledWith(update.storageLabel);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not set an empty string for storage label', async () => {
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
      await sut.update(authStub.admin, userStub.user1.id, { storageLabel: '' });
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        storageLabel: null,
 | 
			
		||||
        updatedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not change an email to one already in use', async () => {
 | 
			
		||||
      const dto = { id: userStub.user1.id, email: 'updated@test.com' };
 | 
			
		||||
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.getByEmail.mockResolvedValue(userStub.admin);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.update(authStub.admin, userStub.user1.id, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not let the admin change the storage label to one already in use', async () => {
 | 
			
		||||
      const dto = { id: userStub.user1.id, storageLabel: 'admin' };
 | 
			
		||||
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.getByStorageLabel.mockResolvedValue(userStub.admin);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.update(authStub.admin, userStub.user1.id, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('update user information should throw error if user not found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValueOnce(null);
 | 
			
		||||
 | 
			
		||||
      await expect(
 | 
			
		||||
        sut.update(authStub.admin, userStub.user1.id, { shouldChangePassword: true }),
 | 
			
		||||
      ).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('delete', () => {
 | 
			
		||||
    it('should throw error if user could not be found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(null);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toThrowError(BadRequestException);
 | 
			
		||||
      expect(userMock.delete).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('cannot delete admin user', async () => {
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toBeInstanceOf(ForbiddenException);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require the auth user be an admin', async () => {
 | 
			
		||||
      await expect(sut.delete(authStub.user1, authStub.admin.user.id, {})).rejects.toBeInstanceOf(ForbiddenException);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.delete).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should delete user', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.user1.id, {})).resolves.toEqual(mapUserAdmin(userStub.user1));
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        status: UserStatus.DELETED,
 | 
			
		||||
        deletedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should force delete user', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.user1.id, { force: true })).resolves.toEqual(
 | 
			
		||||
        mapUserAdmin(userStub.user1),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        status: UserStatus.REMOVING,
 | 
			
		||||
        deletedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
      expect(jobMock.queue).toHaveBeenCalledWith({
 | 
			
		||||
        name: JobName.USER_DELETION,
 | 
			
		||||
        data: { id: userStub.user1.id, force: true },
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('restore', () => {
 | 
			
		||||
    it('should throw error if user could not be found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(null);
 | 
			
		||||
      await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException);
 | 
			
		||||
      expect(userMock.update).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should restore an user', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(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 });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										154
									
								
								server/src/services/user-admin.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								server/src/services/user-admin.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,154 @@
 | 
			
		||||
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import { SALT_ROUNDS } from 'src/constants';
 | 
			
		||||
import { UserCore } from 'src/cores/user.core';
 | 
			
		||||
import { AuthDto } from 'src/dtos/auth.dto';
 | 
			
		||||
import {
 | 
			
		||||
  UserAdminCreateDto,
 | 
			
		||||
  UserAdminDeleteDto,
 | 
			
		||||
  UserAdminResponseDto,
 | 
			
		||||
  UserAdminSearchDto,
 | 
			
		||||
  UserAdminUpdateDto,
 | 
			
		||||
  mapUserAdmin,
 | 
			
		||||
} from 'src/dtos/user.dto';
 | 
			
		||||
import { UserMetadataKey } from 'src/entities/user-metadata.entity';
 | 
			
		||||
import { UserStatus } from 'src/entities/user.entity';
 | 
			
		||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
			
		||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
 | 
			
		||||
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
 | 
			
		||||
import { getPreferences, getPreferencesPartial } from 'src/utils/preferences';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class UserAdminService {
 | 
			
		||||
  private userCore: UserCore;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
 | 
			
		||||
    @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
 | 
			
		||||
    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
			
		||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
			
		||||
    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.userCore = UserCore.create(cryptoRepository, userRepository);
 | 
			
		||||
    this.logger.setContext(UserAdminService.name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async search(auth: AuthDto, dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
 | 
			
		||||
    const users = await this.userRepository.getList({ withDeleted: dto.withDeleted });
 | 
			
		||||
    return users.map((user) => mapUserAdmin(user));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async create(dto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const { memoriesEnabled, notify, ...rest } = dto;
 | 
			
		||||
    let user = await this.userCore.createUser(rest);
 | 
			
		||||
 | 
			
		||||
    // TODO remove and replace with entire dto.preferences config
 | 
			
		||||
    if (memoriesEnabled === false) {
 | 
			
		||||
      await this.userRepository.upsertMetadata(user.id, {
 | 
			
		||||
        key: UserMetadataKey.PREFERENCES,
 | 
			
		||||
        value: { memories: { enabled: false } },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      user = await this.findOrFail(user.id, {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const tempPassword = user.shouldChangePassword ? rest.password : undefined;
 | 
			
		||||
    if (notify) {
 | 
			
		||||
      await this.jobRepository.queue({ name: JobName.NOTIFY_SIGNUP, data: { id: user.id, tempPassword } });
 | 
			
		||||
    }
 | 
			
		||||
    return mapUserAdmin(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const user = await this.findOrFail(id, { withDeleted: true });
 | 
			
		||||
    return mapUserAdmin(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async update(auth: AuthDto, id: string, dto: UserAdminUpdateDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const user = await this.findOrFail(id, {});
 | 
			
		||||
 | 
			
		||||
    if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
 | 
			
		||||
      await this.userRepository.syncUsage(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO replace with entire preferences object
 | 
			
		||||
    if (dto.memoriesEnabled !== undefined || dto.avatarColor) {
 | 
			
		||||
      const newPreferences = getPreferences(user);
 | 
			
		||||
      if (dto.memoriesEnabled !== undefined) {
 | 
			
		||||
        newPreferences.memories.enabled = dto.memoriesEnabled;
 | 
			
		||||
        delete dto.memoriesEnabled;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (dto.avatarColor) {
 | 
			
		||||
        newPreferences.avatar.color = dto.avatarColor;
 | 
			
		||||
        delete dto.avatarColor;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await this.userRepository.upsertMetadata(id, {
 | 
			
		||||
        key: UserMetadataKey.PREFERENCES,
 | 
			
		||||
        value: getPreferencesPartial(user, newPreferences),
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.email) {
 | 
			
		||||
      const duplicate = await this.userRepository.getByEmail(dto.email);
 | 
			
		||||
      if (duplicate && duplicate.id !== id) {
 | 
			
		||||
        throw new BadRequestException('Email already in use by another account');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.storageLabel) {
 | 
			
		||||
      const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel);
 | 
			
		||||
      if (duplicate && duplicate.id !== id) {
 | 
			
		||||
        throw new BadRequestException('Storage label already in use by another account');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.password) {
 | 
			
		||||
      dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dto.storageLabel === '') {
 | 
			
		||||
      dto.storageLabel = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const updatedUser = await this.userRepository.update(id, { ...dto, updatedAt: new Date() });
 | 
			
		||||
 | 
			
		||||
    return mapUserAdmin(updatedUser);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async delete(auth: AuthDto, id: string, dto: UserAdminDeleteDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    const { force } = dto;
 | 
			
		||||
    const { isAdmin } = await this.findOrFail(id, {});
 | 
			
		||||
    if (isAdmin) {
 | 
			
		||||
      throw new ForbiddenException('Cannot delete admin user');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await this.albumRepository.softDeleteAll(id);
 | 
			
		||||
 | 
			
		||||
    const status = force ? UserStatus.REMOVING : UserStatus.DELETED;
 | 
			
		||||
    const user = await this.userRepository.update(id, { status, deletedAt: new Date() });
 | 
			
		||||
 | 
			
		||||
    if (force) {
 | 
			
		||||
      await this.jobRepository.queue({ name: JobName.USER_DELETION, data: { id: user.id, force } });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return mapUserAdmin(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async restore(auth: AuthDto, id: string): Promise<UserAdminResponseDto> {
 | 
			
		||||
    await this.findOrFail(id, { withDeleted: true });
 | 
			
		||||
    await this.albumRepository.restoreAll(id);
 | 
			
		||||
    const user = await this.userRepository.update(id, { deletedAt: null, status: UserStatus.ACTIVE });
 | 
			
		||||
    return mapUserAdmin(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async findOrFail(id: string, options: UserFindOptions) {
 | 
			
		||||
    const user = await this.userRepository.get(id, options);
 | 
			
		||||
    if (!user) {
 | 
			
		||||
      throw new BadRequestException('User not found');
 | 
			
		||||
    }
 | 
			
		||||
    return user;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,11 +1,5 @@
 | 
			
		||||
import {
 | 
			
		||||
  BadRequestException,
 | 
			
		||||
  ForbiddenException,
 | 
			
		||||
  InternalServerErrorException,
 | 
			
		||||
  NotFoundException,
 | 
			
		||||
} from '@nestjs/common';
 | 
			
		||||
import { UpdateUserDto, mapUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserEntity, UserStatus } from 'src/entities/user.entity';
 | 
			
		||||
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
 | 
			
		||||
import { UserEntity } from 'src/entities/user.entity';
 | 
			
		||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
 | 
			
		||||
@ -63,13 +57,13 @@ describe(UserService.name, () => {
 | 
			
		||||
  describe('getAll', () => {
 | 
			
		||||
    it('should get all users', async () => {
 | 
			
		||||
      userMock.getList.mockResolvedValue([userStub.admin]);
 | 
			
		||||
      await expect(sut.getAll(authStub.admin, false)).resolves.toEqual([
 | 
			
		||||
      await expect(sut.search()).resolves.toEqual([
 | 
			
		||||
        expect.objectContaining({
 | 
			
		||||
          id: authStub.admin.user.id,
 | 
			
		||||
          email: authStub.admin.user.email,
 | 
			
		||||
        }),
 | 
			
		||||
      ]);
 | 
			
		||||
      expect(userMock.getList).toHaveBeenCalledWith({ withDeleted: true });
 | 
			
		||||
      expect(userMock.getList).toHaveBeenCalledWith({ withDeleted: false });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -82,255 +76,17 @@ describe(UserService.name, () => {
 | 
			
		||||
 | 
			
		||||
    it('should throw an error if a user is not found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(null);
 | 
			
		||||
      await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(NotFoundException);
 | 
			
		||||
      await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
      expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('getMe', () => {
 | 
			
		||||
    it("should get the auth user's info", async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.admin);
 | 
			
		||||
      await sut.getMe(authStub.admin);
 | 
			
		||||
      expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, {});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should throw an error if a user is not found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(null);
 | 
			
		||||
      await expect(sut.getMe(authStub.admin)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
      expect(userMock.get).toHaveBeenCalledWith(authStub.admin.user.id, {});
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('update', () => {
 | 
			
		||||
    it('should update user', async () => {
 | 
			
		||||
      const update: UpdateUserDto = {
 | 
			
		||||
        id: userStub.user1.id,
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
        email: 'immich@test.com',
 | 
			
		||||
        storageLabel: 'storage_label',
 | 
			
		||||
      };
 | 
			
		||||
      userMock.getByEmail.mockResolvedValue(null);
 | 
			
		||||
      userMock.getByStorageLabel.mockResolvedValue(null);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await sut.update({ user: { ...authStub.user1.user, isAdmin: true } }, update);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.getByEmail).toHaveBeenCalledWith(update.email);
 | 
			
		||||
      expect(userMock.getByStorageLabel).toHaveBeenCalledWith(update.storageLabel);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not set an empty string for storage label', async () => {
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
      await sut.update(authStub.admin, { id: userStub.user1.id, storageLabel: '' });
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        id: userStub.user1.id,
 | 
			
		||||
        storageLabel: null,
 | 
			
		||||
        updatedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should omit a storage label set by non-admin users', async () => {
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
      await sut.update({ user: userStub.user1 }, { id: userStub.user1.id, storageLabel: 'admin' });
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        id: userStub.user1.id,
 | 
			
		||||
        updatedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('user can only update its information', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValueOnce({
 | 
			
		||||
        ...userStub.user1,
 | 
			
		||||
        id: 'not_immich_auth_user_id',
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const result = sut.update(
 | 
			
		||||
        { user: userStub.user1 },
 | 
			
		||||
        {
 | 
			
		||||
          id: 'not_immich_auth_user_id',
 | 
			
		||||
          password: 'I take over your account now',
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
      await expect(result).rejects.toBeInstanceOf(ForbiddenException);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should let a user change their email', async () => {
 | 
			
		||||
      const dto = { id: userStub.user1.id, email: 'updated@test.com' };
 | 
			
		||||
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await sut.update({ user: userStub.user1 }, dto);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        id: 'user-id',
 | 
			
		||||
        email: 'updated@test.com',
 | 
			
		||||
        updatedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not let a user change their email to one already in use', async () => {
 | 
			
		||||
      const dto = { id: userStub.user1.id, email: 'updated@test.com' };
 | 
			
		||||
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.getByEmail.mockResolvedValue(userStub.admin);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.update({ user: userStub.user1 }, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not let the admin change the storage label to one already in use', async () => {
 | 
			
		||||
      const dto = { id: userStub.user1.id, storageLabel: 'admin' };
 | 
			
		||||
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.getByStorageLabel.mockResolvedValue(userStub.admin);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('admin can update any user information', async () => {
 | 
			
		||||
      const update: UpdateUserDto = {
 | 
			
		||||
        id: userStub.user1.id,
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      userMock.update.mockResolvedValueOnce(userStub.user1);
 | 
			
		||||
      await sut.update(authStub.admin, update);
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        id: 'user-id',
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
        updatedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('update user information should throw error if user not found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValueOnce(null);
 | 
			
		||||
 | 
			
		||||
      const result = sut.update(authStub.admin, {
 | 
			
		||||
        id: userStub.user1.id,
 | 
			
		||||
        shouldChangePassword: true,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await expect(result).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should let the admin update himself', async () => {
 | 
			
		||||
      const dto = { id: userStub.admin.id, shouldChangePassword: true, isAdmin: true };
 | 
			
		||||
 | 
			
		||||
      userMock.update.mockResolvedValueOnce(userStub.admin);
 | 
			
		||||
 | 
			
		||||
      await sut.update(authStub.admin, dto);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.admin.id, { ...dto, updatedAt: expect.any(Date) });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should not let the another user become an admin', async () => {
 | 
			
		||||
      const dto = { id: userStub.user1.id, shouldChangePassword: true, isAdmin: true };
 | 
			
		||||
 | 
			
		||||
      userMock.get.mockResolvedValueOnce(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.update(authStub.admin, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('restore', () => {
 | 
			
		||||
    it('should throw error if user could not be found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(null);
 | 
			
		||||
      await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException);
 | 
			
		||||
      expect(userMock.update).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should restore an user', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
      await expect(sut.restore(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUser(userStub.user1));
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, { status: UserStatus.ACTIVE, deletedAt: null });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('delete', () => {
 | 
			
		||||
    it('should throw error if user could not be found', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(null);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toThrowError(BadRequestException);
 | 
			
		||||
      expect(userMock.delete).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('cannot delete admin user', async () => {
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toBeInstanceOf(ForbiddenException);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should require the auth user be an admin', async () => {
 | 
			
		||||
      await expect(sut.delete(authStub.user1, authStub.admin.user.id, {})).rejects.toBeInstanceOf(ForbiddenException);
 | 
			
		||||
 | 
			
		||||
      expect(userMock.delete).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should delete user', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.user1.id, {})).resolves.toEqual(mapUser(userStub.user1));
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        status: UserStatus.DELETED,
 | 
			
		||||
        deletedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should force delete user', async () => {
 | 
			
		||||
      userMock.get.mockResolvedValue(userStub.user1);
 | 
			
		||||
      userMock.update.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(sut.delete(authStub.admin, userStub.user1.id, { force: true })).resolves.toEqual(
 | 
			
		||||
        mapUser(userStub.user1),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
 | 
			
		||||
        status: UserStatus.REMOVING,
 | 
			
		||||
        deletedAt: expect.any(Date),
 | 
			
		||||
      });
 | 
			
		||||
      expect(jobMock.queue).toHaveBeenCalledWith({
 | 
			
		||||
        name: JobName.USER_DELETION,
 | 
			
		||||
        data: { id: userStub.user1.id, force: true },
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('create', () => {
 | 
			
		||||
    it('should not create a user if there is no local admin account', async () => {
 | 
			
		||||
      userMock.getAdmin.mockResolvedValueOnce(null);
 | 
			
		||||
 | 
			
		||||
      await expect(
 | 
			
		||||
        sut.create({
 | 
			
		||||
          email: 'john_smith@email.com',
 | 
			
		||||
          name: 'John Smith',
 | 
			
		||||
          password: 'password',
 | 
			
		||||
        }),
 | 
			
		||||
      ).rejects.toBeInstanceOf(BadRequestException);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should create user', async () => {
 | 
			
		||||
      userMock.getAdmin.mockResolvedValue(userStub.admin);
 | 
			
		||||
      userMock.create.mockResolvedValue(userStub.user1);
 | 
			
		||||
 | 
			
		||||
      await expect(
 | 
			
		||||
        sut.create({
 | 
			
		||||
          email: userStub.user1.email,
 | 
			
		||||
          name: userStub.user1.name,
 | 
			
		||||
          password: 'password',
 | 
			
		||||
          storageLabel: 'label',
 | 
			
		||||
        }),
 | 
			
		||||
      ).resolves.toEqual(mapUser(userStub.user1));
 | 
			
		||||
 | 
			
		||||
      expect(userMock.getAdmin).toBeCalled();
 | 
			
		||||
      expect(userMock.create).toBeCalledWith({
 | 
			
		||||
        email: userStub.user1.email,
 | 
			
		||||
        name: userStub.user1.name,
 | 
			
		||||
        storageLabel: 'label',
 | 
			
		||||
        password: expect.anything(),
 | 
			
		||||
    it("should get the auth user's info", () => {
 | 
			
		||||
      const user = authStub.admin.user;
 | 
			
		||||
      expect(sut.getMe(authStub.admin)).toMatchObject({
 | 
			
		||||
        id: user.id,
 | 
			
		||||
        email: user.email,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common';
 | 
			
		||||
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
 | 
			
		||||
import { DateTime } from 'luxon';
 | 
			
		||||
import { SALT_ROUNDS } from 'src/constants';
 | 
			
		||||
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
 | 
			
		||||
import { SystemConfigCore } from 'src/cores/system-config.core';
 | 
			
		||||
import { UserCore } from 'src/cores/user.core';
 | 
			
		||||
import { AuthDto } from 'src/dtos/auth.dto';
 | 
			
		||||
import { CreateProfileImageResponseDto, mapCreateProfileImageResponse } from 'src/dtos/user-profile.dto';
 | 
			
		||||
import { CreateUserDto, DeleteUserDto, UpdateUserDto, UserResponseDto, mapUser } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto';
 | 
			
		||||
import { UserMetadataKey } from 'src/entities/user-metadata.entity';
 | 
			
		||||
import { UserEntity, UserStatus } from 'src/entities/user.entity';
 | 
			
		||||
import { UserEntity } from 'src/entities/user.entity';
 | 
			
		||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
 | 
			
		||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
 | 
			
		||||
import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
 | 
			
		||||
@ -21,73 +21,30 @@ import { getPreferences, getPreferencesPartial } from 'src/utils/preferences';
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class UserService {
 | 
			
		||||
  private configCore: SystemConfigCore;
 | 
			
		||||
  private userCore: UserCore;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
 | 
			
		||||
    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
			
		||||
    @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
 | 
			
		||||
    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
			
		||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
			
		||||
    @Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
 | 
			
		||||
    @Inject(IUserRepository) private userRepository: IUserRepository,
 | 
			
		||||
    @Inject(ILoggerRepository) private logger: ILoggerRepository,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.userCore = UserCore.create(cryptoRepository, userRepository);
 | 
			
		||||
    this.logger.setContext(UserService.name);
 | 
			
		||||
    this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async listUsers(): Promise<UserResponseDto[]> {
 | 
			
		||||
    const users = await this.userRepository.getList({ withDeleted: true });
 | 
			
		||||
  async search(): Promise<UserResponseDto[]> {
 | 
			
		||||
    const users = await this.userRepository.getList({ withDeleted: false });
 | 
			
		||||
    return users.map((user) => mapUser(user));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getAll(auth: AuthDto, isAll: boolean): Promise<UserResponseDto[]> {
 | 
			
		||||
    const users = await this.userRepository.getList({ withDeleted: !isAll });
 | 
			
		||||
    return users.map((user) => mapUser(user));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get(userId: string): Promise<UserResponseDto> {
 | 
			
		||||
    const user = await this.userRepository.get(userId, { withDeleted: false });
 | 
			
		||||
    if (!user) {
 | 
			
		||||
      throw new NotFoundException('User not found');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return mapUser(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getMe(auth: AuthDto): Promise<UserResponseDto> {
 | 
			
		||||
    return this.findOrFail(auth.user.id, {}).then(mapUser);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async create(dto: CreateUserDto): Promise<UserResponseDto> {
 | 
			
		||||
    const { memoriesEnabled, notify, ...rest } = dto;
 | 
			
		||||
    let user = await this.userCore.createUser(rest);
 | 
			
		||||
 | 
			
		||||
    // TODO remove and replace with entire dto.preferences config
 | 
			
		||||
    if (memoriesEnabled === false) {
 | 
			
		||||
      await this.userRepository.upsertMetadata(user.id, {
 | 
			
		||||
        key: UserMetadataKey.PREFERENCES,
 | 
			
		||||
        value: { memories: { enabled: false } },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      user = await this.findOrFail(user.id, {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const tempPassword = user.shouldChangePassword ? rest.password : undefined;
 | 
			
		||||
    if (notify) {
 | 
			
		||||
      await this.jobRepository.queue({ name: JobName.NOTIFY_SIGNUP, data: { id: user.id, tempPassword } });
 | 
			
		||||
    }
 | 
			
		||||
    return mapUser(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async update(auth: AuthDto, dto: UpdateUserDto): Promise<UserResponseDto> {
 | 
			
		||||
    const user = await this.findOrFail(dto.id, {});
 | 
			
		||||
 | 
			
		||||
    if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) {
 | 
			
		||||
      await this.userRepository.syncUsage(dto.id);
 | 
			
		||||
  getMe(auth: AuthDto): UserAdminResponseDto {
 | 
			
		||||
    return mapUserAdmin(auth.user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateMe({ user }: AuthDto, dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
 | 
			
		||||
    // TODO replace with entire preferences object
 | 
			
		||||
    if (dto.memoriesEnabled !== undefined || dto.avatarColor) {
 | 
			
		||||
      const newPreferences = getPreferences(user);
 | 
			
		||||
@ -101,42 +58,40 @@ export class UserService {
 | 
			
		||||
        delete dto.avatarColor;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await this.userRepository.upsertMetadata(dto.id, {
 | 
			
		||||
      await this.userRepository.upsertMetadata(user.id, {
 | 
			
		||||
        key: UserMetadataKey.PREFERENCES,
 | 
			
		||||
        value: getPreferencesPartial(user, newPreferences),
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const updatedUser = await this.userCore.updateUser(auth.user, dto.id, dto);
 | 
			
		||||
 | 
			
		||||
    return mapUser(updatedUser);
 | 
			
		||||
    if (dto.email) {
 | 
			
		||||
      const duplicate = await this.userRepository.getByEmail(dto.email);
 | 
			
		||||
      if (duplicate && duplicate.id !== user.id) {
 | 
			
		||||
        throw new BadRequestException('Email already in use by another account');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  async delete(auth: AuthDto, id: string, dto: DeleteUserDto): Promise<UserResponseDto> {
 | 
			
		||||
    const { force } = dto;
 | 
			
		||||
    const { isAdmin } = await this.findOrFail(id, {});
 | 
			
		||||
    if (isAdmin) {
 | 
			
		||||
      throw new ForbiddenException('Cannot delete admin user');
 | 
			
		||||
    const update: Partial<UserEntity> = {
 | 
			
		||||
      email: dto.email,
 | 
			
		||||
      name: dto.name,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (dto.password) {
 | 
			
		||||
      const hashedPassword = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
 | 
			
		||||
      update.password = hashedPassword;
 | 
			
		||||
      update.shouldChangePassword = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await this.albumRepository.softDeleteAll(id);
 | 
			
		||||
    const updatedUser = await this.userRepository.update(user.id, update);
 | 
			
		||||
 | 
			
		||||
    const status = force ? UserStatus.REMOVING : UserStatus.DELETED;
 | 
			
		||||
    const user = await this.userRepository.update(id, { status, deletedAt: new Date() });
 | 
			
		||||
 | 
			
		||||
    if (force) {
 | 
			
		||||
      await this.jobRepository.queue({ name: JobName.USER_DELETION, data: { id: user.id, force } });
 | 
			
		||||
    return mapUserAdmin(updatedUser);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get(id: string): Promise<UserResponseDto> {
 | 
			
		||||
    const user = await this.findOrFail(id, { withDeleted: false });
 | 
			
		||||
    return mapUser(user);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async restore(auth: AuthDto, id: string): Promise<UserResponseDto> {
 | 
			
		||||
    await this.findOrFail(id, { withDeleted: true });
 | 
			
		||||
    await this.albumRepository.restoreAll(id);
 | 
			
		||||
    return this.userRepository.update(id, { deletedAt: null, status: UserStatus.ACTIVE }).then(mapUser);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createProfileImage(auth: AuthDto, fileInfo: Express.Multer.File): Promise<CreateProfileImageResponseDto> {
 | 
			
		||||
    const { profileImagePath: oldpath } = await this.findOrFail(auth.user.id, { withDeleted: false });
 | 
			
		||||
    const updatedUser = await this.userRepository.update(auth.user.id, { profileImagePath: fileInfo.path });
 | 
			
		||||
 | 
			
		||||
@ -154,7 +154,7 @@ export function validateCronExpression(expression: string) {
 | 
			
		||||
 | 
			
		||||
type IValue = { value: string };
 | 
			
		||||
 | 
			
		||||
export const toEmail = ({ value }: IValue) => value?.toLowerCase();
 | 
			
		||||
export const toEmail = ({ value }: IValue) => (value ? value.toLowerCase() : value);
 | 
			
		||||
 | 
			
		||||
export const toSanitized = ({ value }: IValue) => sanitize((value || '').replaceAll('.', ''));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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: {
 | 
			
		||||
      await updateUserAdmin({
 | 
			
		||||
        id,
 | 
			
		||||
        userAdminUpdateDto: {
 | 
			
		||||
          email,
 | 
			
		||||
          name,
 | 
			
		||||
          storageLabel: storageLabel || '',
 | 
			
		||||
@ -56,9 +56,9 @@
 | 
			
		||||
    try {
 | 
			
		||||
      newPassword = generatePassword();
 | 
			
		||||
 | 
			
		||||
      await updateUser({
 | 
			
		||||
        updateUserDto: {
 | 
			
		||||
      await updateUserAdmin({
 | 
			
		||||
        id: user.id,
 | 
			
		||||
        userAdminUpdateDto: {
 | 
			
		||||
          password: newPassword,
 | 
			
		||||
          shouldChangePassword: true,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -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 }));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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' });
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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: () => {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -25,5 +25,5 @@
 | 
			
		||||
    enter the new password below.
 | 
			
		||||
  </p>
 | 
			
		||||
 | 
			
		||||
  <ChangePasswordForm user={$user} on:success={onSuccess} />
 | 
			
		||||
  <ChangePasswordForm on:success={onSuccess} />
 | 
			
		||||
</FullscreenContainer>
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user