migrations.go 37 KB

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