diff --git a/server/src/database.ts b/server/src/database.ts index 1770b9d720..148a5fd461 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -128,7 +128,7 @@ export type User = { name: string; email: string; avatarColor: UserAvatarColor | null; - profileImagePath: string; + profileImagePath: string | null; profileChangedAt: Date; }; diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index 40b4e25b6d..a12ee3e515 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -32,7 +32,7 @@ const LoginResponseSchema = z userId: z.uuidv4().describe('User ID'), userEmail: toEmail.describe('User email'), name: z.string().describe('User name'), - profileImagePath: z.string().describe('Profile image path'), + profileImagePath: z.string().nullable().describe('Profile image path'), isAdmin: z.boolean().describe('Is admin user'), shouldChangePassword: z.boolean().describe('Should change password'), isOnboarded: z.boolean().describe('Is onboarded'), diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 528163e57c..7f501ba1b2 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -27,7 +27,7 @@ export const UserResponseSchema = z id: z.uuidv4().describe('User ID'), name: z.string().describe('User name'), email: toEmail.describe('User email'), - profileImagePath: z.string().describe('Profile image path'), + profileImagePath: z.string().nullable().describe('Profile image path'), avatarColor: UserAvatarColorSchema, // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. profileChangedAt: z.string().meta({ format: 'date-time' }).describe('Profile change date'), diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index 09a89fd9bb..2ee1e8c796 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -123,7 +123,7 @@ type UserEvent = { deletedAt: Date | null; status: UserStatus; email: string; - profileImagePath: string; + profileImagePath: string | null; isAdmin: boolean; shouldChangePassword: boolean; avatarColor: UserAvatarColor | null; diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 20b41c80f8..0352801fde 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -81,7 +81,7 @@ export class UserRepository { return this.db .selectFrom('user') .select(['id', 'profileImagePath']) - .where('profileImagePath', '!=', sql.lit('')) + .where('profileImagePath', 'is not', null) .limit(sql.lit(3)) .execute(); } diff --git a/server/src/schema/migrations/1784738477790-ConvertUserProfileImagePathEmptyStringToNull.ts b/server/src/schema/migrations/1784738477790-ConvertUserProfileImagePathEmptyStringToNull.ts new file mode 100644 index 0000000000..74e9bb4261 --- /dev/null +++ b/server/src/schema/migrations/1784738477790-ConvertUserProfileImagePathEmptyStringToNull.ts @@ -0,0 +1,13 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "user" ALTER COLUMN "profileImagePath" DROP NOT NULL;`.execute(db); + await sql`ALTER TABLE "user" ALTER COLUMN "profileImagePath" SET DEFAULT NULL;`.execute(db); + await sql`UPDATE "user" SET "profileImagePath" = NULL WHERE "profileImagePath" = '';`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`UPDATE "user" SET "profileImagePath" = '' WHERE "profileImagePath" IS NULL;`.execute(db); + await sql`ALTER TABLE "user" ALTER COLUMN "profileImagePath" SET DEFAULT '';`.execute(db); + await sql`ALTER TABLE "user" ALTER COLUMN "profileImagePath" SET NOT NULL;`.execute(db); +} diff --git a/server/src/schema/tables/user.table.ts b/server/src/schema/tables/user.table.ts index 0839924d2a..0cad7d317a 100644 --- a/server/src/schema/tables/user.table.ts +++ b/server/src/schema/tables/user.table.ts @@ -40,8 +40,8 @@ export class UserTable { @CreateDateColumn() createdAt!: Generated; - @Column({ default: '' }) - profileImagePath!: Generated; + @Column({ nullable: true, default: null }) + profileImagePath!: string | null; @Column({ type: 'boolean', default: false }) isAdmin!: Generated; diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts index 22f06e2ed9..2c608b8ed9 100644 --- a/server/src/services/cli.service.ts +++ b/server/src/services/cli.service.ts @@ -189,7 +189,9 @@ export class CliService extends BaseService { } for (const user of users) { - paths.push(user.profileImagePath); + if (user.profileImagePath) { + paths.push(user.profileImagePath); + } } for (const asset of assets) { diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index c32c11879f..567cea3285 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -134,10 +134,10 @@ export class UserService extends BaseService { async deleteProfileImage(auth: AuthDto): Promise { const user = await this.findOrFail(auth.user.id, { withDeleted: false }); - if (user.profileImagePath === '') { + if (user.profileImagePath == null) { throw new BadRequestException("Can't delete a missing profile Image"); } - await this.userRepository.update(auth.user.id, { profileImagePath: '', profileChangedAt: new Date() }); + await this.userRepository.update(auth.user.id, { profileImagePath: null, profileChangedAt: new Date() }); await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [user.profileImagePath] } }); }