Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions drizzle/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { pgTable, index, unique, check, serial, varchar, boolean, timestamp, jsonb, foreignKey, integer, numeric, text, date, inet, time, customType } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"

// Convention: every `timestamp` (without time zone) column stores UTC.
// src/lib/db.ts registers a pg type parser that reads them back as UTC,
// so the app behaves identically on Vercel (TZ=UTC) and self-hosted
// servers with a local timezone.

// Postgres `bytea` — not first-class in drizzle-orm/pg-core, defined here so
// the p12 cert columns get a real TS type instead of `unknown`.
const bytea = customType<{ data: Buffer }>({
Expand Down
17 changes: 16 additions & 1 deletion src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Pool, PoolClient, QueryResult } from 'pg';
import { Pool, PoolClient, QueryResult, types, defaults } from 'pg';
import { logger } from './logger';

// Global connection pool
Expand All @@ -9,6 +9,21 @@ let pool: Pool;
*/
function getPool(): Pool {
if (!pool) {
// All `timestamp without time zone` columns store UTC by convention
// (contacts.datetime, sync log timestamps, ...). node-pg's default parser
// interprets them in the server's local timezone, which silently skews
// QRZ/LoTW date/time emission and confirmation matching on self-hosted
// installs with TZ != UTC (on Vercel TZ is UTC, so this is a no-op).
// OID 1114 = timestamp without time zone.
types.setTypeParser(1114, (value: string) => new Date(value.replace(' ', 'T') + 'Z'));
Comment thread
patrickrb marked this conversation as resolved.

// The write side must match: node-pg serializes Date params as *local*
// time strings by default, and `timestamp` columns ignore the trailing
// offset — so on a non-UTC Node process a Date would be stored as local
// wall-clock and then misread as UTC by the parser above. This flag makes
// pg serialize Date params as UTC (lossless for timestamptz too).
defaults.parseInputDatesAsUTC = true;

const sslConfig = process.env.DATABASE_SSL === 'true'
? { rejectUnauthorized: false }
: false;
Expand Down
Loading