diff --git a/server/src/database.ts b/server/src/database.ts index 100ba451e7..c2c222fbc2 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 40b4e25b6d..e04719af90 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 1d5fe64aa1..0e4da0d441 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 528163e57c..02c28398a1 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 0000000000..7784ab2437 --- /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 50d56d9067..1ebcf67916 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;