From 7fdbcb0878e3bc59decf7212479af09d61693420 Mon Sep 17 00:00:00 2001 From: Devesh Kolte Date: Tue, 21 Jul 2026 18:44:52 +0530 Subject: [PATCH] fix(server): store null instead of empty string for user name --- server/src/database.ts | 4 ++-- server/src/dtos/auth.dto.ts | 4 ++-- server/src/dtos/sync.dto.ts | 2 +- server/src/dtos/user.dto.ts | 8 ++++---- ...83176-ConvertUserProfileNameEmptyStringToNull.ts | 13 +++++++++++++ server/src/schema/tables/user.table.ts | 5 ++--- 6 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 server/src/schema/migrations/1784639683176-ConvertUserProfileNameEmptyStringToNull.ts diff --git a/server/src/database.ts b/server/src/database.ts index 1770b9d720b5d..9a51e91b9b5f2 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -22,7 +22,7 @@ import { UserMetadataItem } from 'src/types'; export type AuthUser = { id: string; isAdmin: boolean; - name: string; + name: string | null; email: string; quotaUsageInBytes: number; quotaSizeInBytes: number | null; @@ -125,7 +125,7 @@ export type Asset = { export type User = { id: string; - name: string; + name: string | null; email: string; avatarColor: UserAvatarColor | null; profileImagePath: string; diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index 40b4e25b6d29d..e04719af9091a 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -31,7 +31,7 @@ const LoginResponseSchema = z accessToken: z.string().describe('Access token'), userId: z.uuidv4().describe('User ID'), userEmail: toEmail.describe('User email'), - name: z.string().describe('User name'), + name: z.string().nullable().describe('User name'), profileImagePath: z.string().describe('Profile image path'), isAdmin: z.boolean().describe('Is admin user'), shouldChangePassword: z.boolean().describe('Should change password'), @@ -64,7 +64,7 @@ const LogoutResponseSchema = z .meta({ id: 'LogoutResponseDto' }); const SignUpSchema = LoginCredentialSchema.extend({ - name: z.string().describe('User name').meta({ example: 'Admin' }), + name: z.string().transform(v => v === '' ? null : v).describe('User name').meta({ example: 'Admin' }), }).meta({ id: 'SignUpDto' }); const ChangePasswordSchema = z diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 1d5fe64aa13f7..0e4da0d441488 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -20,7 +20,7 @@ import z from 'zod'; const SyncUserV1Schema = z .object({ id: z.uuidv4().describe('User ID'), - name: z.string().describe('User name'), + name: z.string().nullable().describe('User name'), email: z.string().describe('User email'), avatarColor: UserAvatarColorSchema.nullish(), deletedAt: isoDatetimeToDate.nullable().describe('User deleted at'), diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 528163e57ca78..02c28398a1595 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -15,7 +15,7 @@ export const UserUpdateMeSchema = z .optional() .describe('User password (deprecated, use change password endpoint)') .meta({ deprecated: true }), - name: z.string().optional().describe('User name'), + name: z.string().nullish().transform(v => v === '' ? null : v), avatarColor: UserAvatarColorSchema.nullish(), }) .meta({ id: 'UserUpdateMeDto' }); @@ -25,7 +25,7 @@ export class UserUpdateMeDto extends createZodDto(UserUpdateMeSchema) {} export const UserResponseSchema = z .object({ id: z.uuidv4().describe('User ID'), - name: z.string().describe('User name'), + name: z.string().nullable().describe('User name'), email: toEmail.describe('User email'), profileImagePath: z.string().describe('Profile image path'), avatarColor: UserAvatarColorSchema, @@ -78,7 +78,7 @@ export const UserAdminCreateSchema = z .object({ email: toEmail.describe('User email'), password: z.string().describe('User password'), - name: z.string().describe('User name'), + name: z.string().transform(v => v === '' ? null : v).describe('User name'), avatarColor: UserAvatarColorSchema.nullish(), pinCode: z.string().regex(pinCodeRegex).nullable().optional().describe('PIN code').meta({ example: '123456' }), storageLabel: z.string().pipe(sanitizeFilename).nullish().describe('Storage label'), @@ -96,7 +96,7 @@ const UserAdminUpdateSchema = z email: toEmail.optional().describe('User email'), password: z.string().optional().describe('User password'), pinCode: z.string().regex(pinCodeRegex).nullable().optional().describe('PIN code').meta({ example: '123456' }), - name: z.string().optional().describe('User name'), + name: z.string().nullish().transform(v => v === '' ? null : v), avatarColor: UserAvatarColorSchema.nullish(), storageLabel: z.string().pipe(sanitizeFilename).nullish().describe('Storage label'), shouldChangePassword: z.boolean().optional().describe('Require password change on next login'), diff --git a/server/src/schema/migrations/1784639683176-ConvertUserProfileNameEmptyStringToNull.ts b/server/src/schema/migrations/1784639683176-ConvertUserProfileNameEmptyStringToNull.ts new file mode 100644 index 0000000000000..7784ab243710a --- /dev/null +++ b/server/src/schema/migrations/1784639683176-ConvertUserProfileNameEmptyStringToNull.ts @@ -0,0 +1,13 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "user" ALTER COLUMN "name" DROP NOT NULL;`.execute(db); + await sql`ALTER TABLE "user" ALTER COLUMN "name" SET DEFAULT NULL;`.execute(db); + await sql`UPDATE "user" SET "name" = NULL WHERE "name" = '';`.execute(db); +} + +export async function down(db: Kysely): Promise { + await sql`UPDATE "user" SET "name" = '' WHERE "name" IS NULL;`.execute(db); + await sql`ALTER TABLE "user" ALTER COLUMN "name" SET DEFAULT '';`.execute(db); + await sql`ALTER TABLE "user" ALTER COLUMN "name" SET NOT NULL;`.execute(db); +} diff --git a/server/src/schema/tables/user.table.ts b/server/src/schema/tables/user.table.ts index 0839924d2a0c5..bf49ac7db302a 100644 --- a/server/src/schema/tables/user.table.ts +++ b/server/src/schema/tables/user.table.ts @@ -64,9 +64,8 @@ export class UserTable { @Column({ unique: true, nullable: true, default: null }) storageLabel!: string | null; - // TODO remove default, make nullable, and convert empty spaces to null - @Column({ default: '' }) - name!: string; + @Column({ nullable: true, default: null }) + name!: string | null; @Column({ type: 'bigint', nullable: true }) quotaSizeInBytes!: ColumnType | null;