From 985a13c1e46e800f7b3ef39a292ecd1968f8c7a6 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 23 Jun 2025 01:36:18 +0200 Subject: [PATCH] Fix date handling (force iso on drizzle side) --- api/src/db/schema/entries.ts | 12 ++++++------ api/src/db/schema/history.ts | 7 ++++--- api/src/db/schema/mqueue.ts | 15 +++++---------- api/src/db/schema/seasons.ts | 10 +++++----- api/src/db/schema/shows.ts | 10 +++++----- api/src/db/schema/staff.ts | 9 ++++----- api/src/db/schema/studios.ts | 8 ++++---- api/src/db/schema/videos.ts | 8 ++++---- api/src/db/schema/watchlist.ts | 15 ++++++++------- api/src/db/utils.ts | 28 ++++++++++++++++++++++++---- front/src/models/utils/utils.ts | 6 +++++- 11 files changed, 74 insertions(+), 54 deletions(-) diff --git a/api/src/db/schema/entries.ts b/api/src/db/schema/entries.ts index 0c3a4a60..5425367d 100644 --- a/api/src/db/schema/entries.ts +++ b/api/src/db/schema/entries.ts @@ -8,11 +8,11 @@ import { primaryKey, real, text, - timestamp, unique, uuid, varchar, } from "drizzle-orm/pg-core"; +import { timestamp } from "../utils"; import { shows } from "./shows"; import { image, language, schema } from "./utils"; import { entryVideoJoin } from "./videos"; @@ -67,14 +67,14 @@ export const entries = schema.table( externalId: entry_extid(), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), - availableSince: timestamp({ withTimezone: true, mode: "string" }), - nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(), + availableSince: timestamp({ withTimezone: true, mode: "iso" }), + nextRefresh: timestamp({ withTimezone: true, mode: "iso" }).notNull(), }, (t) => [ unique().on(t.showPk, t.seasonNumber, t.episodeNumber), diff --git a/api/src/db/schema/history.ts b/api/src/db/schema/history.ts index affe6bd5..84b8011b 100644 --- a/api/src/db/schema/history.ts +++ b/api/src/db/schema/history.ts @@ -1,5 +1,6 @@ import { sql } from "drizzle-orm"; -import { check, index, integer, timestamp } from "drizzle-orm/pg-core"; +import { check, index, integer } from "drizzle-orm/pg-core"; +import { timestamp } from "../utils"; import { entries } from "./entries"; import { profiles } from "./profiles"; import { schema } from "./utils"; @@ -18,9 +19,9 @@ export const history = schema.table( videoPk: integer().references(() => videos.pk, { onDelete: "set null" }), percent: integer().notNull().default(0), time: integer(), - playedDate: timestamp({ withTimezone: true, mode: "string" }) + playedDate: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), + .default(sql`now()`), }, (t) => [ index("history_play_date").on(t.playedDate.desc()), diff --git a/api/src/db/schema/mqueue.ts b/api/src/db/schema/mqueue.ts index f912493b..676bcc5e 100644 --- a/api/src/db/schema/mqueue.ts +++ b/api/src/db/schema/mqueue.ts @@ -1,11 +1,6 @@ -import { - index, - integer, - jsonb, - timestamp, - uuid, - varchar, -} from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import { index, integer, jsonb, uuid, varchar } from "drizzle-orm/pg-core"; +import { timestamp } from "../utils"; import { schema } from "./utils"; export const mqueue = schema.table( @@ -15,9 +10,9 @@ export const mqueue = schema.table( kind: varchar({ length: 255 }).notNull(), message: jsonb().notNull(), attempt: integer().notNull().default(0), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), + .default(sql`now()`), }, (t) => [index("mqueue_created").on(t.createdAt)], ); diff --git a/api/src/db/schema/seasons.ts b/api/src/db/schema/seasons.ts index 52f913c0..b529eee9 100644 --- a/api/src/db/schema/seasons.ts +++ b/api/src/db/schema/seasons.ts @@ -6,11 +6,11 @@ import { jsonb, primaryKey, text, - timestamp, unique, uuid, varchar, } from "drizzle-orm/pg-core"; +import { timestamp } from "../utils"; import { shows } from "./shows"; import { image, language, schema } from "./utils"; @@ -42,13 +42,13 @@ export const seasons = schema.table( externalId: season_extid(), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), - nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(), + nextRefresh: timestamp({ withTimezone: true, mode: "iso" }).notNull(), }, (t) => [ unique().on(t.showPk, t.seasonNumber), diff --git a/api/src/db/schema/shows.ts b/api/src/db/schema/shows.ts index 35a75bec..697e8ba3 100644 --- a/api/src/db/schema/shows.ts +++ b/api/src/db/schema/shows.ts @@ -9,11 +9,11 @@ import { primaryKey, smallint, text, - timestamp, uuid, varchar, } from "drizzle-orm/pg-core"; import type { Image, Original } from "~/models/utils"; +import { timestamp } from "../utils"; import { entries } from "./entries"; import { seasons } from "./seasons"; import { roles } from "./staff"; @@ -87,13 +87,13 @@ export const shows = schema.table( externalId: externalid(), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), - nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(), + nextRefresh: timestamp({ withTimezone: true, mode: "iso" }).notNull(), }, (t) => [ check("rating_valid", sql`${t.rating} between 0 and 100`), diff --git a/api/src/db/schema/staff.ts b/api/src/db/schema/staff.ts index e261262a..375e2fdb 100644 --- a/api/src/db/schema/staff.ts +++ b/api/src/db/schema/staff.ts @@ -3,13 +3,12 @@ import { index, integer, jsonb, - primaryKey, text, - timestamp, uuid, varchar, } from "drizzle-orm/pg-core"; import type { Character } from "~/models/staff"; +import { timestamp } from "../utils"; import { shows } from "./shows"; import { externalid, image, schema } from "./utils"; @@ -32,10 +31,10 @@ export const staff = schema.table("staff", { image: image(), externalId: externalid(), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), }); diff --git a/api/src/db/schema/studios.ts b/api/src/db/schema/studios.ts index 1c772ee6..cb4f0f9c 100644 --- a/api/src/db/schema/studios.ts +++ b/api/src/db/schema/studios.ts @@ -4,10 +4,10 @@ import { integer, primaryKey, text, - timestamp, uuid, varchar, } from "drizzle-orm/pg-core"; +import { timestamp } from "../utils"; import { shows } from "./shows"; import { externalid, image, language, schema } from "./utils"; @@ -17,10 +17,10 @@ export const studios = schema.table("studios", { slug: varchar({ length: 255 }).notNull().unique(), externalId: externalid(), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), }); diff --git a/api/src/db/schema/videos.ts b/api/src/db/schema/videos.ts index 2e4fda6c..f5f18340 100644 --- a/api/src/db/schema/videos.ts +++ b/api/src/db/schema/videos.ts @@ -5,12 +5,12 @@ import { jsonb, primaryKey, text, - timestamp, unique, uuid, varchar, } from "drizzle-orm/pg-core"; import type { Guess } from "~/models/video"; +import { timestamp } from "../utils"; import { entries } from "./entries"; import { schema } from "./utils"; @@ -25,10 +25,10 @@ export const videos = schema.table( version: integer().notNull().default(1), guess: jsonb().$type().notNull(), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), }, diff --git a/api/src/db/schema/watchlist.ts b/api/src/db/schema/watchlist.ts index 05ea33cf..4c8e59d6 100644 --- a/api/src/db/schema/watchlist.ts +++ b/api/src/db/schema/watchlist.ts @@ -1,5 +1,6 @@ import { sql } from "drizzle-orm"; -import { check, integer, primaryKey, timestamp } from "drizzle-orm/pg-core"; +import { check, integer, primaryKey } from "drizzle-orm/pg-core"; +import { timestamp } from "../utils"; import { entries } from "./entries"; import { profiles } from "./profiles"; import { shows } from "./shows"; @@ -29,14 +30,14 @@ export const watchlist = schema.table( score: integer(), - startedAt: timestamp({ withTimezone: true, mode: "string" }), - lastPlayedAt: timestamp({ withTimezone: true, mode: "string" }), - completedAt: timestamp({ withTimezone: true, mode: "string" }), + startedAt: timestamp({ withTimezone: true, mode: "iso" }), + lastPlayedAt: timestamp({ withTimezone: true, mode: "iso" }), + completedAt: timestamp({ withTimezone: true, mode: "iso" }), - createdAt: timestamp({ withTimezone: true, mode: "string" }) + createdAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() - .defaultNow(), - updatedAt: timestamp({ withTimezone: true, mode: "string" }) + .default(sql`now()`), + updatedAt: timestamp({ withTimezone: true, mode: "iso" }) .notNull() .$onUpdate(() => sql`now()`), }, diff --git a/api/src/db/utils.ts b/api/src/db/utils.ts index c57acffa..5add5b95 100644 --- a/api/src/db/utils.ts +++ b/api/src/db/utils.ts @@ -1,19 +1,23 @@ import { type Column, type ColumnsSelection, + getTableColumns, + is, type SQL, type SQLWrapper, type Subquery, + sql, Table, View, ViewBaseConfig, - getTableColumns, - is, - sql, } from "drizzle-orm"; import type { CasingCache } from "drizzle-orm/casing"; import type { AnyMySqlSelect } from "drizzle-orm/mysql-core"; -import type { AnyPgSelect, SelectedFieldsFlat } from "drizzle-orm/pg-core"; +import { + type AnyPgSelect, + customType, + type SelectedFieldsFlat, +} from "drizzle-orm/pg-core"; import type { AnySQLiteSelect } from "drizzle-orm/sqlite-core"; import type { WithSubquery } from "drizzle-orm/subquery"; import { db } from "./index"; @@ -148,3 +152,19 @@ export const isUniqueConstraint = (e: unknown): boolean => { typeof e === "object" && e != null && "code" in e && e.code === "23505" ); }; + +export const timestamp = customType<{ + data: string; + driverData: string; + config: { withTimezone: boolean; precision?: number; mode: "iso" }; +}>({ + dataType(config) { + const precision = config?.precision ? ` (${config.precision})` : ""; + return `timestamp${precision}${config?.withTimezone ? " with time zone" : ""}`; + }, + fromDriver(value: string): string { + // postgres format: 2025-06-22 16:13:37.489301+00 + // what we want: 2025-06-22T16:13:37Z + return `${value.substring(0, 10)}T${value.substring(11, 19)}Z`; + }, +}); diff --git a/front/src/models/utils/utils.ts b/front/src/models/utils/utils.ts index bb37184d..07ec426e 100644 --- a/front/src/models/utils/utils.ts +++ b/front/src/models/utils/utils.ts @@ -1,3 +1,7 @@ import { z } from "zod/v4"; -export const zdate = z.coerce.date; +export const zdate = () => + z.iso + .date() + .or(z.iso.datetime()) + .transform((x) => new Date(x));