fix: migration

This commit is contained in:
Krispire Admin
2026-06-11 01:11:10 +05:30
parent 0655b2314d
commit 0b24ab4b45
6 changed files with 177 additions and 4 deletions
+4 -2
View File
@@ -105,9 +105,11 @@ func (s *ReminderService) SetEnabled(ctx context.Context, userID string, enabled
}
// SetTimezone validates the IANA zone before storing so a bad value can never
// break the (timezone-using) candidate scan.
// break the (timezone-using) candidate scan. Legacy aliases are canonicalized
// first (browsers report e.g. "Asia/Calcutta"): Go resolves them but a
// Postgres without the tzdata backward links rejects them in AT TIME ZONE.
func (s *ReminderService) SetTimezone(ctx context.Context, userID, tz string) error {
tz = strings.TrimSpace(tz)
tz = canonicalTZ(strings.TrimSpace(tz))
if tz == "" {
tz = "UTC"
}
+5
View File
@@ -94,6 +94,11 @@ const fallbackTZ = "Asia/Kolkata"
// genuine UTC user re-reports their real zone on next load.
func (s *SadhnaService) resolveTZ(ctx context.Context, userID string) (string, *time.Location) {
tz, _ := s.logs.UserTimezone(ctx, userID)
// Canonicalize legacy aliases (e.g. stored "Asia/Calcutta") — the name
// is fed into SQL AT TIME ZONE, which rejects aliases on a Postgres
// without the tzdata backward links. Covers rows written before
// SetTimezone started canonicalizing.
tz = canonicalTZ(tz)
if tz == "" || tz == "UTC" {
tz = fallbackTZ
}
+89
View File
@@ -0,0 +1,89 @@
package service
// Legacy IANA timezone aliases → canonical names.
//
// Browsers report Intl.DateTimeFormat().resolvedOptions().timeZone, and the
// ICU/CLDR data they ship still uses several LEGACY ids as its canonical
// form — Safari and Chrome genuinely say "Asia/Calcutta", not "Asia/Kolkata".
// Go's time.LoadLocation resolves these aliases, so they pass validation and
// get stored; but a Postgres built against tzdata without the "backward"
// links (Debian/Alpine images with the tzdata-legacy split) rejects them in
// AT TIME ZONE with SQLSTATE 22023. Everything we store or hand to SQL goes
// through canonicalTZ first.
var legacyTZ = map[string]string{
// Ids ICU/CLDR still reports as canonical (the ones browsers send).
"Asia/Calcutta": "Asia/Kolkata",
"Asia/Katmandu": "Asia/Kathmandu",
"Asia/Rangoon": "Asia/Yangon",
"Asia/Saigon": "Asia/Ho_Chi_Minh",
"America/Buenos_Aires": "America/Argentina/Buenos_Aires",
"America/Catamarca": "America/Argentina/Catamarca",
"America/Cordoba": "America/Argentina/Cordoba",
"America/Jujuy": "America/Argentina/Jujuy",
"America/Mendoza": "America/Argentina/Mendoza",
"America/Indianapolis": "America/Indiana/Indianapolis",
"America/Louisville": "America/Kentucky/Louisville",
"America/Godthab": "America/Nuuk",
"Atlantic/Faeroe": "Atlantic/Faroe",
"Pacific/Ponape": "Pacific/Pohnpei",
"Pacific/Truk": "Pacific/Chuuk",
"Europe/Kiev": "Europe/Kyiv",
// Other common backward links, in case a zone arrives from an older
// client or was set by hand.
"Asia/Ashkhabad": "Asia/Ashgabat",
"Asia/Chongqing": "Asia/Shanghai",
"Asia/Chungking": "Asia/Shanghai",
"Asia/Dacca": "Asia/Dhaka",
"Asia/Harbin": "Asia/Shanghai",
"Asia/Istanbul": "Europe/Istanbul",
"Asia/Kashgar": "Asia/Urumqi",
"Asia/Macao": "Asia/Macau",
"Asia/Tel_Aviv": "Asia/Jerusalem",
"Asia/Thimbu": "Asia/Thimphu",
"Asia/Ujung_Pandang": "Asia/Makassar",
"Asia/Ulan_Bator": "Asia/Ulaanbaatar",
"Africa/Asmera": "Africa/Asmara",
"America/Coral_Harbour": "America/Atikokan",
"Australia/ACT": "Australia/Sydney",
"Australia/Canberra": "Australia/Sydney",
"Australia/NSW": "Australia/Sydney",
"Australia/North": "Australia/Darwin",
"Australia/Queensland": "Australia/Brisbane",
"Australia/South": "Australia/Adelaide",
"Australia/Tasmania": "Australia/Hobart",
"Australia/Victoria": "Australia/Melbourne",
"Australia/West": "Australia/Perth",
"US/Alaska": "America/Anchorage",
"US/Arizona": "America/Phoenix",
"US/Central": "America/Chicago",
"US/Eastern": "America/New_York",
"US/Hawaii": "Pacific/Honolulu",
"US/Mountain": "America/Denver",
"US/Pacific": "America/Los_Angeles",
"GB": "Europe/London",
"GB-Eire": "Europe/London",
"Eire": "Europe/Dublin",
"Egypt": "Africa/Cairo",
"Hongkong": "Asia/Hong_Kong",
"Iceland": "Atlantic/Reykjavik",
"Israel": "Asia/Jerusalem",
"Japan": "Asia/Tokyo",
"NZ": "Pacific/Auckland",
"Poland": "Europe/Warsaw",
"Portugal": "Europe/Lisbon",
"PRC": "Asia/Shanghai",
"ROC": "Asia/Taipei",
"ROK": "Asia/Seoul",
"Singapore": "Asia/Singapore",
"Turkey": "Europe/Istanbul",
}
// canonicalTZ returns the canonical IANA name for tz, resolving legacy
// aliases; unknown values pass through unchanged.
func canonicalTZ(tz string) string {
if c, ok := legacyTZ[tz]; ok {
return c
}
return tz
}
+2 -2
View File
@@ -9,8 +9,8 @@
-- chant_reminder, …); the Settings UI groups kinds into coarser categories
-- but storage stays kind-level so backfilled opt-outs map exactly.
CREATE TABLE IF NOT EXISTS notification_prefs (
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
kind TEXT NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
kind TEXT NOT NULL,
channel TEXT NOT NULL CHECK (channel IN ('in_app', 'email', 'push')),
enabled BOOLEAN NOT NULL DEFAULT FALSE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
@@ -0,0 +1,3 @@
-- No-op: the canonical names are valid everywhere the aliases were, so
-- there's nothing to restore (and the original alias per row isn't known).
SELECT 1;
@@ -0,0 +1,74 @@
-- Normalize legacy IANA timezone aliases stored in users.timezone.
--
-- Browsers (ICU/CLDR) report legacy ids — Safari sends "Asia/Calcutta", not
-- "Asia/Kolkata" — and Go's validation accepts them, so they were stored
-- verbatim. A Postgres built against tzdata without the "backward" links
-- rejects those names in AT TIME ZONE (SQLSTATE 22023), 500-ing the sādhana
-- dashboard. The Go side now canonicalizes on read and write (see
-- internal/service/timezone.go); this backfills the rows written before.
UPDATE users SET timezone = m.canonical
FROM (VALUES
('Asia/Calcutta', 'Asia/Kolkata'),
('Asia/Katmandu', 'Asia/Kathmandu'),
('Asia/Rangoon', 'Asia/Yangon'),
('Asia/Saigon', 'Asia/Ho_Chi_Minh'),
('America/Buenos_Aires', 'America/Argentina/Buenos_Aires'),
('America/Catamarca', 'America/Argentina/Catamarca'),
('America/Cordoba', 'America/Argentina/Cordoba'),
('America/Jujuy', 'America/Argentina/Jujuy'),
('America/Mendoza', 'America/Argentina/Mendoza'),
('America/Indianapolis', 'America/Indiana/Indianapolis'),
('America/Louisville', 'America/Kentucky/Louisville'),
('America/Godthab', 'America/Nuuk'),
('Atlantic/Faeroe', 'Atlantic/Faroe'),
('Pacific/Ponape', 'Pacific/Pohnpei'),
('Pacific/Truk', 'Pacific/Chuuk'),
('Europe/Kiev', 'Europe/Kyiv'),
('Asia/Ashkhabad', 'Asia/Ashgabat'),
('Asia/Chongqing', 'Asia/Shanghai'),
('Asia/Chungking', 'Asia/Shanghai'),
('Asia/Dacca', 'Asia/Dhaka'),
('Asia/Harbin', 'Asia/Shanghai'),
('Asia/Istanbul', 'Europe/Istanbul'),
('Asia/Kashgar', 'Asia/Urumqi'),
('Asia/Macao', 'Asia/Macau'),
('Asia/Tel_Aviv', 'Asia/Jerusalem'),
('Asia/Thimbu', 'Asia/Thimphu'),
('Asia/Ujung_Pandang', 'Asia/Makassar'),
('Asia/Ulan_Bator', 'Asia/Ulaanbaatar'),
('Africa/Asmera', 'Africa/Asmara'),
('America/Coral_Harbour','America/Atikokan'),
('Australia/ACT', 'Australia/Sydney'),
('Australia/Canberra', 'Australia/Sydney'),
('Australia/NSW', 'Australia/Sydney'),
('Australia/North', 'Australia/Darwin'),
('Australia/Queensland', 'Australia/Brisbane'),
('Australia/South', 'Australia/Adelaide'),
('Australia/Tasmania', 'Australia/Hobart'),
('Australia/Victoria', 'Australia/Melbourne'),
('Australia/West', 'Australia/Perth'),
('US/Alaska', 'America/Anchorage'),
('US/Arizona', 'America/Phoenix'),
('US/Central', 'America/Chicago'),
('US/Eastern', 'America/New_York'),
('US/Hawaii', 'Pacific/Honolulu'),
('US/Mountain', 'America/Denver'),
('US/Pacific', 'America/Los_Angeles'),
('GB', 'Europe/London'),
('GB-Eire', 'Europe/London'),
('Eire', 'Europe/Dublin'),
('Egypt', 'Africa/Cairo'),
('Hongkong', 'Asia/Hong_Kong'),
('Iceland', 'Atlantic/Reykjavik'),
('Israel', 'Asia/Jerusalem'),
('Japan', 'Asia/Tokyo'),
('NZ', 'Pacific/Auckland'),
('Poland', 'Europe/Warsaw'),
('Portugal', 'Europe/Lisbon'),
('PRC', 'Asia/Shanghai'),
('ROC', 'Asia/Taipei'),
('ROK', 'Asia/Seoul'),
('Singapore', 'Asia/Singapore'),
('Turkey', 'Europe/Istanbul')
) AS m(alias, canonical)
WHERE users.timezone = m.alias;