migrations.go 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package database // import "miniflux.app/v2/internal/database"
  4. import (
  5. "database/sql"
  6. "errors"
  7. "miniflux.app/v2/internal/crypto"
  8. )
  9. var schemaVersion = len(migrations)
  10. // Order is important. Add new migrations at the end of the list.
  11. var migrations = [...]func(tx *sql.Tx) error{
  12. func(tx *sql.Tx) (err error) {
  13. sql := `
  14. CREATE TABLE schema_version (
  15. version text not null
  16. );
  17. CREATE TABLE users (
  18. id SERIAL,
  19. username text not null unique,
  20. password text,
  21. is_admin bool default 'f',
  22. language text default 'en_US',
  23. timezone text default 'UTC',
  24. theme text default 'default',
  25. last_login_at timestamp with time zone,
  26. primary key (id)
  27. );
  28. CREATE TABLE sessions (
  29. id SERIAL,
  30. user_id int not null,
  31. token text not null unique,
  32. created_at timestamp with time zone default now(),
  33. user_agent text,
  34. ip text,
  35. primary key (id),
  36. unique (user_id, token),
  37. foreign key (user_id) references users(id) on delete cascade
  38. );
  39. CREATE TABLE categories (
  40. id SERIAL,
  41. user_id int not null,
  42. title text not null,
  43. primary key (id),
  44. unique (user_id, title),
  45. foreign key (user_id) references users(id) on delete cascade
  46. );
  47. CREATE TABLE feeds (
  48. id BIGSERIAL,
  49. user_id int not null,
  50. category_id int not null,
  51. title text not null,
  52. feed_url text not null,
  53. site_url text not null,
  54. checked_at timestamp with time zone default now(),
  55. etag_header text default '',
  56. last_modified_header text default '',
  57. parsing_error_msg text default '',
  58. parsing_error_count int default 0,
  59. primary key (id),
  60. unique (user_id, feed_url),
  61. foreign key (user_id) references users(id) on delete cascade,
  62. foreign key (category_id) references categories(id) on delete cascade
  63. );
  64. CREATE TYPE entry_status as enum('unread', 'read', 'removed');
  65. CREATE TABLE entries (
  66. id BIGSERIAL,
  67. user_id int not null,
  68. feed_id bigint not null,
  69. hash text not null,
  70. published_at timestamp with time zone not null,
  71. title text not null,
  72. url text not null,
  73. author text,
  74. content text,
  75. status entry_status default 'unread',
  76. primary key (id),
  77. unique (feed_id, hash),
  78. foreign key (user_id) references users(id) on delete cascade,
  79. foreign key (feed_id) references feeds(id) on delete cascade
  80. );
  81. CREATE INDEX entries_feed_idx on entries using btree(feed_id);
  82. CREATE TABLE enclosures (
  83. id BIGSERIAL,
  84. user_id int not null,
  85. entry_id bigint not null,
  86. url text not null,
  87. size int default 0,
  88. mime_type text default '',
  89. primary key (id),
  90. foreign key (user_id) references users(id) on delete cascade,
  91. foreign key (entry_id) references entries(id) on delete cascade
  92. );
  93. CREATE TABLE icons (
  94. id BIGSERIAL,
  95. hash text not null unique,
  96. mime_type text not null,
  97. content bytea not null,
  98. primary key (id)
  99. );
  100. CREATE TABLE feed_icons (
  101. feed_id bigint not null,
  102. icon_id bigint not null,
  103. primary key(feed_id, icon_id),
  104. foreign key (feed_id) references feeds(id) on delete cascade,
  105. foreign key (icon_id) references icons(id) on delete cascade
  106. );
  107. `
  108. _, err = tx.Exec(sql)
  109. return err
  110. },
  111. func(tx *sql.Tx) (err error) {
  112. // This used to create a HSTORE `extra` column in the table `users`,
  113. // which hasn't been used since Miniflux 2.0.27.
  114. return nil
  115. },
  116. func(tx *sql.Tx) (err error) {
  117. sql := `
  118. CREATE TABLE tokens (
  119. id text not null,
  120. value text not null,
  121. created_at timestamp with time zone not null default now(),
  122. primary key(id, value)
  123. );
  124. `
  125. _, err = tx.Exec(sql)
  126. return err
  127. },
  128. func(tx *sql.Tx) (err error) {
  129. sql := `
  130. CREATE TYPE entry_sorting_direction AS enum('asc', 'desc');
  131. ALTER TABLE users ADD COLUMN entry_direction entry_sorting_direction default 'asc';
  132. `
  133. _, err = tx.Exec(sql)
  134. return err
  135. },
  136. func(tx *sql.Tx) (err error) {
  137. sql := `
  138. CREATE TABLE integrations (
  139. user_id int not null,
  140. pinboard_enabled bool default 'f',
  141. pinboard_token text default '',
  142. pinboard_tags text default 'miniflux',
  143. pinboard_mark_as_unread bool default 'f',
  144. instapaper_enabled bool default 'f',
  145. instapaper_username text default '',
  146. instapaper_password text default '',
  147. fever_enabled bool default 'f',
  148. fever_username text default '',
  149. fever_password text default '',
  150. fever_token text default '',
  151. primary key(user_id)
  152. );
  153. `
  154. _, err = tx.Exec(sql)
  155. return err
  156. },
  157. func(tx *sql.Tx) (err error) {
  158. sql := `ALTER TABLE feeds ADD COLUMN scraper_rules text default ''`
  159. _, err = tx.Exec(sql)
  160. return err
  161. },
  162. func(tx *sql.Tx) (err error) {
  163. sql := `ALTER TABLE feeds ADD COLUMN rewrite_rules text default ''`
  164. _, err = tx.Exec(sql)
  165. return err
  166. },
  167. func(tx *sql.Tx) (err error) {
  168. sql := `ALTER TABLE feeds ADD COLUMN crawler boolean default 'f'`
  169. _, err = tx.Exec(sql)
  170. return err
  171. },
  172. func(tx *sql.Tx) (err error) {
  173. sql := `ALTER TABLE sessions rename to user_sessions`
  174. _, err = tx.Exec(sql)
  175. return err
  176. },
  177. func(tx *sql.Tx) (err error) {
  178. sql := `
  179. DROP TABLE tokens;
  180. CREATE TABLE sessions (
  181. id text not null,
  182. data jsonb not null,
  183. created_at timestamp with time zone not null default now(),
  184. primary key(id)
  185. );
  186. `
  187. _, err = tx.Exec(sql)
  188. return err
  189. },
  190. func(tx *sql.Tx) (err error) {
  191. sql := `
  192. ALTER TABLE integrations
  193. ADD COLUMN wallabag_enabled bool default 'f',
  194. ADD COLUMN wallabag_url text default '',
  195. ADD COLUMN wallabag_client_id text default '',
  196. ADD COLUMN wallabag_client_secret text default '',
  197. ADD COLUMN wallabag_username text default '',
  198. ADD COLUMN wallabag_password text default '';
  199. `
  200. _, err = tx.Exec(sql)
  201. return err
  202. },
  203. func(tx *sql.Tx) (err error) {
  204. sql := `ALTER TABLE entries ADD COLUMN starred bool default 'f'`
  205. _, err = tx.Exec(sql)
  206. return err
  207. },
  208. func(tx *sql.Tx) (err error) {
  209. sql := `
  210. CREATE INDEX entries_user_status_idx ON entries(user_id, status);
  211. CREATE INDEX feeds_user_category_idx ON feeds(user_id, category_id);
  212. `
  213. _, err = tx.Exec(sql)
  214. return err
  215. },
  216. func(tx *sql.Tx) (err error) {
  217. sql := `
  218. ALTER TABLE integrations
  219. ADD COLUMN nunux_keeper_enabled bool default 'f',
  220. ADD COLUMN nunux_keeper_url text default '',
  221. ADD COLUMN nunux_keeper_api_key text default '';
  222. `
  223. _, err = tx.Exec(sql)
  224. return err
  225. },
  226. func(tx *sql.Tx) (err error) {
  227. sql := `ALTER TABLE enclosures ALTER COLUMN size SET DATA TYPE bigint`
  228. _, err = tx.Exec(sql)
  229. return err
  230. },
  231. func(tx *sql.Tx) (err error) {
  232. sql := `ALTER TABLE entries ADD COLUMN comments_url text default ''`
  233. _, err = tx.Exec(sql)
  234. return err
  235. },
  236. func(tx *sql.Tx) (err error) {
  237. sql := `
  238. ALTER TABLE integrations
  239. ADD COLUMN pocket_enabled bool default 'f',
  240. ADD COLUMN pocket_access_token text default '',
  241. ADD COLUMN pocket_consumer_key text default '';
  242. `
  243. _, err = tx.Exec(sql)
  244. return err
  245. },
  246. func(tx *sql.Tx) (err error) {
  247. sql := `
  248. ALTER TABLE user_sessions ALTER COLUMN ip SET DATA TYPE inet using ip::inet;
  249. `
  250. _, err = tx.Exec(sql)
  251. return err
  252. },
  253. func(tx *sql.Tx) (err error) {
  254. sql := `
  255. ALTER TABLE feeds
  256. ADD COLUMN username text default '',
  257. ADD COLUMN password text default '';
  258. `
  259. _, err = tx.Exec(sql)
  260. return err
  261. },
  262. func(tx *sql.Tx) (err error) {
  263. sql := `
  264. ALTER TABLE entries ADD COLUMN document_vectors tsvector;
  265. UPDATE entries SET document_vectors = to_tsvector(substring(title || ' ' || coalesce(content, '') for 1000000));
  266. CREATE INDEX document_vectors_idx ON entries USING gin(document_vectors);
  267. `
  268. _, err = tx.Exec(sql)
  269. return err
  270. },
  271. func(tx *sql.Tx) (err error) {
  272. sql := `ALTER TABLE feeds ADD COLUMN user_agent text default ''`
  273. _, err = tx.Exec(sql)
  274. return err
  275. },
  276. func(tx *sql.Tx) (err error) {
  277. sql := `
  278. UPDATE
  279. entries
  280. SET
  281. document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B')
  282. `
  283. _, err = tx.Exec(sql)
  284. return err
  285. },
  286. func(tx *sql.Tx) (err error) {
  287. sql := `ALTER TABLE users ADD COLUMN keyboard_shortcuts boolean default 't'`
  288. _, err = tx.Exec(sql)
  289. return err
  290. },
  291. func(tx *sql.Tx) (err error) {
  292. sql := `ALTER TABLE feeds ADD COLUMN disabled boolean default 'f';`
  293. _, err = tx.Exec(sql)
  294. return err
  295. },
  296. func(tx *sql.Tx) (err error) {
  297. sql := `
  298. ALTER TABLE users ALTER COLUMN theme SET DEFAULT 'light_serif';
  299. UPDATE users SET theme='light_serif' WHERE theme='default';
  300. UPDATE users SET theme='light_sans_serif' WHERE theme='sansserif';
  301. UPDATE users SET theme='dark_serif' WHERE theme='black';
  302. `
  303. _, err = tx.Exec(sql)
  304. return err
  305. },
  306. func(tx *sql.Tx) (err error) {
  307. sql := `
  308. ALTER TABLE entries ADD COLUMN changed_at timestamp with time zone;
  309. UPDATE entries SET changed_at = published_at;
  310. ALTER TABLE entries ALTER COLUMN changed_at SET not null;
  311. `
  312. _, err = tx.Exec(sql)
  313. return err
  314. },
  315. func(tx *sql.Tx) (err error) {
  316. sql := `
  317. CREATE TABLE api_keys (
  318. id SERIAL,
  319. user_id int not null references users(id) on delete cascade,
  320. token text not null unique,
  321. description text not null,
  322. last_used_at timestamp with time zone,
  323. created_at timestamp with time zone default now(),
  324. primary key(id),
  325. unique (user_id, description)
  326. );
  327. `
  328. _, err = tx.Exec(sql)
  329. return err
  330. },
  331. func(tx *sql.Tx) (err error) {
  332. sql := `
  333. ALTER TABLE entries ADD COLUMN share_code text not null default '';
  334. CREATE UNIQUE INDEX entries_share_code_idx ON entries USING btree(share_code) WHERE share_code <> '';
  335. `
  336. _, err = tx.Exec(sql)
  337. return err
  338. },
  339. func(tx *sql.Tx) (err error) {
  340. sql := `CREATE INDEX enclosures_user_entry_url_idx ON enclosures(user_id, entry_id, md5(url))`
  341. _, err = tx.Exec(sql)
  342. return err
  343. },
  344. func(tx *sql.Tx) (err error) {
  345. sql := `
  346. ALTER TABLE feeds ADD COLUMN next_check_at timestamp with time zone default now();
  347. CREATE INDEX entries_user_feed_idx ON entries (user_id, feed_id);
  348. `
  349. _, err = tx.Exec(sql)
  350. return err
  351. },
  352. func(tx *sql.Tx) (err error) {
  353. sql := `ALTER TABLE feeds ADD COLUMN ignore_http_cache bool default false`
  354. _, err = tx.Exec(sql)
  355. return err
  356. },
  357. func(tx *sql.Tx) (err error) {
  358. sql := `ALTER TABLE users ADD COLUMN entries_per_page int default 100`
  359. _, err = tx.Exec(sql)
  360. return err
  361. },
  362. func(tx *sql.Tx) (err error) {
  363. sql := `ALTER TABLE users ADD COLUMN show_reading_time boolean default 't'`
  364. _, err = tx.Exec(sql)
  365. return err
  366. },
  367. func(tx *sql.Tx) (err error) {
  368. sql := `CREATE INDEX entries_id_user_status_idx ON entries USING btree (id, user_id, status)`
  369. _, err = tx.Exec(sql)
  370. return err
  371. },
  372. func(tx *sql.Tx) (err error) {
  373. sql := `ALTER TABLE feeds ADD COLUMN fetch_via_proxy bool default false`
  374. _, err = tx.Exec(sql)
  375. return err
  376. },
  377. func(tx *sql.Tx) (err error) {
  378. sql := `CREATE INDEX entries_feed_id_status_hash_idx ON entries USING btree (feed_id, status, hash)`
  379. _, err = tx.Exec(sql)
  380. return err
  381. },
  382. func(tx *sql.Tx) (err error) {
  383. sql := `CREATE INDEX entries_user_id_status_starred_idx ON entries (user_id, status, starred)`
  384. _, err = tx.Exec(sql)
  385. return err
  386. },
  387. func(tx *sql.Tx) (err error) {
  388. sql := `ALTER TABLE users ADD COLUMN entry_swipe boolean default 't'`
  389. _, err = tx.Exec(sql)
  390. return err
  391. },
  392. func(tx *sql.Tx) (err error) {
  393. sql := `ALTER TABLE integrations DROP COLUMN fever_password`
  394. _, err = tx.Exec(sql)
  395. return err
  396. },
  397. func(tx *sql.Tx) (err error) {
  398. sql := `
  399. ALTER TABLE feeds
  400. ADD COLUMN blocklist_rules text not null default '',
  401. ADD COLUMN keeplist_rules text not null default ''
  402. `
  403. _, err = tx.Exec(sql)
  404. return err
  405. },
  406. func(tx *sql.Tx) (err error) {
  407. sql := `ALTER TABLE entries ADD COLUMN reading_time int not null default 0`
  408. _, err = tx.Exec(sql)
  409. return err
  410. },
  411. func(tx *sql.Tx) (err error) {
  412. sql := `
  413. ALTER TABLE entries ADD COLUMN created_at timestamp with time zone not null default now();
  414. UPDATE entries SET created_at = published_at;
  415. `
  416. _, err = tx.Exec(sql)
  417. return err
  418. },
  419. func(tx *sql.Tx) (err error) {
  420. hasExtra := false
  421. if err := tx.QueryRow(`
  422. SELECT true
  423. FROM information_schema.columns
  424. WHERE
  425. table_name='users' AND
  426. column_name='extra';
  427. `).Scan(&hasExtra); err != nil && err != sql.ErrNoRows {
  428. return err
  429. }
  430. _, err = tx.Exec(`
  431. ALTER TABLE users
  432. ADD column stylesheet text not null default '',
  433. ADD column google_id text not null default '',
  434. ADD column openid_connect_id text not null default ''
  435. `)
  436. if err != nil {
  437. return err
  438. }
  439. if !hasExtra {
  440. // No need to migrate things from the `extra` column if it's not present
  441. return nil
  442. }
  443. _, err = tx.Exec(`
  444. DECLARE my_cursor CURSOR FOR
  445. SELECT
  446. id,
  447. COALESCE(extra->'custom_css', '') as custom_css,
  448. COALESCE(extra->'google_id', '') as google_id,
  449. COALESCE(extra->'oidc_id', '') as oidc_id
  450. FROM users
  451. FOR UPDATE
  452. `)
  453. if err != nil {
  454. return err
  455. }
  456. defer tx.Exec("CLOSE my_cursor")
  457. for {
  458. var (
  459. userID int64
  460. customStylesheet string
  461. googleID string
  462. oidcID string
  463. )
  464. if err := tx.QueryRow(`FETCH NEXT FROM my_cursor`).Scan(&userID, &customStylesheet, &googleID, &oidcID); err != nil {
  465. if errors.Is(err, sql.ErrNoRows) {
  466. break
  467. }
  468. return err
  469. }
  470. _, err := tx.Exec(
  471. `UPDATE
  472. users
  473. SET
  474. stylesheet=$2,
  475. google_id=$3,
  476. openid_connect_id=$4
  477. WHERE
  478. id=$1
  479. `,
  480. userID, customStylesheet, googleID, oidcID)
  481. if err != nil {
  482. return err
  483. }
  484. }
  485. return err
  486. },
  487. func(tx *sql.Tx) (err error) {
  488. if _, err = tx.Exec(`ALTER TABLE users DROP COLUMN IF EXISTS extra;`); err != nil {
  489. return err
  490. }
  491. _, err = tx.Exec(`
  492. CREATE UNIQUE INDEX users_google_id_idx ON users(google_id) WHERE google_id <> '';
  493. CREATE UNIQUE INDEX users_openid_connect_id_idx ON users(openid_connect_id) WHERE openid_connect_id <> '';
  494. `)
  495. return err
  496. },
  497. func(tx *sql.Tx) (err error) {
  498. _, err = tx.Exec(`
  499. CREATE INDEX entries_feed_url_idx ON entries(feed_id, url) WHERE length(url) < 2000;
  500. CREATE INDEX entries_user_status_feed_idx ON entries(user_id, status, feed_id);
  501. CREATE INDEX entries_user_status_changed_idx ON entries(user_id, status, changed_at);
  502. `)
  503. return err
  504. },
  505. func(tx *sql.Tx) (err error) {
  506. _, err = tx.Exec(`
  507. CREATE TABLE acme_cache (
  508. key varchar(400) not null primary key,
  509. data bytea not null,
  510. updated_at timestamptz not null
  511. );
  512. `)
  513. return err
  514. },
  515. func(tx *sql.Tx) (err error) {
  516. _, err = tx.Exec(`
  517. ALTER TABLE feeds ADD COLUMN allow_self_signed_certificates boolean not null default false
  518. `)
  519. return err
  520. },
  521. func(tx *sql.Tx) (err error) {
  522. sql := `
  523. CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
  524. ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
  525. `
  526. _, err = tx.Exec(sql)
  527. return err
  528. },
  529. func(tx *sql.Tx) (err error) {
  530. sql := `ALTER TABLE feeds ADD COLUMN cookie text default ''`
  531. _, err = tx.Exec(sql)
  532. return err
  533. },
  534. func(tx *sql.Tx) (err error) {
  535. _, err = tx.Exec(`
  536. ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
  537. `)
  538. return err
  539. },
  540. func(tx *sql.Tx) (err error) {
  541. _, err = tx.Exec(`
  542. ALTER TABLE feeds ADD COLUMN hide_globally boolean not null default false
  543. `)
  544. return err
  545. },
  546. func(tx *sql.Tx) (err error) {
  547. sql := `
  548. ALTER TABLE integrations
  549. ADD COLUMN telegram_bot_enabled bool default 'f',
  550. ADD COLUMN telegram_bot_token text default '',
  551. ADD COLUMN telegram_bot_chat_id text default '';
  552. `
  553. _, err = tx.Exec(sql)
  554. return err
  555. },
  556. func(tx *sql.Tx) (err error) {
  557. sql := `
  558. CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
  559. ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
  560. `
  561. _, err = tx.Exec(sql)
  562. return err
  563. },
  564. func(tx *sql.Tx) (err error) {
  565. sql := `
  566. ALTER TABLE integrations
  567. ADD COLUMN googlereader_enabled bool default 'f',
  568. ADD COLUMN googlereader_username text default '',
  569. ADD COLUMN googlereader_password text default '';
  570. `
  571. _, err = tx.Exec(sql)
  572. return err
  573. },
  574. func(tx *sql.Tx) (err error) {
  575. sql := `
  576. ALTER TABLE integrations
  577. ADD COLUMN espial_enabled bool default 'f',
  578. ADD COLUMN espial_url text default '',
  579. ADD COLUMN espial_api_key text default '',
  580. ADD COLUMN espial_tags text default 'miniflux';
  581. `
  582. _, err = tx.Exec(sql)
  583. return err
  584. },
  585. func(tx *sql.Tx) (err error) {
  586. sql := `
  587. ALTER TABLE integrations
  588. ADD COLUMN linkding_enabled bool default 'f',
  589. ADD COLUMN linkding_url text default '',
  590. ADD COLUMN linkding_api_key text default '';
  591. `
  592. _, err = tx.Exec(sql)
  593. return err
  594. },
  595. func(tx *sql.Tx) (err error) {
  596. _, err = tx.Exec(`
  597. ALTER TABLE feeds ADD COLUMN url_rewrite_rules text not null default ''
  598. `)
  599. return err
  600. },
  601. func(tx *sql.Tx) (err error) {
  602. _, err = tx.Exec(`
  603. ALTER TABLE users
  604. ADD COLUMN default_reading_speed int default 265,
  605. ADD COLUMN cjk_reading_speed int default 500;
  606. `)
  607. return
  608. },
  609. func(tx *sql.Tx) (err error) {
  610. _, err = tx.Exec(`
  611. ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
  612. `)
  613. return
  614. },
  615. func(tx *sql.Tx) (err error) {
  616. _, err = tx.Exec(`
  617. ALTER TABLE integrations ADD COLUMN wallabag_only_url bool default 'f';
  618. `)
  619. return
  620. },
  621. func(tx *sql.Tx) (err error) {
  622. _, err = tx.Exec(`
  623. ALTER TABLE users ADD COLUMN categories_sorting_order text not null default 'unread_count';
  624. `)
  625. return
  626. },
  627. func(tx *sql.Tx) (err error) {
  628. sql := `
  629. ALTER TABLE integrations
  630. ADD COLUMN matrix_bot_enabled bool default 'f',
  631. ADD COLUMN matrix_bot_user text default '',
  632. ADD COLUMN matrix_bot_password text default '',
  633. ADD COLUMN matrix_bot_url text default '',
  634. ADD COLUMN matrix_bot_chat_id text default '';
  635. `
  636. _, err = tx.Exec(sql)
  637. return
  638. },
  639. func(tx *sql.Tx) (err error) {
  640. sql := `ALTER TABLE users ADD COLUMN double_tap boolean default 't'`
  641. _, err = tx.Exec(sql)
  642. return err
  643. },
  644. func(tx *sql.Tx) (err error) {
  645. _, err = tx.Exec(`
  646. ALTER TABLE entries ADD COLUMN tags text[] default '{}';
  647. `)
  648. return
  649. },
  650. func(tx *sql.Tx) (err error) {
  651. sql := `
  652. ALTER TABLE users RENAME double_tap TO gesture_nav;
  653. ALTER TABLE users
  654. ALTER COLUMN gesture_nav SET DATA TYPE text using case when gesture_nav = true then 'tap' when gesture_nav = false then 'none' end,
  655. ALTER COLUMN gesture_nav SET default 'tap';
  656. `
  657. _, err = tx.Exec(sql)
  658. return err
  659. },
  660. func(tx *sql.Tx) (err error) {
  661. sql := `
  662. ALTER TABLE integrations ADD COLUMN linkding_tags text default '';
  663. `
  664. _, err = tx.Exec(sql)
  665. return err
  666. },
  667. func(tx *sql.Tx) (err error) {
  668. sql := `
  669. ALTER TABLE feeds ADD COLUMN no_media_player boolean default 'f';
  670. ALTER TABLE enclosures ADD COLUMN media_progression int default 0;
  671. `
  672. _, err = tx.Exec(sql)
  673. return err
  674. },
  675. func(tx *sql.Tx) (err error) {
  676. sql := `
  677. ALTER TABLE integrations ADD COLUMN linkding_mark_as_unread bool default 'f';
  678. `
  679. _, err = tx.Exec(sql)
  680. return err
  681. },
  682. func(tx *sql.Tx) (err error) {
  683. // Delete duplicated rows
  684. sql := `
  685. DELETE FROM enclosures a USING enclosures b
  686. WHERE a.id < b.id
  687. AND a.user_id = b.user_id
  688. AND a.entry_id = b.entry_id
  689. AND a.url = b.url;
  690. `
  691. _, err = tx.Exec(sql)
  692. if err != nil {
  693. return err
  694. }
  695. // Remove previous index
  696. _, err = tx.Exec(`DROP INDEX enclosures_user_entry_url_idx`)
  697. if err != nil {
  698. return err
  699. }
  700. // Create unique index
  701. _, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
  702. if err != nil {
  703. return err
  704. }
  705. return nil
  706. },
  707. func(tx *sql.Tx) (err error) {
  708. sql := `ALTER TABLE users ADD COLUMN mark_read_on_view boolean default 't'`
  709. _, err = tx.Exec(sql)
  710. return err
  711. },
  712. func(tx *sql.Tx) (err error) {
  713. sql := `
  714. ALTER TABLE integrations
  715. ADD COLUMN notion_enabled bool default 'f',
  716. ADD COLUMN notion_token text default '',
  717. ADD COLUMN notion_page_id text default '';
  718. `
  719. _, err = tx.Exec(sql)
  720. return err
  721. },
  722. func(tx *sql.Tx) (err error) {
  723. sql := `
  724. ALTER TABLE integrations
  725. ADD COLUMN readwise_enabled bool default 'f',
  726. ADD COLUMN readwise_api_key text default '';
  727. `
  728. _, err = tx.Exec(sql)
  729. return err
  730. },
  731. func(tx *sql.Tx) (err error) {
  732. sql := `
  733. ALTER TABLE integrations
  734. ADD COLUMN apprise_enabled bool default 'f',
  735. ADD COLUMN apprise_url text default '',
  736. ADD COLUMN apprise_services_url text default '';
  737. `
  738. _, err = tx.Exec(sql)
  739. return err
  740. },
  741. func(tx *sql.Tx) (err error) {
  742. sql := `
  743. ALTER TABLE integrations
  744. ADD COLUMN shiori_enabled bool default 'f',
  745. ADD COLUMN shiori_url text default '',
  746. ADD COLUMN shiori_username text default '',
  747. ADD COLUMN shiori_password text default '';
  748. `
  749. _, err = tx.Exec(sql)
  750. return err
  751. },
  752. func(tx *sql.Tx) (err error) {
  753. sql := `
  754. ALTER TABLE integrations
  755. ADD COLUMN shaarli_enabled bool default 'f',
  756. ADD COLUMN shaarli_url text default '',
  757. ADD COLUMN shaarli_api_secret text default '';
  758. `
  759. _, err = tx.Exec(sql)
  760. return err
  761. },
  762. func(tx *sql.Tx) (err error) {
  763. _, err = tx.Exec(`
  764. ALTER TABLE feeds ADD COLUMN apprise_service_urls text default '';
  765. `)
  766. return err
  767. },
  768. func(tx *sql.Tx) (err error) {
  769. sql := `
  770. ALTER TABLE integrations
  771. ADD COLUMN webhook_enabled bool default 'f',
  772. ADD COLUMN webhook_url text default '',
  773. ADD COLUMN webhook_secret text default '';
  774. `
  775. _, err = tx.Exec(sql)
  776. return err
  777. },
  778. func(tx *sql.Tx) (err error) {
  779. sql := `
  780. ALTER TABLE integrations
  781. ADD COLUMN telegram_bot_topic_id int,
  782. ADD COLUMN telegram_bot_disable_web_page_preview bool default 'f',
  783. ADD COLUMN telegram_bot_disable_notification bool default 'f';
  784. `
  785. _, err = tx.Exec(sql)
  786. return err
  787. },
  788. func(tx *sql.Tx) (err error) {
  789. sql := `
  790. ALTER TABLE integrations ADD COLUMN telegram_bot_disable_buttons bool default 'f';
  791. `
  792. _, err = tx.Exec(sql)
  793. return err
  794. },
  795. func(tx *sql.Tx) (err error) {
  796. sql := `
  797. -- Speed up has_enclosure
  798. CREATE INDEX enclosures_entry_id_idx ON enclosures(entry_id);
  799. -- Speed up unread page
  800. CREATE INDEX entries_user_status_published_idx ON entries(user_id, status, published_at);
  801. CREATE INDEX entries_user_status_created_idx ON entries(user_id, status, created_at);
  802. CREATE INDEX feeds_feed_id_hide_globally_idx ON feeds(id, hide_globally);
  803. -- Speed up history page
  804. CREATE INDEX entries_user_status_changed_published_idx ON entries(user_id, status, changed_at, published_at);
  805. `
  806. _, err = tx.Exec(sql)
  807. return err
  808. },
  809. func(tx *sql.Tx) (err error) {
  810. sql := `
  811. ALTER TABLE integrations
  812. ADD COLUMN rssbridge_enabled bool default 'f',
  813. ADD COLUMN rssbridge_url text default '';
  814. `
  815. _, err = tx.Exec(sql)
  816. return
  817. },
  818. func(tx *sql.Tx) (err error) {
  819. _, err = tx.Exec(`
  820. CREATE TABLE webauthn_credentials (
  821. handle bytea primary key,
  822. cred_id bytea unique not null,
  823. user_id int references users(id) on delete cascade not null,
  824. public_key bytea not null,
  825. attestation_type varchar(255) not null,
  826. aaguid bytea,
  827. sign_count bigint,
  828. clone_warning bool,
  829. name text,
  830. added_on timestamp with time zone default now(),
  831. last_seen_on timestamp with time zone default now()
  832. );
  833. `)
  834. return
  835. },
  836. func(tx *sql.Tx) (err error) {
  837. sql := `
  838. ALTER TABLE integrations
  839. ADD COLUMN omnivore_enabled bool default 'f',
  840. ADD COLUMN omnivore_api_key text default '',
  841. ADD COLUMN omnivore_url text default '';
  842. `
  843. _, err = tx.Exec(sql)
  844. return
  845. },
  846. func(tx *sql.Tx) (err error) {
  847. sql := `
  848. ALTER TABLE integrations
  849. ADD COLUMN linkace_enabled bool default 'f',
  850. ADD COLUMN linkace_url text default '',
  851. ADD COLUMN linkace_api_key text default '',
  852. ADD COLUMN linkace_tags text default '',
  853. ADD COLUMN linkace_is_private bool default 't',
  854. ADD COLUMN linkace_check_disabled bool default 't';
  855. `
  856. _, err = tx.Exec(sql)
  857. return err
  858. },
  859. func(tx *sql.Tx) (err error) {
  860. sql := `
  861. ALTER TABLE integrations
  862. ADD COLUMN linkwarden_enabled bool default 'f',
  863. ADD COLUMN linkwarden_url text default '',
  864. ADD COLUMN linkwarden_api_key text default '';
  865. `
  866. _, err = tx.Exec(sql)
  867. return err
  868. },
  869. func(tx *sql.Tx) (err error) {
  870. sql := `
  871. ALTER TABLE integrations
  872. ADD COLUMN readeck_enabled bool default 'f',
  873. ADD COLUMN readeck_only_url bool default 'f',
  874. ADD COLUMN readeck_url text default '',
  875. ADD COLUMN readeck_api_key text default '',
  876. ADD COLUMN readeck_labels text default '';
  877. `
  878. _, err = tx.Exec(sql)
  879. return err
  880. },
  881. func(tx *sql.Tx) (err error) {
  882. sql := `ALTER TABLE feeds ADD COLUMN disable_http2 bool default 'f'`
  883. _, err = tx.Exec(sql)
  884. return err
  885. },
  886. func(tx *sql.Tx) (err error) {
  887. sql := `ALTER TABLE users ADD COLUMN media_playback_rate numeric default 1;`
  888. _, err = tx.Exec(sql)
  889. return err
  890. },
  891. func(tx *sql.Tx) (err error) {
  892. // the WHERE part speed-up the request a lot
  893. sql := `UPDATE entries SET tags = array_remove(tags, '') WHERE '' = ANY(tags);`
  894. _, err = tx.Exec(sql)
  895. return err
  896. },
  897. func(tx *sql.Tx) (err error) {
  898. // Entry URLs can exceeds btree maximum size
  899. // Checking entry existence is now using entries_feed_id_status_hash_idx index
  900. _, err = tx.Exec(`DROP INDEX entries_feed_url_idx`)
  901. return err
  902. },
  903. func(tx *sql.Tx) (err error) {
  904. sql := `
  905. ALTER TABLE integrations
  906. ADD COLUMN raindrop_enabled bool default 'f',
  907. ADD COLUMN raindrop_token text default '',
  908. ADD COLUMN raindrop_collection_id text default '',
  909. ADD COLUMN raindrop_tags text default '';
  910. `
  911. _, err = tx.Exec(sql)
  912. return err
  913. },
  914. func(tx *sql.Tx) (err error) {
  915. sql := `ALTER TABLE feeds ADD COLUMN description text default ''`
  916. _, err = tx.Exec(sql)
  917. return err
  918. },
  919. func(tx *sql.Tx) (err error) {
  920. sql := `
  921. ALTER TABLE users
  922. ADD COLUMN block_filter_entry_rules text not null default '',
  923. ADD COLUMN keep_filter_entry_rules text not null default ''
  924. `
  925. _, err = tx.Exec(sql)
  926. return err
  927. },
  928. func(tx *sql.Tx) (err error) {
  929. sql := `
  930. ALTER TABLE integrations
  931. ADD COLUMN betula_url text default '',
  932. ADD COLUMN betula_token text default '',
  933. ADD COLUMN betula_enabled bool default 'f';
  934. `
  935. _, err = tx.Exec(sql)
  936. return err
  937. },
  938. func(tx *sql.Tx) (err error) {
  939. sql := `
  940. ALTER TABLE integrations
  941. ADD COLUMN ntfy_enabled bool default 'f',
  942. ADD COLUMN ntfy_url text default '',
  943. ADD COLUMN ntfy_topic text default '',
  944. ADD COLUMN ntfy_api_token text default '',
  945. ADD COLUMN ntfy_username text default '',
  946. ADD COLUMN ntfy_password text default '',
  947. ADD COLUMN ntfy_icon_url text default '';
  948. ALTER TABLE feeds
  949. ADD COLUMN ntfy_enabled bool default 'f',
  950. ADD COLUMN ntfy_priority int default '3';
  951. `
  952. _, err = tx.Exec(sql)
  953. return err
  954. },
  955. func(tx *sql.Tx) (err error) {
  956. sql := `ALTER TABLE users ADD COLUMN mark_read_on_media_player_completion bool default 'f';`
  957. _, err = tx.Exec(sql)
  958. return err
  959. },
  960. func(tx *sql.Tx) (err error) {
  961. sql := `ALTER TABLE users ADD COLUMN custom_js text not null default '';`
  962. _, err = tx.Exec(sql)
  963. return err
  964. },
  965. func(tx *sql.Tx) (err error) {
  966. sql := `ALTER TABLE users ADD COLUMN external_font_hosts text not null default '';`
  967. _, err = tx.Exec(sql)
  968. return err
  969. },
  970. func(tx *sql.Tx) (err error) {
  971. sql := `
  972. ALTER TABLE integrations
  973. ADD COLUMN cubox_enabled bool default 'f',
  974. ADD COLUMN cubox_api_link text default '';
  975. `
  976. _, err = tx.Exec(sql)
  977. return err
  978. },
  979. func(tx *sql.Tx) (err error) {
  980. sql := `
  981. ALTER TABLE integrations
  982. ADD COLUMN discord_enabled bool default 'f',
  983. ADD COLUMN discord_webhook_link text default '';
  984. `
  985. _, err = tx.Exec(sql)
  986. return err
  987. },
  988. func(tx *sql.Tx) (err error) {
  989. sql := `ALTER TABLE integrations ADD COLUMN ntfy_internal_links bool default 'f';`
  990. _, err = tx.Exec(sql)
  991. return err
  992. },
  993. func(tx *sql.Tx) (err error) {
  994. sql := `
  995. ALTER TABLE integrations
  996. ADD COLUMN slack_enabled bool default 'f',
  997. ADD COLUMN slack_webhook_link text default '';
  998. `
  999. _, err = tx.Exec(sql)
  1000. return err
  1001. },
  1002. func(tx *sql.Tx) (err error) {
  1003. _, err = tx.Exec(`ALTER TABLE feeds ADD COLUMN webhook_url text default '';`)
  1004. return err
  1005. },
  1006. func(tx *sql.Tx) (err error) {
  1007. sql := `
  1008. ALTER TABLE integrations
  1009. ADD COLUMN pushover_enabled bool default 'f',
  1010. ADD COLUMN pushover_user text default '',
  1011. ADD COLUMN pushover_token text default '',
  1012. ADD COLUMN pushover_device text default '',
  1013. ADD COLUMN pushover_prefix text default '';
  1014. ALTER TABLE feeds
  1015. ADD COLUMN pushover_enabled bool default 'f',
  1016. ADD COLUMN pushover_priority int default '0';
  1017. `
  1018. _, err = tx.Exec(sql)
  1019. return err
  1020. },
  1021. func(tx *sql.Tx) (err error) {
  1022. sql := `
  1023. ALTER TABLE feeds ADD COLUMN ntfy_topic text default '';
  1024. `
  1025. _, err = tx.Exec(sql)
  1026. return err
  1027. },
  1028. func(tx *sql.Tx) (err error) {
  1029. sql := `
  1030. ALTER TABLE icons ADD COLUMN external_id text default '';
  1031. CREATE UNIQUE INDEX icons_external_id_idx ON icons USING btree(external_id) WHERE external_id <> '';
  1032. `
  1033. _, err = tx.Exec(sql)
  1034. return err
  1035. },
  1036. func(tx *sql.Tx) (err error) {
  1037. _, err = tx.Exec(`
  1038. DECLARE id_cursor CURSOR FOR
  1039. SELECT
  1040. id
  1041. FROM icons
  1042. WHERE external_id = ''
  1043. FOR UPDATE`)
  1044. if err != nil {
  1045. return err
  1046. }
  1047. defer tx.Exec("CLOSE id_cursor")
  1048. for {
  1049. var id int64
  1050. if err := tx.QueryRow(`FETCH NEXT FROM id_cursor`).Scan(&id); err != nil {
  1051. if errors.Is(err, sql.ErrNoRows) {
  1052. break
  1053. }
  1054. return err
  1055. }
  1056. _, err = tx.Exec(
  1057. `
  1058. UPDATE icons SET external_id = $1 WHERE id = $2
  1059. `,
  1060. crypto.GenerateRandomStringHex(20), id)
  1061. if err != nil {
  1062. return err
  1063. }
  1064. }
  1065. return nil
  1066. },
  1067. func(tx *sql.Tx) (err error) {
  1068. _, err = tx.Exec(`ALTER TABLE feeds ADD COLUMN proxy_url text default ''`)
  1069. return err
  1070. },
  1071. func(tx *sql.Tx) (err error) {
  1072. sql := `
  1073. ALTER TABLE integrations ADD COLUMN rssbridge_token text default '';
  1074. `
  1075. _, err = tx.Exec(sql)
  1076. return err
  1077. },
  1078. func(tx *sql.Tx) (err error) {
  1079. _, err = tx.Exec(`ALTER TABLE users ADD COLUMN always_open_external_links bool default 'f'`)
  1080. return err
  1081. },
  1082. func(tx *sql.Tx) (err error) {
  1083. sql := `
  1084. ALTER TABLE integrations
  1085. ADD COLUMN karakeep_enabled bool default 'f',
  1086. ADD COLUMN karakeep_api_key text default '',
  1087. ADD COLUMN karakeep_url text default '';
  1088. `
  1089. _, err = tx.Exec(sql)
  1090. return err
  1091. },
  1092. func(tx *sql.Tx) (err error) {
  1093. _, err = tx.Exec(`ALTER TABLE users ADD COLUMN open_external_links_in_new_tab bool default 't'`)
  1094. return err
  1095. },
  1096. func(tx *sql.Tx) (err error) {
  1097. sql := `
  1098. ALTER TABLE integrations
  1099. DROP COLUMN pocket_enabled,
  1100. DROP COLUMN pocket_access_token,
  1101. DROP COLUMN pocket_consumer_key;
  1102. `
  1103. _, err = tx.Exec(sql)
  1104. return err
  1105. },
  1106. func(tx *sql.Tx) (err error) {
  1107. sql := `
  1108. ALTER TABLE feeds
  1109. ADD COLUMN block_filter_entry_rules text not null default '',
  1110. ADD COLUMN keep_filter_entry_rules text not null default ''
  1111. `
  1112. _, err = tx.Exec(sql)
  1113. return err
  1114. },
  1115. func(tx *sql.Tx) (err error) {
  1116. sql := `
  1117. CREATE TYPE linktaco_link_visibility AS ENUM (
  1118. 'PUBLIC',
  1119. 'PRIVATE'
  1120. );
  1121. ALTER TABLE integrations
  1122. ADD COLUMN linktaco_enabled bool default 'f',
  1123. ADD COLUMN linktaco_api_token text default '',
  1124. ADD COLUMN linktaco_org_slug text default '',
  1125. ADD COLUMN linktaco_tags text default '',
  1126. ADD COLUMN linktaco_visibility linktaco_link_visibility default 'PUBLIC';
  1127. `
  1128. _, err = tx.Exec(sql)
  1129. return err
  1130. },
  1131. func(tx *sql.Tx) (err error) {
  1132. sql := `
  1133. ALTER TABLE integrations ADD COLUMN wallabag_tags text default '';
  1134. `
  1135. _, err = tx.Exec(sql)
  1136. return err
  1137. },
  1138. // This migration replaces deprecated timezones by their equivalent on Debian Trixie.
  1139. func(tx *sql.Tx) (err error) {
  1140. var deprecatedTimeZoneMap = map[string]string{
  1141. // Africa
  1142. "Africa/Asmera": "Africa/Asmara",
  1143. // America - Argentina
  1144. "America/Argentina/ComodRivadavia": "America/Argentina/Catamarca",
  1145. "America/Buenos_Aires": "America/Argentina/Buenos_Aires",
  1146. "America/Catamarca": "America/Argentina/Catamarca",
  1147. "America/Cordoba": "America/Argentina/Cordoba",
  1148. "America/Jujuy": "America/Argentina/Jujuy",
  1149. "America/Mendoza": "America/Argentina/Mendoza",
  1150. "America/Rosario": "America/Argentina/Cordoba",
  1151. // America - US
  1152. "America/Fort_Wayne": "America/Indiana/Indianapolis",
  1153. "America/Indianapolis": "America/Indiana/Indianapolis",
  1154. "America/Knox_IN": "America/Indiana/Knox",
  1155. "America/Louisville": "America/Kentucky/Louisville",
  1156. // America - Greenland
  1157. "America/Godthab": "America/Nuuk",
  1158. // Antarctica
  1159. "Antarctica/South_Pole": "Pacific/Auckland",
  1160. // Asia
  1161. "Asia/Ashkhabad": "Asia/Ashgabat",
  1162. "Asia/Calcutta": "Asia/Kolkata",
  1163. "Asia/Choibalsan": "Asia/Ulaanbaatar",
  1164. "Asia/Chungking": "Asia/Chongqing",
  1165. "Asia/Dacca": "Asia/Dhaka",
  1166. "Asia/Katmandu": "Asia/Kathmandu",
  1167. "Asia/Macao": "Asia/Macau",
  1168. "Asia/Rangoon": "Asia/Yangon",
  1169. "Asia/Saigon": "Asia/Ho_Chi_Minh",
  1170. "Asia/Thimbu": "Asia/Thimphu",
  1171. "Asia/Ujung_Pandang": "Asia/Makassar",
  1172. "Asia/Ulan_Bator": "Asia/Ulaanbaatar",
  1173. // Atlantic
  1174. "Atlantic/Faeroe": "Atlantic/Faroe",
  1175. // Australia
  1176. "Australia/ACT": "Australia/Sydney",
  1177. "Australia/LHI": "Australia/Lord_Howe",
  1178. "Australia/North": "Australia/Darwin",
  1179. "Australia/NSW": "Australia/Sydney",
  1180. "Australia/Queensland": "Australia/Brisbane",
  1181. "Australia/South": "Australia/Adelaide",
  1182. "Australia/Tasmania": "Australia/Hobart",
  1183. "Australia/Victoria": "Australia/Melbourne",
  1184. "Australia/West": "Australia/Perth",
  1185. // Brazil
  1186. "Brazil/Acre": "America/Rio_Branco",
  1187. "Brazil/DeNoronha": "America/Noronha",
  1188. "Brazil/East": "America/Sao_Paulo",
  1189. "Brazil/West": "America/Manaus",
  1190. // Canada
  1191. "Canada/Atlantic": "America/Halifax",
  1192. "Canada/Central": "America/Winnipeg",
  1193. "Canada/Eastern": "America/Toronto",
  1194. "Canada/Mountain": "America/Edmonton",
  1195. "Canada/Newfoundland": "America/St_Johns",
  1196. "Canada/Pacific": "America/Vancouver",
  1197. "Canada/Saskatchewan": "America/Regina",
  1198. "Canada/Yukon": "America/Whitehorse",
  1199. // Europe
  1200. "CET": "Europe/Paris",
  1201. "EET": "Europe/Sofia",
  1202. "Europe/Kiev": "Europe/Kyiv",
  1203. "Europe/Uzhgorod": "Europe/Kyiv",
  1204. "Europe/Zaporozhye": "Europe/Kyiv",
  1205. "MET": "Europe/Paris",
  1206. "WET": "Europe/Lisbon",
  1207. // Chile
  1208. "Chile/Continental": "America/Santiago",
  1209. "Chile/EasterIsland": "Pacific/Easter",
  1210. // Fixed offset and generic zones
  1211. "CST6CDT": "America/Chicago",
  1212. "EST": "America/New_York",
  1213. "EST5EDT": "America/New_York",
  1214. "HST": "Pacific/Honolulu",
  1215. "MST": "America/Denver",
  1216. "MST7MDT": "America/Denver",
  1217. "PST8PDT": "America/Los_Angeles",
  1218. // Countries/Regions
  1219. "Cuba": "America/Havana",
  1220. "Egypt": "Africa/Cairo",
  1221. "Eire": "Europe/Dublin",
  1222. "GB": "Europe/London",
  1223. "GB-Eire": "Europe/London",
  1224. "Hongkong": "Asia/Hong_Kong",
  1225. "Iceland": "Atlantic/Reykjavik",
  1226. "Iran": "Asia/Tehran",
  1227. "Israel": "Asia/Jerusalem",
  1228. "Jamaica": "America/Jamaica",
  1229. "Japan": "Asia/Tokyo",
  1230. "Libya": "Africa/Tripoli",
  1231. "Poland": "Europe/Warsaw",
  1232. "Portugal": "Europe/Lisbon",
  1233. "PRC": "Asia/Shanghai",
  1234. "ROC": "Asia/Taipei",
  1235. "ROK": "Asia/Seoul",
  1236. "Singapore": "Asia/Singapore",
  1237. "Turkey": "Europe/Istanbul",
  1238. // GMT variations
  1239. "GMT+0": "GMT",
  1240. "GMT-0": "GMT",
  1241. "GMT0": "GMT",
  1242. "Greenwich": "GMT",
  1243. "UCT": "UTC",
  1244. "Universal": "UTC",
  1245. "Zulu": "UTC",
  1246. // Mexico
  1247. "Mexico/BajaNorte": "America/Tijuana",
  1248. "Mexico/BajaSur": "America/Mazatlan",
  1249. "Mexico/General": "America/Mexico_City",
  1250. // US zones
  1251. "Navajo": "America/Denver",
  1252. "US/Alaska": "America/Anchorage",
  1253. "US/Aleutian": "America/Adak",
  1254. "US/Arizona": "America/Phoenix",
  1255. "US/Central": "America/Chicago",
  1256. "US/Eastern": "America/New_York",
  1257. "US/East-Indiana": "America/Indiana/Indianapolis",
  1258. "US/Hawaii": "Pacific/Honolulu",
  1259. "US/Indiana-Starke": "America/Indiana/Knox",
  1260. "US/Michigan": "America/Detroit",
  1261. "US/Mountain": "America/Denver",
  1262. "US/Pacific": "America/Los_Angeles",
  1263. "US/Samoa": "Pacific/Pago_Pago",
  1264. // Pacific
  1265. "Kwajalein": "Pacific/Kwajalein",
  1266. "NZ": "Pacific/Auckland",
  1267. "NZ-CHAT": "Pacific/Chatham",
  1268. "Pacific/Enderbury": "Pacific/Kanton",
  1269. "Pacific/Ponape": "Pacific/Pohnpei",
  1270. "Pacific/Truk": "Pacific/Chuuk",
  1271. // Special cases
  1272. "Factory": "UTC", // Factory is used for unconfigured systems
  1273. "W-SU": "Europe/Moscow",
  1274. }
  1275. // Loop through each user and correct the timezone
  1276. rows, err := tx.Query(`SELECT id, timezone FROM users`)
  1277. if err != nil {
  1278. return err
  1279. }
  1280. userTimezoneMap := make(map[int64]string)
  1281. for rows.Next() {
  1282. var userID int64
  1283. var userTimezone string
  1284. if err := rows.Scan(&userID, &userTimezone); err != nil {
  1285. return err
  1286. }
  1287. userTimezoneMap[userID] = userTimezone
  1288. }
  1289. rows.Close()
  1290. for userID, userTimezone := range userTimezoneMap {
  1291. if newTimezone, found := deprecatedTimeZoneMap[userTimezone]; found {
  1292. if _, err := tx.Exec(`UPDATE users SET timezone = $1 WHERE id = $2`, newTimezone, userID); err != nil {
  1293. return err
  1294. }
  1295. }
  1296. }
  1297. return nil
  1298. },
  1299. func(tx *sql.Tx) (err error) {
  1300. sql := `
  1301. ALTER TABLE integrations ADD COLUMN archiveorg_enabled bool default 'f'
  1302. `
  1303. _, err = tx.Exec(sql)
  1304. return err
  1305. },
  1306. func(tx *sql.Tx) (err error) {
  1307. sql := `DROP EXTENSION IF EXISTS hstore;`
  1308. _, err = tx.Exec(sql)
  1309. return err
  1310. },
  1311. func(tx *sql.Tx) (err error) {
  1312. sql := `
  1313. ALTER TABLE integrations ADD COLUMN karakeep_tags text default '';
  1314. `
  1315. _, err = tx.Exec(sql)
  1316. return err
  1317. },
  1318. func(tx *sql.Tx) (err error) {
  1319. sql := `
  1320. ALTER TABLE integrations ADD COLUMN linkwarden_collection_id int;
  1321. `
  1322. _, err = tx.Exec(sql)
  1323. return err
  1324. },
  1325. func(tx *sql.Tx) (err error) {
  1326. sql := `
  1327. ALTER TABLE integrations ADD COLUMN readeck_push_enabled bool default 'f';
  1328. `
  1329. _, err = tx.Exec(sql)
  1330. return err
  1331. },
  1332. func(tx *sql.Tx) (err error) {
  1333. // There is no need to keep an index on the content of deleted entries.
  1334. _, err = tx.Exec(`DROP INDEX document_vectors_idx;`)
  1335. if err != nil {
  1336. return err
  1337. }
  1338. sql := `
  1339. CREATE INDEX document_vectors_idx
  1340. ON entries
  1341. USING gin(document_vectors)
  1342. WHERE status != 'removed';
  1343. `
  1344. _, err = tx.Exec(sql)
  1345. return err
  1346. },
  1347. func(tx *sql.Tx) (err error) {
  1348. _, err = tx.Exec(`UPDATE user_sessions SET ip = '127.0.0.1'::inet WHERE ip IS NULL`)
  1349. if err != nil {
  1350. return err
  1351. }
  1352. _, err = tx.Exec(`UPDATE user_sessions SET created_at = now() WHERE created_at IS NULL`)
  1353. if err != nil {
  1354. return err
  1355. }
  1356. _, err = tx.Exec(`UPDATE user_sessions SET user_agent = '' WHERE user_agent IS NULL`)
  1357. if err != nil {
  1358. return err
  1359. }
  1360. _, err = tx.Exec(`
  1361. ALTER TABLE user_sessions
  1362. ALTER COLUMN ip SET DEFAULT '127.0.0.1'::inet,
  1363. ALTER COLUMN ip SET NOT NULL,
  1364. ALTER COLUMN created_at SET DEFAULT now(),
  1365. ALTER COLUMN created_at SET NOT NULL,
  1366. ALTER COLUMN user_agent SET DEFAULT '',
  1367. ALTER COLUMN user_agent SET NOT NULL
  1368. `)
  1369. return err
  1370. },
  1371. func(tx *sql.Tx) (err error) {
  1372. _, err = tx.Exec(`ALTER TABLE feeds ADD COLUMN ignore_entry_updates bool default 'f'`)
  1373. return err
  1374. },
  1375. func(tx *sql.Tx) (err error) {
  1376. _, err = tx.Exec(`
  1377. DROP TABLE IF EXISTS sessions;
  1378. DROP TABLE IF EXISTS user_sessions;
  1379. CREATE TABLE web_sessions (
  1380. id text not null,
  1381. secret_hash bytea not null,
  1382. user_id int references users(id) on delete cascade,
  1383. created_at timestamp with time zone not null default now(),
  1384. user_agent text not null default '',
  1385. ip inet,
  1386. state jsonb not null default '{}'::jsonb,
  1387. primary key (id),
  1388. check (jsonb_typeof(state) = 'object')
  1389. );
  1390. CREATE INDEX web_sessions_user_id_idx
  1391. ON web_sessions (user_id)
  1392. WHERE user_id IS NOT NULL;
  1393. CREATE INDEX web_sessions_created_at_idx
  1394. ON web_sessions (created_at);
  1395. `)
  1396. return err
  1397. },
  1398. func(tx *sql.Tx) (err error) {
  1399. _, err = tx.Exec(`
  1400. CREATE TABLE entry_tombstones (
  1401. feed_id bigint not null references feeds(id) on delete cascade,
  1402. hash text not null check (hash <> ''),
  1403. deleted_at timestamp with time zone not null default now(),
  1404. primary key (feed_id, hash)
  1405. );
  1406. CREATE INDEX entry_tombstones_deleted_at_idx
  1407. ON entry_tombstones (deleted_at);
  1408. INSERT INTO entry_tombstones (feed_id, hash, deleted_at)
  1409. SELECT feed_id, hash, changed_at
  1410. FROM entries
  1411. WHERE status = 'removed' AND hash <> ''
  1412. ON CONFLICT (feed_id, hash) DO NOTHING;
  1413. DELETE FROM entries WHERE status = 'removed';
  1414. -- The "removed" status is no longer used, so drop the partial
  1415. -- predicate so the planner can use the index for every search.
  1416. DROP INDEX document_vectors_idx;
  1417. CREATE INDEX document_vectors_idx
  1418. ON entries
  1419. USING gin(document_vectors);
  1420. `)
  1421. return err
  1422. },
  1423. func(tx *sql.Tx) (err error) {
  1424. _, err = tx.Exec(`
  1425. DELETE FROM integrations WHERE user_id NOT IN (SELECT id FROM users);
  1426. ALTER TABLE integrations
  1427. ADD CONSTRAINT integrations_user_id_fkey
  1428. FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
  1429. `)
  1430. return err
  1431. },
  1432. func(tx *sql.Tx) (err error) {
  1433. // backup_eligible is nullable: NULL marks pre-migration rows so the login path can backfill it from the assertion on first use.
  1434. _, err = tx.Exec(`
  1435. UPDATE webauthn_credentials SET name = '' WHERE name IS NULL;
  1436. ALTER TABLE webauthn_credentials
  1437. ALTER COLUMN name SET DEFAULT '',
  1438. ALTER COLUMN name SET NOT NULL,
  1439. ADD COLUMN backup_eligible boolean,
  1440. ADD COLUMN backup_state boolean NOT NULL DEFAULT false;
  1441. `)
  1442. return err
  1443. },
  1444. }