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:
Jason Rasmussen 2024-05-22 08:13:36 -04:00 committed by GitHub
parent a4887bfa7e
commit 06ce8247cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 267 additions and 126 deletions

View file

@ -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,
];

View 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>;
}

View file

@ -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[];
}