From d7f46ccee364e438e8ac8ac4011eaa0df8806a27 Mon Sep 17 00:00:00 2001 From: Devesh Kolte Date: Sun, 19 Jul 2026 20:00:13 +0530 Subject: [PATCH 1/2] fix(server): store null instead of empty string for session deviceType and deviceOS --- server/src/database.ts | 4 ++-- server/src/dtos/session.dto.ts | 8 ++++---- ...SessionDeviceTypeAndOSEmptyStringToNull.ts | 19 +++++++++++++++++++ server/src/schema/tables/session.table.ts | 8 ++++---- 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 server/src/schema/migrations/1784471379379-ConvertSessionDeviceTypeAndOSEmptyStringToNull.ts diff --git a/server/src/database.ts b/server/src/database.ts index 1770b9d720..c37a98b094 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -230,8 +230,8 @@ export type Session = { createdAt: Date; updatedAt: Date; expiresAt: Date | null; - deviceOS: string; - deviceType: string; + deviceOS: string | null; + deviceType: string | null; appVersion: string | null; pinExpiresAt: Date | null; isPendingSyncReset: boolean; diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index c81dac6d79..f49ac87bf1 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -5,8 +5,8 @@ import z from 'zod'; const SessionCreateSchema = z .object({ duration: z.int().min(1).optional().describe('Session duration in seconds'), - deviceType: z.string().optional().describe('Device type'), - deviceOS: z.string().optional().describe('Device OS'), + deviceType: z.string().nullish().transform((val) => (val === '' ? null : val)).describe('Device type'), + deviceOS: z.string().nullish().transform((val) => (val === '' ? null : val)).describe('Device OS'), }) .meta({ id: 'SessionCreateDto' }); @@ -23,8 +23,8 @@ const SessionResponseSchema = z updatedAt: z.string().describe('Last update date'), expiresAt: z.string().optional().describe('Expiration date'), current: z.boolean().describe('Is current session'), - deviceType: z.string().describe('Device type'), - deviceOS: z.string().describe('Device OS'), + deviceType: z.string().nullable().describe('Device type'), + deviceOS: z.string().nullable().describe('Device OS'), appVersion: z.string().nullable().describe('App version'), isPendingSyncReset: z.boolean().describe('Is pending sync reset'), }) diff --git a/server/src/schema/migrations/1784471379379-ConvertSessionDeviceTypeAndOSEmptyStringToNull.ts b/server/src/schema/migrations/1784471379379-ConvertSessionDeviceTypeAndOSEmptyStringToNull.ts new file mode 100644 index 0000000000..2bfbfd08e0 --- /dev/null +++ b/server/src/schema/migrations/1784471379379-ConvertSessionDeviceTypeAndOSEmptyStringToNull.ts @@ -0,0 +1,19 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "session" ALTER COLUMN "deviceType" DROP NOT NULL;`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceType" SET DEFAULT NULL;`.execute(db); + await sql`UPDATE "session" SET "deviceType" = NULL WHERE "deviceType" = '';`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceOS" DROP NOT NULL;`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceOS" SET DEFAULT NULL;`.execute(db); + await sql`UPDATE "session" SET "deviceOS" = NULL WHERE "deviceOS" = '';`.execute(db); +} + +export async function down(): Promise { + await sql`UPDATE "session" SET "deviceOS" = '' WHERE "deviceOS" IS NULL;`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceOS" SET DEFAULT '';`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceOS" SET NOT NULL;`.execute(db); + await sql`UPDATE "session" SET "deviceType" = '' WHERE "deviceType" IS NULL;`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceType" SET DEFAULT '';`.execute(db); + await sql`ALTER TABLE "session" ALTER COLUMN "deviceType" SET NOT NULL;`.execute(db); +} diff --git a/server/src/schema/tables/session.table.ts b/server/src/schema/tables/session.table.ts index 950c1eeffd..2ea99fb2e0 100644 --- a/server/src/schema/tables/session.table.ts +++ b/server/src/schema/tables/session.table.ts @@ -35,11 +35,11 @@ export class SessionTable { @ForeignKeyColumn(() => SessionTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', nullable: true }) parentId!: string | null; - @Column({ default: '' }) - deviceType!: Generated; + @Column({ nullable: true, default: null }) + deviceType!: string | null; - @Column({ default: '' }) - deviceOS!: Generated; + @Column({ nullable: true, default: null }) + deviceOS!: string | null; @Column({ nullable: true }) appVersion!: string | null; From 71a9070f4a2e73e2a28024b81f21decb4269537d Mon Sep 17 00:00:00 2001 From: Devesh Kolte Date: Mon, 20 Jul 2026 19:04:42 +0530 Subject: [PATCH 2/2] fix(server): store null instead of empty string for person thumbnailPath --- server/src/database.ts | 2 +- server/src/dtos/person.dto.ts | 2 +- ...6-ConvertPersonThumbnailPathEmptyStringToNull.ts | 13 +++++++++++++ server/src/schema/tables/person.table.ts | 4 ++-- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 server/src/schema/migrations/1784554467506-ConvertPersonThumbnailPathEmptyStringToNull.ts diff --git a/server/src/database.ts b/server/src/database.ts index c37a98b094..78a826373b 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -251,7 +251,7 @@ export type Person = { color: string | null; faceAssetId: string | null; isHidden: boolean; - thumbnailPath: string; + thumbnailPath: string | null; }; export type AssetFace = { diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index fa56ded176..b1408198bb 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -64,7 +64,7 @@ export const PersonResponseSchema = z name: z.string().describe('Person name'), // TODO: use `isoDateToDate` when using `ZodSerializerDto` on the controllers. birthDate: z.string().meta({ format: 'date' }).describe('Person date of birth').nullable(), - thumbnailPath: z.string().describe('Thumbnail path'), + thumbnailPath: z.string().nullable().describe('Thumbnail path'), isHidden: z.boolean().describe('Is hidden'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. updatedAt: z diff --git a/server/src/schema/migrations/1784554467506-ConvertPersonThumbnailPathEmptyStringToNull.ts b/server/src/schema/migrations/1784554467506-ConvertPersonThumbnailPathEmptyStringToNull.ts new file mode 100644 index 0000000000..8abeda8cb9 --- /dev/null +++ b/server/src/schema/migrations/1784554467506-ConvertPersonThumbnailPathEmptyStringToNull.ts @@ -0,0 +1,13 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await sql`ALTER TABLE "person" ALTER COLUMN "thumbnailPath" DROP NOT NULL;`.execute(db); + await sql`ALTER TABLE "person" ALTER COLUMN "thumbnailPath" SET DEFAULT NULL;`.execute(db); + await sql`UPDATE "person" SET "thumbnailPath" = NULL WHERE "thumbnailPath" = '';`.execute(db); +} + +export async function down(): Promise { + await sql`UPDATE "person" SET "thumbnailPath" = '' WHERE "thumbnailPath" IS NULL;`.execute(db); + await sql`ALTER TABLE "person" ALTER COLUMN "thumbnailPath" SET DEFAULT '';`.execute(db); + await sql`ALTER TABLE "person" ALTER COLUMN "thumbnailPath" SET NOT NULL;`.execute(db); +} diff --git a/server/src/schema/tables/person.table.ts b/server/src/schema/tables/person.table.ts index 35447acfd0..4600702ed0 100644 --- a/server/src/schema/tables/person.table.ts +++ b/server/src/schema/tables/person.table.ts @@ -46,8 +46,8 @@ export class PersonTable { @Column({ default: '' }) name!: Generated; - @Column({ default: '' }) - thumbnailPath!: Generated; + @Column({ nullable: true, default: null }) + thumbnailPath!: string | null; @Column({ type: 'boolean', default: false }) isHidden!: Generated;