mirror of
https://github.com/immich-app/immich.git
synced 2024-11-15 18:08:48 -07:00
feat(server): user metadata (#9650)
* feat(server): user metadata * add missing method to user mock * update migration to include cascades * update sql files * test: fix e2e * chore: clean up --------- Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
parent
a4887bfa7e
commit
06ce8247cc
@ -257,23 +257,6 @@ describe('/user', () => {
|
||||
expect(body).toMatchObject({ id: admin.userId, profileImagePath: '' });
|
||||
});
|
||||
|
||||
it('should ignore updates to createdAt, updatedAt and deletedAt', async () => {
|
||||
const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
|
||||
|
||||
const { status, body } = await request(app)
|
||||
.put(`/user`)
|
||||
.send({
|
||||
id: admin.userId,
|
||||
createdAt: '2023-01-01T00:00:00.000Z',
|
||||
updatedAt: '2023-01-01T00:00:00.000Z',
|
||||
deletedAt: '2023-01-01T00:00:00.000Z',
|
||||
})
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toStrictEqual(before);
|
||||
});
|
||||
|
||||
it('should update first and last name', async () => {
|
||||
const before = await getUserById({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
|
||||
|
||||
|
@ -69,7 +69,7 @@ export class UserCore {
|
||||
dto.storageLabel = null;
|
||||
}
|
||||
|
||||
return this.userRepository.update(id, dto);
|
||||
return this.userRepository.update(id, { ...dto, updatedAt: new Date() });
|
||||
}
|
||||
|
||||
async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { UploadFieldName } from 'src/dtos/asset.dto';
|
||||
import { UserAvatarColor, UserEntity } from 'src/entities/user.entity';
|
||||
|
||||
export class CreateProfileImageDto {
|
||||
@ApiProperty({ type: 'string', format: 'binary' })
|
||||
@ -18,11 +17,3 @@ export function mapCreateProfileImageResponse(userId: string, profileImagePath:
|
||||
profileImagePath: profileImagePath,
|
||||
};
|
||||
}
|
||||
|
||||
export const getRandomAvatarColor = (user: UserEntity): UserAvatarColor => {
|
||||
const values = Object.values(UserAvatarColor);
|
||||
const randomIndex = Math.floor(
|
||||
[...user.email].map((letter) => letter.codePointAt(0) ?? 0).reduce((a, b) => a + b, 0) % values.length,
|
||||
);
|
||||
return values[randomIndex] as UserAvatarColor;
|
||||
};
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString, IsUUID } from 'class-validator';
|
||||
import { getRandomAvatarColor } from 'src/dtos/user-profile.dto';
|
||||
import { UserAvatarColor, UserEntity, UserStatus } from 'src/entities/user.entity';
|
||||
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 {
|
||||
@ -151,7 +152,7 @@ export const mapSimpleUser = (entity: UserEntity): UserDto => {
|
||||
email: entity.email,
|
||||
name: entity.name,
|
||||
profileImagePath: entity.profileImagePath,
|
||||
avatarColor: entity.avatarColor ?? getRandomAvatarColor(entity),
|
||||
avatarColor: getPreferences(entity).avatar.color,
|
||||
};
|
||||
};
|
||||
|
||||
@ -165,7 +166,7 @@ export function mapUser(entity: UserEntity): UserResponseDto {
|
||||
deletedAt: entity.deletedAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
oauthId: entity.oauthId,
|
||||
memoriesEnabled: entity.memoriesEnabled,
|
||||
memoriesEnabled: getPreferences(entity).memories.enabled,
|
||||
quotaSizeInBytes: entity.quotaSizeInBytes,
|
||||
quotaUsageInBytes: entity.quotaUsageInBytes,
|
||||
status: entity.status,
|
||||
|
@ -20,6 +20,7 @@ import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
||||
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
|
||||
import { SystemMetadataEntity } from 'src/entities/system-metadata.entity';
|
||||
import { TagEntity } from 'src/entities/tag.entity';
|
||||
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
|
||||
export const entities = [
|
||||
@ -44,6 +45,7 @@ export const entities = [
|
||||
SystemMetadataEntity,
|
||||
TagEntity,
|
||||
UserEntity,
|
||||
UserMetadataEntity,
|
||||
SessionEntity,
|
||||
LibraryEntity,
|
||||
];
|
||||
|
63
server/src/entities/user-metadata.entity.ts
Normal file
63
server/src/entities/user-metadata.entity.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { Column, DeepPartial, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity('user_metadata')
|
||||
export class UserMetadataEntity<T extends keyof UserMetadata = UserMetadataKey> {
|
||||
@PrimaryColumn({ type: 'uuid' })
|
||||
userId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.metadata, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
|
||||
user!: UserEntity;
|
||||
|
||||
@PrimaryColumn({ type: 'varchar' })
|
||||
key!: T;
|
||||
|
||||
@Column({ type: 'jsonb' })
|
||||
value!: UserMetadata[T];
|
||||
}
|
||||
|
||||
export enum UserAvatarColor {
|
||||
PRIMARY = 'primary',
|
||||
PINK = 'pink',
|
||||
RED = 'red',
|
||||
YELLOW = 'yellow',
|
||||
BLUE = 'blue',
|
||||
GREEN = 'green',
|
||||
PURPLE = 'purple',
|
||||
ORANGE = 'orange',
|
||||
GRAY = 'gray',
|
||||
AMBER = 'amber',
|
||||
}
|
||||
|
||||
export interface UserPreferences {
|
||||
memories: {
|
||||
enabled: boolean;
|
||||
};
|
||||
avatar: {
|
||||
color: UserAvatarColor;
|
||||
};
|
||||
}
|
||||
|
||||
export const getDefaultPreferences = (user: { email: string }): UserPreferences => {
|
||||
const values = Object.values(UserAvatarColor);
|
||||
const randomIndex = Math.floor(
|
||||
[...user.email].map((letter) => letter.codePointAt(0) ?? 0).reduce((a, b) => a + b, 0) % values.length,
|
||||
);
|
||||
|
||||
return {
|
||||
memories: {
|
||||
enabled: true,
|
||||
},
|
||||
avatar: {
|
||||
color: values[randomIndex],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export enum UserMetadataKey {
|
||||
PREFERENCES = 'preferences',
|
||||
}
|
||||
|
||||
export interface UserMetadata extends Record<UserMetadataKey, Record<string, any>> {
|
||||
[UserMetadataKey.PREFERENCES]: DeepPartial<UserPreferences>;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { TagEntity } from 'src/entities/tag.entity';
|
||||
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
@ -10,19 +11,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
export enum UserAvatarColor {
|
||||
PRIMARY = 'primary',
|
||||
PINK = 'pink',
|
||||
RED = 'red',
|
||||
YELLOW = 'yellow',
|
||||
BLUE = 'blue',
|
||||
GREEN = 'green',
|
||||
PURPLE = 'purple',
|
||||
ORANGE = 'orange',
|
||||
GRAY = 'gray',
|
||||
AMBER = 'amber',
|
||||
}
|
||||
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
REMOVING = 'removing',
|
||||
@ -37,9 +25,6 @@ export class UserEntity {
|
||||
@Column({ default: '' })
|
||||
name!: string;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
avatarColor!: UserAvatarColor | null;
|
||||
|
||||
@Column({ default: false })
|
||||
isAdmin!: boolean;
|
||||
|
||||
@ -73,9 +58,6 @@ export class UserEntity {
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column({ default: true })
|
||||
memoriesEnabled!: boolean;
|
||||
|
||||
@OneToMany(() => TagEntity, (tag) => tag.user)
|
||||
tags!: TagEntity[];
|
||||
|
||||
@ -87,4 +69,7 @@ export class UserEntity {
|
||||
|
||||
@Column({ type: 'bigint', default: 0 })
|
||||
quotaUsageInBytes!: number;
|
||||
|
||||
@OneToMany(() => UserMetadataEntity, (metadata) => metadata.user)
|
||||
metadata!: UserMetadataEntity[];
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { UserMetadata } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
|
||||
export interface UserListFilter {
|
||||
@ -31,6 +32,7 @@ export interface IUserRepository {
|
||||
getUserStats(): Promise<UserStatsQueryResponse[]>;
|
||||
create(user: Partial<UserEntity>): Promise<UserEntity>;
|
||||
update(id: string, user: Partial<UserEntity>): Promise<UserEntity>;
|
||||
upsertMetadata<T extends keyof UserMetadata>(id: string, item: { key: T; value: UserMetadata[T] }): Promise<void>;
|
||||
delete(user: UserEntity, hard?: boolean): Promise<UserEntity>;
|
||||
updateUsage(id: string, delta: number): Promise<void>;
|
||||
syncUsage(id?: string): Promise<void>;
|
||||
|
60
server/src/migrations/1716312279245-UserMetadata.ts
Normal file
60
server/src/migrations/1716312279245-UserMetadata.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UserMetadata1716312279245 implements MigrationInterface {
|
||||
name = 'UserMetadata1716312279245';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL, CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key"))`,
|
||||
);
|
||||
const users = await queryRunner.query('SELECT "id", "memoriesEnabled", "avatarColor" FROM "users"');
|
||||
for (const { id, memoriesEnabled, avatarColor } of users) {
|
||||
const preferences: any = {};
|
||||
if (!memoriesEnabled) {
|
||||
preferences.memories = { enabled: false };
|
||||
}
|
||||
|
||||
if (avatarColor) {
|
||||
preferences.avatar = { color: avatarColor };
|
||||
}
|
||||
|
||||
if (Object.keys(preferences).length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await queryRunner.query('INSERT INTO "user_metadata" ("userId", "key", "value") VALUES ($1, $2, $3)', [
|
||||
id,
|
||||
'preferences',
|
||||
preferences,
|
||||
]);
|
||||
}
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "memoriesEnabled"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "avatarColor"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_metadata" ADD CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_metadata" DROP CONSTRAINT "FK_6afb43681a21cf7815932bc38ac"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "avatarColor" character varying`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "memoriesEnabled" boolean NOT NULL DEFAULT true`);
|
||||
const items = await queryRunner.query(
|
||||
`SELECT "userId" as "id", "value" FROM "user_metadata" WHERE "key"='preferences'`,
|
||||
);
|
||||
for (const { id, value } of items) {
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value.avatar?.color) {
|
||||
await queryRunner.query(`UPDATE "users" SET "avatarColor" = $1 WHERE "id" = $2`, [value.avatar.color, id]);
|
||||
}
|
||||
|
||||
if (value.memories?.enabled === false) {
|
||||
await queryRunner.query(`UPDATE "users" SET "memoriesEnabled" = false WHERE "id" = $1`, [id]);
|
||||
}
|
||||
}
|
||||
await queryRunner.query(`DROP TABLE "user_metadata"`);
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@ SELECT
|
||||
"ActivityEntity"."isLiked" AS "ActivityEntity_isLiked",
|
||||
"ActivityEntity__ActivityEntity_user"."id" AS "ActivityEntity__ActivityEntity_user_id",
|
||||
"ActivityEntity__ActivityEntity_user"."name" AS "ActivityEntity__ActivityEntity_user_name",
|
||||
"ActivityEntity__ActivityEntity_user"."avatarColor" AS "ActivityEntity__ActivityEntity_user_avatarColor",
|
||||
"ActivityEntity__ActivityEntity_user"."isAdmin" AS "ActivityEntity__ActivityEntity_user_isAdmin",
|
||||
"ActivityEntity__ActivityEntity_user"."email" AS "ActivityEntity__ActivityEntity_user_email",
|
||||
"ActivityEntity__ActivityEntity_user"."storageLabel" AS "ActivityEntity__ActivityEntity_user_storageLabel",
|
||||
@ -23,7 +22,6 @@ SELECT
|
||||
"ActivityEntity__ActivityEntity_user"."deletedAt" AS "ActivityEntity__ActivityEntity_user_deletedAt",
|
||||
"ActivityEntity__ActivityEntity_user"."status" AS "ActivityEntity__ActivityEntity_user_status",
|
||||
"ActivityEntity__ActivityEntity_user"."updatedAt" AS "ActivityEntity__ActivityEntity_user_updatedAt",
|
||||
"ActivityEntity__ActivityEntity_user"."memoriesEnabled" AS "ActivityEntity__ActivityEntity_user_memoriesEnabled",
|
||||
"ActivityEntity__ActivityEntity_user"."quotaSizeInBytes" AS "ActivityEntity__ActivityEntity_user_quotaSizeInBytes",
|
||||
"ActivityEntity__ActivityEntity_user"."quotaUsageInBytes" AS "ActivityEntity__ActivityEntity_user_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -18,7 +18,6 @@ FROM
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -29,7 +28,6 @@ FROM
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
|
||||
@ -37,7 +35,6 @@ FROM
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."avatarColor" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_avatarColor",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
|
||||
@ -48,7 +45,6 @@ FROM
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."memoriesEnabled" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_memoriesEnabled",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||
@ -98,7 +94,6 @@ SELECT
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -109,7 +104,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
|
||||
@ -117,7 +111,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."avatarColor" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_avatarColor",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
|
||||
@ -128,7 +121,6 @@ SELECT
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."memoriesEnabled" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_memoriesEnabled",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -160,7 +152,6 @@ SELECT
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -171,7 +162,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
|
||||
@ -179,7 +169,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."avatarColor" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_avatarColor",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
|
||||
@ -190,7 +179,6 @@ SELECT
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."memoriesEnabled" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_memoriesEnabled",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -299,7 +287,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."avatarColor" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_avatarColor",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
|
||||
@ -310,7 +297,6 @@ SELECT
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."memoriesEnabled" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_memoriesEnabled",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||
@ -327,7 +313,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -338,7 +323,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -376,7 +360,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."avatarColor" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_avatarColor",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
|
||||
@ -387,7 +370,6 @@ SELECT
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."memoriesEnabled" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_memoriesEnabled",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||
@ -404,7 +386,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -415,7 +396,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -504,7 +484,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -515,7 +494,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -564,7 +542,6 @@ SELECT
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||
@ -575,7 +552,6 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
|
||||
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -11,7 +11,6 @@ FROM
|
||||
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||
"APIKeyEntity__APIKeyEntity_user"."id" AS "APIKeyEntity__APIKeyEntity_user_id",
|
||||
"APIKeyEntity__APIKeyEntity_user"."name" AS "APIKeyEntity__APIKeyEntity_user_name",
|
||||
"APIKeyEntity__APIKeyEntity_user"."avatarColor" AS "APIKeyEntity__APIKeyEntity_user_avatarColor",
|
||||
"APIKeyEntity__APIKeyEntity_user"."isAdmin" AS "APIKeyEntity__APIKeyEntity_user_isAdmin",
|
||||
"APIKeyEntity__APIKeyEntity_user"."email" AS "APIKeyEntity__APIKeyEntity_user_email",
|
||||
"APIKeyEntity__APIKeyEntity_user"."storageLabel" AS "APIKeyEntity__APIKeyEntity_user_storageLabel",
|
||||
@ -22,7 +21,6 @@ FROM
|
||||
"APIKeyEntity__APIKeyEntity_user"."deletedAt" AS "APIKeyEntity__APIKeyEntity_user_deletedAt",
|
||||
"APIKeyEntity__APIKeyEntity_user"."status" AS "APIKeyEntity__APIKeyEntity_user_status",
|
||||
"APIKeyEntity__APIKeyEntity_user"."updatedAt" AS "APIKeyEntity__APIKeyEntity_user_updatedAt",
|
||||
"APIKeyEntity__APIKeyEntity_user"."memoriesEnabled" AS "APIKeyEntity__APIKeyEntity_user_memoriesEnabled",
|
||||
"APIKeyEntity__APIKeyEntity_user"."quotaSizeInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaSizeInBytes",
|
||||
"APIKeyEntity__APIKeyEntity_user"."quotaUsageInBytes" AS "APIKeyEntity__APIKeyEntity_user_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -17,7 +17,6 @@ FROM
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
@ -28,7 +27,6 @@ FROM
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -89,7 +87,6 @@ SELECT
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
@ -100,7 +97,6 @@ SELECT
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -128,7 +124,6 @@ SELECT
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
@ -139,7 +134,6 @@ SELECT
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -166,7 +160,6 @@ SELECT
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
@ -177,7 +170,6 @@ SELECT
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -27,7 +27,6 @@ FROM
|
||||
"SessionEntity"."deviceOS" AS "SessionEntity_deviceOS",
|
||||
"SessionEntity__SessionEntity_user"."id" AS "SessionEntity__SessionEntity_user_id",
|
||||
"SessionEntity__SessionEntity_user"."name" AS "SessionEntity__SessionEntity_user_name",
|
||||
"SessionEntity__SessionEntity_user"."avatarColor" AS "SessionEntity__SessionEntity_user_avatarColor",
|
||||
"SessionEntity__SessionEntity_user"."isAdmin" AS "SessionEntity__SessionEntity_user_isAdmin",
|
||||
"SessionEntity__SessionEntity_user"."email" AS "SessionEntity__SessionEntity_user_email",
|
||||
"SessionEntity__SessionEntity_user"."storageLabel" AS "SessionEntity__SessionEntity_user_storageLabel",
|
||||
@ -38,7 +37,6 @@ FROM
|
||||
"SessionEntity__SessionEntity_user"."deletedAt" AS "SessionEntity__SessionEntity_user_deletedAt",
|
||||
"SessionEntity__SessionEntity_user"."status" AS "SessionEntity__SessionEntity_user_status",
|
||||
"SessionEntity__SessionEntity_user"."updatedAt" AS "SessionEntity__SessionEntity_user_updatedAt",
|
||||
"SessionEntity__SessionEntity_user"."memoriesEnabled" AS "SessionEntity__SessionEntity_user_memoriesEnabled",
|
||||
"SessionEntity__SessionEntity_user"."quotaSizeInBytes" AS "SessionEntity__SessionEntity_user_quotaSizeInBytes",
|
||||
"SessionEntity__SessionEntity_user"."quotaUsageInBytes" AS "SessionEntity__SessionEntity_user_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -147,7 +147,6 @@ FROM
|
||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fps" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fps",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."avatarColor" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_avatarColor",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel",
|
||||
@ -158,7 +157,6 @@ FROM
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_deletedAt",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."status" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_status",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."updatedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_updatedAt",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."memoriesEnabled" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_memoriesEnabled",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaSizeInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaSizeInBytes",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaUsageInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -252,7 +250,6 @@ SELECT
|
||||
"SharedLinkEntity__SharedLinkEntity_album"."order" AS "SharedLinkEntity__SharedLinkEntity_album_order",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."avatarColor" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_avatarColor",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel",
|
||||
@ -263,7 +260,6 @@ SELECT
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_deletedAt",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."status" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_status",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."updatedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_updatedAt",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."memoriesEnabled" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_memoriesEnabled",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaSizeInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaSizeInBytes",
|
||||
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."quotaUsageInBytes" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -306,7 +302,6 @@ FROM
|
||||
"SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."id" AS "SharedLinkEntity__SharedLinkEntity_user_id",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."name" AS "SharedLinkEntity__SharedLinkEntity_user_name",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."avatarColor" AS "SharedLinkEntity__SharedLinkEntity_user_avatarColor",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."isAdmin" AS "SharedLinkEntity__SharedLinkEntity_user_isAdmin",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."email" AS "SharedLinkEntity__SharedLinkEntity_user_email",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."storageLabel" AS "SharedLinkEntity__SharedLinkEntity_user_storageLabel",
|
||||
@ -317,7 +312,6 @@ FROM
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_user_deletedAt",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."status" AS "SharedLinkEntity__SharedLinkEntity_user_status",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_user_updatedAt",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."memoriesEnabled" AS "SharedLinkEntity__SharedLinkEntity_user_memoriesEnabled",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."quotaSizeInBytes" AS "SharedLinkEntity__SharedLinkEntity_user_quotaSizeInBytes",
|
||||
"SharedLinkEntity__SharedLinkEntity_user"."quotaUsageInBytes" AS "SharedLinkEntity__SharedLinkEntity_user_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -4,7 +4,6 @@
|
||||
SELECT
|
||||
"UserEntity"."id" AS "UserEntity_id",
|
||||
"UserEntity"."name" AS "UserEntity_name",
|
||||
"UserEntity"."avatarColor" AS "UserEntity_avatarColor",
|
||||
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||
"UserEntity"."email" AS "UserEntity_email",
|
||||
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||
@ -15,7 +14,6 @@ SELECT
|
||||
"UserEntity"."deletedAt" AS "UserEntity_deletedAt",
|
||||
"UserEntity"."status" AS "UserEntity_status",
|
||||
"UserEntity"."updatedAt" AS "UserEntity_updatedAt",
|
||||
"UserEntity"."memoriesEnabled" AS "UserEntity_memoriesEnabled",
|
||||
"UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes",
|
||||
"UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -51,7 +49,6 @@ LIMIT
|
||||
SELECT
|
||||
"user"."id" AS "user_id",
|
||||
"user"."name" AS "user_name",
|
||||
"user"."avatarColor" AS "user_avatarColor",
|
||||
"user"."isAdmin" AS "user_isAdmin",
|
||||
"user"."email" AS "user_email",
|
||||
"user"."storageLabel" AS "user_storageLabel",
|
||||
@ -62,7 +59,6 @@ SELECT
|
||||
"user"."deletedAt" AS "user_deletedAt",
|
||||
"user"."status" AS "user_status",
|
||||
"user"."updatedAt" AS "user_updatedAt",
|
||||
"user"."memoriesEnabled" AS "user_memoriesEnabled",
|
||||
"user"."quotaSizeInBytes" AS "user_quotaSizeInBytes",
|
||||
"user"."quotaUsageInBytes" AS "user_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -75,7 +71,6 @@ WHERE
|
||||
SELECT
|
||||
"UserEntity"."id" AS "UserEntity_id",
|
||||
"UserEntity"."name" AS "UserEntity_name",
|
||||
"UserEntity"."avatarColor" AS "UserEntity_avatarColor",
|
||||
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||
"UserEntity"."email" AS "UserEntity_email",
|
||||
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||
@ -86,7 +81,6 @@ SELECT
|
||||
"UserEntity"."deletedAt" AS "UserEntity_deletedAt",
|
||||
"UserEntity"."status" AS "UserEntity_status",
|
||||
"UserEntity"."updatedAt" AS "UserEntity_updatedAt",
|
||||
"UserEntity"."memoriesEnabled" AS "UserEntity_memoriesEnabled",
|
||||
"UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes",
|
||||
"UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes"
|
||||
FROM
|
||||
@ -101,7 +95,6 @@ LIMIT
|
||||
SELECT
|
||||
"UserEntity"."id" AS "UserEntity_id",
|
||||
"UserEntity"."name" AS "UserEntity_name",
|
||||
"UserEntity"."avatarColor" AS "UserEntity_avatarColor",
|
||||
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||
"UserEntity"."email" AS "UserEntity_email",
|
||||
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||
@ -112,7 +105,6 @@ SELECT
|
||||
"UserEntity"."deletedAt" AS "UserEntity_deletedAt",
|
||||
"UserEntity"."status" AS "UserEntity_status",
|
||||
"UserEntity"."updatedAt" AS "UserEntity_updatedAt",
|
||||
"UserEntity"."memoriesEnabled" AS "UserEntity_memoriesEnabled",
|
||||
"UserEntity"."quotaSizeInBytes" AS "UserEntity_quotaSizeInBytes",
|
||||
"UserEntity"."quotaUsageInBytes" AS "UserEntity_quotaUsageInBytes"
|
||||
FROM
|
||||
|
@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { UserMetadata, UserMetadataEntity, UserMetadataKey } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import {
|
||||
IUserRepository,
|
||||
@ -18,6 +19,7 @@ export class UserRepository implements IUserRepository {
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(UserEntity) private userRepository: Repository<UserEntity>,
|
||||
@InjectRepository(UserMetadataEntity) private metadataRepository: Repository<UserMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async get(userId: string, options: UserFindOptions): Promise<UserEntity | null> {
|
||||
@ -25,6 +27,9 @@ export class UserRepository implements IUserRepository {
|
||||
return this.userRepository.findOne({
|
||||
where: { id: userId },
|
||||
withDeleted: options.withDeleted,
|
||||
relations: {
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -69,6 +74,9 @@ export class UserRepository implements IUserRepository {
|
||||
order: {
|
||||
createdAt: 'DESC',
|
||||
},
|
||||
relations: {
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -81,6 +89,13 @@ export class UserRepository implements IUserRepository {
|
||||
return this.save({ ...user, id });
|
||||
}
|
||||
|
||||
async upsertMetadata<T extends UserMetadataKey.PREFERENCES>(
|
||||
id: string,
|
||||
{ key, value }: { key: T; value: UserMetadata[T] },
|
||||
) {
|
||||
await this.metadataRepository.upsert({ userId: id, key, value }, { conflictPaths: { userId: true, key: true } });
|
||||
}
|
||||
|
||||
async delete(user: UserEntity, hard?: boolean): Promise<UserEntity> {
|
||||
return hard ? this.userRepository.remove(user) : this.userRepository.softRemove(user);
|
||||
}
|
||||
@ -142,6 +157,12 @@ export class UserRepository implements IUserRepository {
|
||||
|
||||
private async save(user: Partial<UserEntity>) {
|
||||
const { id } = await this.userRepository.save(user);
|
||||
return this.userRepository.findOneOrFail({ where: { id }, withDeleted: true });
|
||||
return this.userRepository.findOneOrFail({
|
||||
where: { id },
|
||||
withDeleted: true,
|
||||
relations: {
|
||||
metadata: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { Issuer, generators } from 'openid-client';
|
||||
import { Socket } from 'socket.io';
|
||||
import { AuthType } from 'src/constants';
|
||||
import { AuthDto, SignUpDto } from 'src/dtos/auth.dto';
|
||||
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
@ -248,8 +249,13 @@ describe('AuthService', () => {
|
||||
|
||||
it('should sign up the admin', async () => {
|
||||
userMock.getAdmin.mockResolvedValue(null);
|
||||
userMock.create.mockResolvedValue({ ...dto, id: 'admin', createdAt: new Date('2021-01-01') } as UserEntity);
|
||||
await expect(sut.adminSignUp(dto)).resolves.toEqual({
|
||||
userMock.create.mockResolvedValue({
|
||||
...dto,
|
||||
id: 'admin',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
metadata: [] as UserMetadataEntity[],
|
||||
} as UserEntity);
|
||||
await expect(sut.adminSignUp(dto)).resolves.toMatchObject({
|
||||
avatarColor: expect.any(String),
|
||||
id: 'admin',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { PartnerResponseDto } from 'src/dtos/partner.dto';
|
||||
import { UserAvatarColor } from 'src/entities/user.entity';
|
||||
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';
|
||||
@ -23,7 +23,7 @@ const responseDto = {
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
avatarColor: UserAvatarColor.GRAY,
|
||||
quotaSizeInBytes: null,
|
||||
inTimeline: true,
|
||||
quotaUsageInBytes: 0,
|
||||
|
@ -138,13 +138,17 @@ describe(UserService.name, () => {
|
||||
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 });
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
|
||||
id: userStub.user1.id,
|
||||
updatedAt: expect.any(Date),
|
||||
});
|
||||
});
|
||||
|
||||
it('user can only update its information', async () => {
|
||||
@ -174,6 +178,7 @@ describe(UserService.name, () => {
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
|
||||
id: 'user-id',
|
||||
email: 'updated@test.com',
|
||||
updatedAt: expect.any(Date),
|
||||
});
|
||||
});
|
||||
|
||||
@ -210,6 +215,7 @@ describe(UserService.name, () => {
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.user1.id, {
|
||||
id: 'user-id',
|
||||
shouldChangePassword: true,
|
||||
updatedAt: expect.any(Date),
|
||||
});
|
||||
});
|
||||
|
||||
@ -231,7 +237,7 @@ describe(UserService.name, () => {
|
||||
|
||||
await sut.update(authStub.admin, dto);
|
||||
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.admin.id, dto);
|
||||
expect(userMock.update).toHaveBeenCalledWith(userStub.admin.id, { ...dto, updatedAt: expect.any(Date) });
|
||||
});
|
||||
|
||||
it('should not let the another user become an admin', async () => {
|
||||
|
@ -6,6 +6,7 @@ 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 { UserMetadataKey } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity, UserStatus } from 'src/entities/user.entity';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
@ -16,6 +17,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
||||
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
|
||||
import { getPreferences, getPreferencesPartial } from 'src/utils/preferences';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
@ -61,9 +63,21 @@ export class UserService {
|
||||
}
|
||||
|
||||
async create(dto: CreateUserDto): Promise<UserResponseDto> {
|
||||
const user = await this.userCore.createUser(dto);
|
||||
const tempPassword = user.shouldChangePassword ? dto.password : undefined;
|
||||
if (dto.notify) {
|
||||
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);
|
||||
@ -76,7 +90,28 @@ export class UserService {
|
||||
await this.userRepository.syncUsage(dto.id);
|
||||
}
|
||||
|
||||
return this.userCore.updateUser(auth.user, dto.id, dto).then(mapUser);
|
||||
// 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(dto.id, {
|
||||
key: UserMetadataKey.PREFERENCES,
|
||||
value: getPreferencesPartial(user, newPreferences),
|
||||
});
|
||||
}
|
||||
|
||||
const updatedUser = await this.userCore.updateUser(auth.user, dto.id, dto);
|
||||
|
||||
return mapUser(updatedUser);
|
||||
}
|
||||
|
||||
async delete(auth: AuthDto, id: string, dto: DeleteUserDto): Promise<UserResponseDto> {
|
||||
|
39
server/src/utils/preferences.ts
Normal file
39
server/src/utils/preferences.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import _ from 'lodash';
|
||||
import { UserMetadataKey, UserPreferences, getDefaultPreferences } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { getKeysDeep } from 'src/utils/misc';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
|
||||
export const getPreferences = (user: UserEntity) => {
|
||||
const preferences = getDefaultPreferences(user);
|
||||
if (!user.metadata) {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
const item = user.metadata.find(({ key }) => key === UserMetadataKey.PREFERENCES);
|
||||
const partial = item?.value || {};
|
||||
for (const property of getKeysDeep(partial)) {
|
||||
_.set(preferences, property, _.get(partial, property));
|
||||
}
|
||||
|
||||
return preferences;
|
||||
};
|
||||
|
||||
export const getPreferencesPartial = (user: { email: string }, newPreferences: UserPreferences) => {
|
||||
const defaultPreferences = getDefaultPreferences(user);
|
||||
const partial: DeepPartial<UserPreferences> = {};
|
||||
for (const property of getKeysDeep(defaultPreferences)) {
|
||||
const newValue = _.get(newPreferences, property);
|
||||
const isEmpty = newValue === undefined || newValue === null || newValue === '';
|
||||
const defaultValue = _.get(defaultPreferences, property);
|
||||
const isEqual = newValue === defaultValue || _.isEqual(newValue, defaultValue);
|
||||
|
||||
if (isEmpty || isEqual) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_.set(partial, property, newValue);
|
||||
}
|
||||
|
||||
return partial;
|
||||
};
|
24
server/test/fixtures/user.stub.ts
vendored
24
server/test/fixtures/user.stub.ts
vendored
@ -1,4 +1,5 @@
|
||||
import { UserAvatarColor, UserEntity } from 'src/entities/user.entity';
|
||||
import { UserAvatarColor, UserMetadataKey } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
|
||||
export const userDto = {
|
||||
@ -39,8 +40,7 @@ export const userStub = {
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
metadata: [],
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
}),
|
||||
@ -57,8 +57,14 @@ export const userStub = {
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
metadata: [
|
||||
{
|
||||
user: authStub.user1.user,
|
||||
userId: authStub.user1.user.id,
|
||||
key: UserMetadataKey.PREFERENCES,
|
||||
value: { avatar: { color: UserAvatarColor.PRIMARY } },
|
||||
},
|
||||
],
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
}),
|
||||
@ -75,8 +81,6 @@ export const userStub = {
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
}),
|
||||
@ -93,8 +97,6 @@ export const userStub = {
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
}),
|
||||
@ -111,8 +113,6 @@ export const userStub = {
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
}),
|
||||
@ -129,8 +129,6 @@ export const userStub = {
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
tags: [],
|
||||
assets: [],
|
||||
memoriesEnabled: true,
|
||||
avatarColor: UserAvatarColor.PRIMARY,
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
}),
|
||||
|
@ -22,5 +22,6 @@ export const newUserRepositoryMock = (reset = true): Mocked<IUserRepository> =>
|
||||
hasAdmin: vitest.fn(),
|
||||
updateUsage: vitest.fn(),
|
||||
syncUsage: vitest.fn(),
|
||||
upsertMetadata: vitest.fn(),
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user