migrations.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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. )
  7. var schemaVersion = len(migrations)
  8. // Order is important. Add new migrations at the end of the list.
  9. var migrations = []func(tx *sql.Tx, driver string) error{
  10. func(tx *sql.Tx, _ string) (err error) {
  11. sql := `
  12. CREATE TABLE schema_version (
  13. version text not null
  14. );
  15. CREATE TABLE users (
  16. id serial not null,
  17. username text not null unique,
  18. password text,
  19. is_admin bool default 'f',
  20. language text default 'en_US',
  21. timezone text default 'UTC',
  22. theme text default 'default',
  23. last_login_at timestamp with time zone,
  24. primary key (id)
  25. );
  26. CREATE TABLE sessions (
  27. id serial not null,
  28. user_id int not null,
  29. token text not null unique,
  30. created_at timestamp with time zone default now(),
  31. user_agent text,
  32. ip text,
  33. primary key (id),
  34. unique (user_id, token),
  35. foreign key (user_id) references users(id) on delete cascade
  36. );
  37. CREATE TABLE categories (
  38. id serial not null,
  39. user_id int not null,
  40. title text not null,
  41. primary key (id),
  42. unique (user_id, title),
  43. foreign key (user_id) references users(id) on delete cascade
  44. );
  45. CREATE TABLE feeds (
  46. id bigserial not null,
  47. user_id int not null,
  48. category_id int not null,
  49. title text not null,
  50. feed_url text not null,
  51. site_url text not null,
  52. checked_at timestamp with time zone default now(),
  53. etag_header text default '',
  54. last_modified_header text default '',
  55. parsing_error_msg text default '',
  56. parsing_error_count int default 0,
  57. primary key (id),
  58. unique (user_id, feed_url),
  59. foreign key (user_id) references users(id) on delete cascade,
  60. foreign key (category_id) references categories(id) on delete cascade
  61. );
  62. CREATE TYPE entry_status as enum('unread', 'read', 'removed');
  63. CREATE TABLE entries (
  64. id bigserial not null,
  65. user_id int not null,
  66. feed_id bigint not null,
  67. hash text not null,
  68. published_at timestamp with time zone not null,
  69. title text not null,
  70. url text not null,
  71. author text,
  72. content text,
  73. status entry_status default 'unread',
  74. primary key (id),
  75. unique (feed_id, hash),
  76. foreign key (user_id) references users(id) on delete cascade,
  77. foreign key (feed_id) references feeds(id) on delete cascade
  78. );
  79. CREATE INDEX entries_feed_idx on entries using btree(feed_id);
  80. CREATE TABLE enclosures (
  81. id bigserial not null,
  82. user_id int not null,
  83. entry_id bigint not null,
  84. url text not null,
  85. size int default 0,
  86. mime_type text default '',
  87. primary key (id),
  88. foreign key (user_id) references users(id) on delete cascade,
  89. foreign key (entry_id) references entries(id) on delete cascade
  90. );
  91. CREATE TABLE icons (
  92. id bigserial not null,
  93. hash text not null unique,
  94. mime_type text not null,
  95. content bytea not null,
  96. primary key (id)
  97. );
  98. CREATE TABLE feed_icons (
  99. feed_id bigint not null,
  100. icon_id bigint not null,
  101. primary key(feed_id, icon_id),
  102. foreign key (feed_id) references feeds(id) on delete cascade,
  103. foreign key (icon_id) references icons(id) on delete cascade
  104. );
  105. `
  106. _, err = tx.Exec(sql)
  107. return err
  108. },
  109. func(tx *sql.Tx, driver string) (err error) {
  110. if driver == "postgresql" {
  111. sql := `
  112. CREATE EXTENSION IF NOT EXISTS hstore;
  113. ALTER TABLE users ADD COLUMN extra hstore;
  114. CREATE INDEX users_extra_idx ON users using gin(extra);
  115. `
  116. _, err = tx.Exec(sql)
  117. return err
  118. }
  119. return nil
  120. },
  121. func(tx *sql.Tx, _ string) (err error) {
  122. sql := `
  123. CREATE TABLE tokens (
  124. id text not null,
  125. value text not null,
  126. created_at timestamp with time zone not null default now(),
  127. primary key(id, value)
  128. );
  129. `
  130. _, err = tx.Exec(sql)
  131. return err
  132. },
  133. func(tx *sql.Tx, _ string) (err error) {
  134. sql := `
  135. CREATE TYPE entry_sorting_direction AS enum('asc', 'desc');
  136. ALTER TABLE users ADD COLUMN entry_direction entry_sorting_direction default 'asc';
  137. `
  138. _, err = tx.Exec(sql)
  139. return err
  140. },
  141. func(tx *sql.Tx, _ string) (err error) {
  142. sql := `
  143. CREATE TABLE integrations (
  144. user_id int not null,
  145. pinboard_enabled bool default 'f',
  146. pinboard_token text default '',
  147. pinboard_tags text default 'miniflux',
  148. pinboard_mark_as_unread bool default 'f',
  149. instapaper_enabled bool default 'f',
  150. instapaper_username text default '',
  151. instapaper_password text default '',
  152. fever_enabled bool default 'f',
  153. fever_username text default '',
  154. fever_password text default '',
  155. fever_token text default '',
  156. primary key(user_id)
  157. );
  158. `
  159. _, err = tx.Exec(sql)
  160. return err
  161. },
  162. func(tx *sql.Tx, _ string) (err error) {
  163. sql := `ALTER TABLE feeds ADD COLUMN scraper_rules text default ''`
  164. _, err = tx.Exec(sql)
  165. return err
  166. },
  167. func(tx *sql.Tx, _ string) (err error) {
  168. sql := `ALTER TABLE feeds ADD COLUMN rewrite_rules text default ''`
  169. _, err = tx.Exec(sql)
  170. return err
  171. },
  172. func(tx *sql.Tx, _ string) (err error) {
  173. sql := `ALTER TABLE feeds ADD COLUMN crawler boolean default 'f'`
  174. _, err = tx.Exec(sql)
  175. return err
  176. },
  177. func(tx *sql.Tx, _ string) (err error) {
  178. sql := `ALTER TABLE sessions rename to user_sessions`
  179. _, err = tx.Exec(sql)
  180. return err
  181. },
  182. func(tx *sql.Tx, _ string) (err error) {
  183. sql := `
  184. DROP TABLE tokens;
  185. CREATE TABLE sessions (
  186. id text not null,
  187. data jsonb not null,
  188. created_at timestamp with time zone not null default now(),
  189. primary key(id)
  190. );
  191. `
  192. _, err = tx.Exec(sql)
  193. return err
  194. },
  195. func(tx *sql.Tx, _ string) (err error) {
  196. sql := `
  197. ALTER TABLE integrations ADD COLUMN wallabag_enabled bool default 'f';
  198. ALTER TABLE integrations ADD COLUMN wallabag_url text default '';
  199. ALTER TABLE integrations ADD COLUMN wallabag_client_id text default '';
  200. ALTER TABLE integrations ADD COLUMN wallabag_client_secret text default '';
  201. ALTER TABLE integrations ADD COLUMN wallabag_username text default '';
  202. ALTER TABLE integrations ADD COLUMN wallabag_password text default '';
  203. `
  204. _, err = tx.Exec(sql)
  205. return err
  206. },
  207. func(tx *sql.Tx, _ string) (err error) {
  208. sql := `ALTER TABLE entries ADD COLUMN starred bool default 'f'`
  209. _, err = tx.Exec(sql)
  210. return err
  211. },
  212. func(tx *sql.Tx, _ string) (err error) {
  213. sql := `
  214. CREATE INDEX entries_user_status_idx ON entries(user_id, status);
  215. CREATE INDEX feeds_user_category_idx ON feeds(user_id, category_id);
  216. `
  217. _, err = tx.Exec(sql)
  218. return err
  219. },
  220. func(tx *sql.Tx, _ string) (err error) {
  221. sql := `
  222. ALTER TABLE integrations ADD COLUMN nunux_keeper_enabled bool default 'f';
  223. ALTER TABLE integrations ADD COLUMN nunux_keeper_url text default '';
  224. ALTER TABLE integrations ADD COLUMN nunux_keeper_api_key text default '';
  225. `
  226. _, err = tx.Exec(sql)
  227. return err
  228. },
  229. func(tx *sql.Tx, _ string) (err error) {
  230. sql := `ALTER TABLE enclosures ALTER COLUMN size SET DATA TYPE bigint`
  231. _, err = tx.Exec(sql)
  232. return err
  233. },
  234. func(tx *sql.Tx, _ string) (err error) {
  235. sql := `ALTER TABLE entries ADD COLUMN comments_url text default ''`
  236. _, err = tx.Exec(sql)
  237. return err
  238. },
  239. func(tx *sql.Tx, _ string) (err error) {
  240. sql := `
  241. ALTER TABLE integrations ADD COLUMN pocket_enabled bool default 'f';
  242. ALTER TABLE integrations ADD COLUMN pocket_access_token text default '';
  243. ALTER TABLE integrations ADD COLUMN pocket_consumer_key text default '';
  244. `
  245. _, err = tx.Exec(sql)
  246. return err
  247. },
  248. func(tx *sql.Tx, _ string) (err error) {
  249. sql := `
  250. ALTER TABLE user_sessions ALTER COLUMN ip SET DATA TYPE inet using ip::inet;
  251. `
  252. _, err = tx.Exec(sql)
  253. return err
  254. },
  255. func(tx *sql.Tx, _ string) (err error) {
  256. sql := `
  257. ALTER TABLE feeds ADD COLUMN username text default '';
  258. ALTER TABLE feeds ADD COLUMN password text default '';
  259. `
  260. _, err = tx.Exec(sql)
  261. return err
  262. },
  263. func(tx *sql.Tx, _ string) (err error) {
  264. sql := `
  265. ALTER TABLE entries ADD COLUMN document_vectors tsvector;
  266. UPDATE entries SET document_vectors = to_tsvector(substring(title || ' ' || coalesce(content, '') for 1000000));
  267. CREATE INDEX document_vectors_idx ON entries USING gin(document_vectors);
  268. `
  269. _, err = tx.Exec(sql)
  270. return err
  271. },
  272. func(tx *sql.Tx, _ string) (err error) {
  273. sql := `ALTER TABLE feeds ADD COLUMN user_agent text default ''`
  274. _, err = tx.Exec(sql)
  275. return err
  276. },
  277. func(tx *sql.Tx, _ string) (err error) {
  278. sql := `
  279. UPDATE
  280. entries
  281. SET
  282. document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B')
  283. `
  284. _, err = tx.Exec(sql)
  285. return err
  286. },
  287. func(tx *sql.Tx, _ string) (err error) {
  288. sql := `ALTER TABLE users ADD COLUMN keyboard_shortcuts boolean default 't'`
  289. _, err = tx.Exec(sql)
  290. return err
  291. },
  292. func(tx *sql.Tx, _ string) (err error) {
  293. sql := `ALTER TABLE feeds ADD COLUMN disabled boolean default 'f';`
  294. _, err = tx.Exec(sql)
  295. return err
  296. },
  297. func(tx *sql.Tx, _ string) (err error) {
  298. sql := `
  299. ALTER TABLE users ALTER COLUMN theme SET DEFAULT 'light_serif';
  300. UPDATE users SET theme='light_serif' WHERE theme='default';
  301. UPDATE users SET theme='light_sans_serif' WHERE theme='sansserif';
  302. UPDATE users SET theme='dark_serif' WHERE theme='black';
  303. `
  304. _, err = tx.Exec(sql)
  305. return err
  306. },
  307. func(tx *sql.Tx, _ string) (err error) {
  308. sql := `
  309. ALTER TABLE entries ADD COLUMN changed_at timestamp with time zone;
  310. UPDATE entries SET changed_at = published_at;
  311. ALTER TABLE entries ALTER COLUMN changed_at SET not null;
  312. `
  313. _, err = tx.Exec(sql)
  314. return err
  315. },
  316. func(tx *sql.Tx, _ string) (err error) {
  317. sql := `
  318. CREATE TABLE api_keys (
  319. id serial not null,
  320. user_id int not null references users(id) on delete cascade,
  321. token text not null unique,
  322. description text not null,
  323. last_used_at timestamp with time zone,
  324. created_at timestamp with time zone default now(),
  325. primary key(id),
  326. unique (user_id, description)
  327. );
  328. `
  329. _, err = tx.Exec(sql)
  330. return err
  331. },
  332. func(tx *sql.Tx, _ string) (err error) {
  333. sql := `
  334. ALTER TABLE entries ADD COLUMN share_code text not null default '';
  335. CREATE UNIQUE INDEX entries_share_code_idx ON entries USING btree(share_code) WHERE share_code <> '';
  336. `
  337. _, err = tx.Exec(sql)
  338. return err
  339. },
  340. func(tx *sql.Tx, _ string) (err error) {
  341. sql := `CREATE INDEX enclosures_user_entry_url_idx ON enclosures(user_id, entry_id, md5(url))`
  342. _, err = tx.Exec(sql)
  343. return err
  344. },
  345. func(tx *sql.Tx, _ string) (err error) {
  346. sql := `
  347. ALTER TABLE feeds ADD COLUMN next_check_at timestamp with time zone default now();
  348. CREATE INDEX entries_user_feed_idx ON entries (user_id, feed_id);
  349. `
  350. _, err = tx.Exec(sql)
  351. return err
  352. },
  353. func(tx *sql.Tx, _ string) (err error) {
  354. sql := `ALTER TABLE feeds ADD COLUMN ignore_http_cache bool default false`
  355. _, err = tx.Exec(sql)
  356. return err
  357. },
  358. func(tx *sql.Tx, _ string) (err error) {
  359. sql := `ALTER TABLE users ADD COLUMN entries_per_page int default 100`
  360. _, err = tx.Exec(sql)
  361. return err
  362. },
  363. func(tx *sql.Tx, _ string) (err error) {
  364. sql := `ALTER TABLE users ADD COLUMN show_reading_time boolean default 't'`
  365. _, err = tx.Exec(sql)
  366. return err
  367. },
  368. func(tx *sql.Tx, _ string) (err error) {
  369. sql := `CREATE INDEX entries_id_user_status_idx ON entries USING btree (id, user_id, status)`
  370. _, err = tx.Exec(sql)
  371. return err
  372. },
  373. func(tx *sql.Tx, _ string) (err error) {
  374. sql := `ALTER TABLE feeds ADD COLUMN fetch_via_proxy bool default false`
  375. _, err = tx.Exec(sql)
  376. return err
  377. },
  378. func(tx *sql.Tx, _ string) (err error) {
  379. sql := `CREATE INDEX entries_feed_id_status_hash_idx ON entries USING btree (feed_id, status, hash)`
  380. _, err = tx.Exec(sql)
  381. return err
  382. },
  383. func(tx *sql.Tx, _ string) (err error) {
  384. sql := `CREATE INDEX entries_user_id_status_starred_idx ON entries (user_id, status, starred)`
  385. _, err = tx.Exec(sql)
  386. return err
  387. },
  388. func(tx *sql.Tx, _ string) (err error) {
  389. sql := `ALTER TABLE users ADD COLUMN entry_swipe boolean default 't'`
  390. _, err = tx.Exec(sql)
  391. return err
  392. },
  393. func(tx *sql.Tx, _ string) (err error) {
  394. sql := `ALTER TABLE integrations DROP COLUMN fever_password`
  395. _, err = tx.Exec(sql)
  396. return err
  397. },
  398. func(tx *sql.Tx, _ string) (err error) {
  399. sql := `
  400. ALTER TABLE feeds
  401. ADD COLUMN blocklist_rules text not null default '',
  402. ADD COLUMN keeplist_rules text not null default ''
  403. `
  404. _, err = tx.Exec(sql)
  405. return err
  406. },
  407. func(tx *sql.Tx, _ string) (err error) {
  408. sql := `ALTER TABLE entries ADD COLUMN reading_time int not null default 0`
  409. _, err = tx.Exec(sql)
  410. return err
  411. },
  412. func(tx *sql.Tx, _ string) (err error) {
  413. sql := `
  414. ALTER TABLE entries ADD COLUMN created_at timestamp with time zone not null default now();
  415. UPDATE entries SET created_at = published_at;
  416. `
  417. _, err = tx.Exec(sql)
  418. return err
  419. },
  420. func(tx *sql.Tx, driver string) (err error) {
  421. _, err = tx.Exec(`
  422. ALTER TABLE users
  423. ADD column stylesheet text not null default '',
  424. ADD column google_id text not null default '',
  425. ADD column openid_connect_id text not null default ''
  426. `)
  427. if err != nil {
  428. return err
  429. }
  430. if driver == "postgresql" {
  431. _, err = tx.Exec(`
  432. DECLARE my_cursor CURSOR FOR
  433. SELECT
  434. id,
  435. COALESCE(extra->'custom_css', '') as custom_css,
  436. COALESCE(extra->'google_id', '') as google_id,
  437. COALESCE(extra->'oidc_id', '') as oidc_id
  438. FROM users
  439. FOR UPDATE
  440. `)
  441. if err != nil {
  442. return err
  443. }
  444. defer tx.Exec("CLOSE my_cursor")
  445. for {
  446. var (
  447. userID int64
  448. customStylesheet string
  449. googleID string
  450. oidcID string
  451. )
  452. if err := tx.QueryRow(`FETCH NEXT FROM my_cursor`).Scan(&userID, &customStylesheet, &googleID, &oidcID); err != nil {
  453. if err == sql.ErrNoRows {
  454. break
  455. }
  456. return err
  457. }
  458. _, err := tx.Exec(
  459. `UPDATE
  460. users
  461. SET
  462. stylesheet=$2,
  463. google_id=$3,
  464. openid_connect_id=$4
  465. WHERE
  466. id=$1
  467. `,
  468. userID, customStylesheet, googleID, oidcID)
  469. if err != nil {
  470. return err
  471. }
  472. }
  473. }
  474. return err
  475. },
  476. func(tx *sql.Tx, driver string) (err error) {
  477. if driver == "postgresql" {
  478. if _, err = tx.Exec(`ALTER TABLE users DROP COLUMN extra;`); err != nil {
  479. return err
  480. }
  481. }
  482. _, err = tx.Exec(`
  483. CREATE UNIQUE INDEX users_google_id_idx ON users(google_id) WHERE google_id <> '';
  484. CREATE UNIQUE INDEX users_openid_connect_id_idx ON users(openid_connect_id) WHERE openid_connect_id <> '';
  485. `)
  486. return err
  487. },
  488. func(tx *sql.Tx, _ string) (err error) {
  489. _, err = tx.Exec(`
  490. CREATE INDEX entries_feed_url_idx ON entries(feed_id, url);
  491. CREATE INDEX entries_user_status_feed_idx ON entries(user_id, status, feed_id);
  492. CREATE INDEX entries_user_status_changed_idx ON entries(user_id, status, changed_at);
  493. `)
  494. return err
  495. },
  496. func(tx *sql.Tx, _ string) (err error) {
  497. _, err = tx.Exec(`
  498. CREATE TABLE acme_cache (
  499. key varchar(400) not null primary key,
  500. data bytea not null,
  501. updated_at timestamptz not null
  502. );
  503. `)
  504. return err
  505. },
  506. func(tx *sql.Tx, _ string) (err error) {
  507. _, err = tx.Exec(`
  508. ALTER TABLE feeds ADD COLUMN allow_self_signed_certificates boolean not null default false
  509. `)
  510. return err
  511. },
  512. func(tx *sql.Tx, _ string) (err error) {
  513. sql := `
  514. CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
  515. ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
  516. `
  517. _, err = tx.Exec(sql)
  518. return err
  519. },
  520. func(tx *sql.Tx, _ string) (err error) {
  521. sql := `ALTER TABLE feeds ADD COLUMN cookie text default ''`
  522. _, err = tx.Exec(sql)
  523. return err
  524. },
  525. func(tx *sql.Tx, _ string) (err error) {
  526. _, err = tx.Exec(`
  527. ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
  528. `)
  529. return err
  530. },
  531. func(tx *sql.Tx, _ string) (err error) {
  532. _, err = tx.Exec(`
  533. ALTER TABLE feeds ADD COLUMN hide_globally boolean not null default false
  534. `)
  535. return err
  536. },
  537. func(tx *sql.Tx, _ string) (err error) {
  538. sql := `
  539. ALTER TABLE integrations ADD COLUMN telegram_bot_enabled bool default 'f';
  540. ALTER TABLE integrations ADD COLUMN telegram_bot_token text default '';
  541. ALTER TABLE integrations ADD COLUMN telegram_bot_chat_id text default '';
  542. `
  543. _, err = tx.Exec(sql)
  544. return err
  545. },
  546. func(tx *sql.Tx, _ string) (err error) {
  547. sql := `
  548. CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
  549. ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
  550. `
  551. _, err = tx.Exec(sql)
  552. return err
  553. },
  554. func(tx *sql.Tx, _ string) (err error) {
  555. sql := `
  556. ALTER TABLE integrations ADD COLUMN googlereader_enabled bool default 'f';
  557. ALTER TABLE integrations ADD COLUMN googlereader_username text default '';
  558. ALTER TABLE integrations ADD COLUMN googlereader_password text default '';
  559. `
  560. _, err = tx.Exec(sql)
  561. return err
  562. },
  563. func(tx *sql.Tx, _ string) (err error) {
  564. sql := `
  565. ALTER TABLE integrations ADD COLUMN espial_enabled bool default 'f';
  566. ALTER TABLE integrations ADD COLUMN espial_url text default '';
  567. ALTER TABLE integrations ADD COLUMN espial_api_key text default '';
  568. ALTER TABLE integrations ADD COLUMN espial_tags text default 'miniflux';
  569. `
  570. _, err = tx.Exec(sql)
  571. return err
  572. },
  573. func(tx *sql.Tx, _ string) (err error) {
  574. sql := `
  575. ALTER TABLE integrations ADD COLUMN linkding_enabled bool default 'f';
  576. ALTER TABLE integrations ADD COLUMN linkding_url text default '';
  577. ALTER TABLE integrations ADD COLUMN linkding_api_key text default '';
  578. `
  579. _, err = tx.Exec(sql)
  580. return err
  581. },
  582. func(tx *sql.Tx, _ string) (err error) {
  583. _, err = tx.Exec(`
  584. ALTER TABLE feeds ADD COLUMN url_rewrite_rules text not null default ''
  585. `)
  586. return err
  587. },
  588. func(tx *sql.Tx, _ string) (err error) {
  589. _, err = tx.Exec(`
  590. ALTER TABLE users ADD COLUMN default_reading_speed int default 265;
  591. ALTER TABLE users ADD COLUMN cjk_reading_speed int default 500;
  592. `)
  593. return
  594. },
  595. func(tx *sql.Tx, _ string) (err error) {
  596. _, err = tx.Exec(`
  597. ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
  598. `)
  599. return
  600. },
  601. func(tx *sql.Tx, _ string) (err error) {
  602. _, err = tx.Exec(`
  603. ALTER TABLE integrations ADD COLUMN wallabag_only_url bool default 'f';
  604. `)
  605. return
  606. },
  607. func(tx *sql.Tx, _ string) (err error) {
  608. _, err = tx.Exec(`
  609. ALTER TABLE users ADD COLUMN categories_sorting_order text not null default 'unread_count';
  610. `)
  611. return
  612. },
  613. func(tx *sql.Tx, _ string) (err error) {
  614. sql := `
  615. ALTER TABLE integrations ADD COLUMN matrix_bot_enabled bool default 'f';
  616. ALTER TABLE integrations ADD COLUMN matrix_bot_user text default '';
  617. ALTER TABLE integrations ADD COLUMN matrix_bot_password text default '';
  618. ALTER TABLE integrations ADD COLUMN matrix_bot_url text default '';
  619. ALTER TABLE integrations ADD COLUMN matrix_bot_chat_id text default '';
  620. `
  621. _, err = tx.Exec(sql)
  622. return
  623. },
  624. func(tx *sql.Tx, _ string) (err error) {
  625. sql := `ALTER TABLE users ADD COLUMN double_tap boolean default 't'`
  626. _, err = tx.Exec(sql)
  627. return err
  628. },
  629. func(tx *sql.Tx, _ string) (err error) {
  630. _, err = tx.Exec(`
  631. ALTER TABLE entries ADD COLUMN tags text[] default '{}';
  632. `)
  633. return
  634. },
  635. func(tx *sql.Tx, _ string) (err error) {
  636. sql := `
  637. ALTER TABLE users RENAME double_tap TO gesture_nav;
  638. ALTER TABLE users ALTER COLUMN gesture_nav SET DATA TYPE text using case when gesture_nav = true then 'tap' when gesture_nav = false then 'none' end;
  639. ALTER TABLE users ALTER COLUMN gesture_nav SET default 'tap';
  640. `
  641. _, err = tx.Exec(sql)
  642. return err
  643. },
  644. func(tx *sql.Tx, _ string) (err error) {
  645. sql := `
  646. ALTER TABLE integrations ADD COLUMN linkding_tags text default '';
  647. `
  648. _, err = tx.Exec(sql)
  649. return err
  650. },
  651. func(tx *sql.Tx, _ string) (err error) {
  652. sql := `
  653. ALTER TABLE feeds ADD COLUMN no_media_player boolean default 'f';
  654. ALTER TABLE enclosures ADD COLUMN media_progression int default 0;
  655. `
  656. _, err = tx.Exec(sql)
  657. return err
  658. },
  659. func(tx *sql.Tx, _ string) (err error) {
  660. sql := `
  661. ALTER TABLE integrations ADD COLUMN linkding_mark_as_unread bool default 'f';
  662. `
  663. _, err = tx.Exec(sql)
  664. return err
  665. },
  666. func(tx *sql.Tx, _ string) (err error) {
  667. // Delete duplicated rows
  668. sql := `
  669. DELETE FROM enclosures a USING enclosures b
  670. WHERE a.id < b.id
  671. AND a.user_id = b.user_id
  672. AND a.entry_id = b.entry_id
  673. AND a.url = b.url;
  674. `
  675. _, err = tx.Exec(sql)
  676. if err != nil {
  677. return err
  678. }
  679. // Remove previous index
  680. _, err = tx.Exec(`DROP INDEX enclosures_user_entry_url_idx`)
  681. if err != nil {
  682. return err
  683. }
  684. // Create unique index
  685. _, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
  686. if err != nil {
  687. return err
  688. }
  689. return nil
  690. },
  691. func(tx *sql.Tx, _ string) (err error) {
  692. sql := `ALTER TABLE users ADD COLUMN mark_read_on_view boolean default 't'`
  693. _, err = tx.Exec(sql)
  694. return err
  695. },
  696. func(tx *sql.Tx, _ string) (err error) {
  697. sql := `
  698. ALTER TABLE integrations ADD COLUMN notion_enabled bool default 'f';
  699. ALTER TABLE integrations ADD COLUMN notion_token text default '';
  700. ALTER TABLE integrations ADD COLUMN notion_page_id text default '';
  701. `
  702. _, err = tx.Exec(sql)
  703. return err
  704. },
  705. func(tx *sql.Tx, _ string) (err error) {
  706. sql := `
  707. ALTER TABLE integrations ADD COLUMN readwise_enabled bool default 'f';
  708. ALTER TABLE integrations ADD COLUMN readwise_api_key text default '';
  709. `
  710. _, err = tx.Exec(sql)
  711. return err
  712. },
  713. func(tx *sql.Tx, _ string) (err error) {
  714. sql := `
  715. ALTER TABLE integrations ADD COLUMN apprise_enabled bool default 'f';
  716. ALTER TABLE integrations ADD COLUMN apprise_url text default '';
  717. ALTER TABLE integrations ADD COLUMN apprise_services_url text default '';
  718. `
  719. _, err = tx.Exec(sql)
  720. return err
  721. },
  722. func(tx *sql.Tx, _ string) (err error) {
  723. sql := `
  724. ALTER TABLE integrations ADD COLUMN shiori_enabled bool default 'f';
  725. ALTER TABLE integrations ADD COLUMN shiori_url text default '';
  726. ALTER TABLE integrations ADD COLUMN shiori_username text default '';
  727. ALTER TABLE integrations ADD COLUMN shiori_password text default '';
  728. `
  729. _, err = tx.Exec(sql)
  730. return err
  731. },
  732. func(tx *sql.Tx, _ string) (err error) {
  733. sql := `
  734. ALTER TABLE integrations ADD COLUMN shaarli_enabled bool default 'f';
  735. ALTER TABLE integrations ADD COLUMN shaarli_url text default '';
  736. ALTER TABLE integrations ADD COLUMN shaarli_api_secret text default '';
  737. `
  738. _, err = tx.Exec(sql)
  739. return err
  740. },
  741. func(tx *sql.Tx, _ string) (err error) {
  742. _, err = tx.Exec(`
  743. ALTER TABLE feeds ADD COLUMN apprise_service_urls text default '';
  744. `)
  745. return err
  746. },
  747. func(tx *sql.Tx, _ string) (err error) {
  748. sql := `
  749. ALTER TABLE integrations ADD COLUMN webhook_enabled bool default 'f';
  750. ALTER TABLE integrations ADD COLUMN webhook_url text default '';
  751. ALTER TABLE integrations ADD COLUMN webhook_secret text default '';
  752. `
  753. _, err = tx.Exec(sql)
  754. return err
  755. },
  756. func(tx *sql.Tx, _ string) (err error) {
  757. sql := `
  758. ALTER TABLE integrations ADD COLUMN telegram_bot_topic_id int;
  759. ALTER TABLE integrations ADD COLUMN telegram_bot_disable_web_page_preview bool default 'f';
  760. ALTER TABLE integrations ADD COLUMN telegram_bot_disable_notification bool default 'f';
  761. `
  762. _, err = tx.Exec(sql)
  763. return err
  764. },
  765. func(tx *sql.Tx, _ string) (err error) {
  766. sql := `
  767. ALTER TABLE integrations ADD COLUMN telegram_bot_disable_buttons bool default 'f';
  768. `
  769. _, err = tx.Exec(sql)
  770. return err
  771. },
  772. func(tx *sql.Tx, _ string) (err error) {
  773. sql := `
  774. -- Speed up has_enclosure
  775. CREATE INDEX enclosures_entry_id_idx ON enclosures(entry_id);
  776. -- Speed up unread page
  777. CREATE INDEX entries_user_status_published_idx ON entries(user_id, status, published_at);
  778. CREATE INDEX entries_user_status_created_idx ON entries(user_id, status, created_at);
  779. CREATE INDEX feeds_feed_id_hide_globally_idx ON feeds(id, hide_globally);
  780. -- Speed up history page
  781. CREATE INDEX entries_user_status_changed_published_idx ON entries(user_id, status, changed_at, published_at);
  782. `
  783. _, err = tx.Exec(sql)
  784. return err
  785. },
  786. func(tx *sql.Tx, _ string) (err error) {
  787. sql := `
  788. ALTER TABLE integrations ADD COLUMN rssbridge_enabled bool default 'f';
  789. ALTER TABLE integrations ADD COLUMN rssbridge_url text default '';
  790. `
  791. _, err = tx.Exec(sql)
  792. return
  793. },
  794. func(tx *sql.Tx, _ string) (err error) {
  795. _, err = tx.Exec(`
  796. CREATE TABLE webauthn_credentials (
  797. handle bytea primary key,
  798. cred_id bytea unique not null,
  799. user_id int references users(id) on delete cascade not null,
  800. public_key bytea not null,
  801. attestation_type varchar(255) not null,
  802. aaguid bytea,
  803. sign_count bigint,
  804. clone_warning bool,
  805. name text,
  806. added_on timestamp with time zone default now(),
  807. last_seen_on timestamp with time zone default now()
  808. );
  809. `)
  810. return
  811. },
  812. func(tx *sql.Tx, _ string) (err error) {
  813. sql := `
  814. ALTER TABLE integrations ADD COLUMN omnivore_enabled bool default 'f';
  815. ALTER TABLE integrations ADD COLUMN omnivore_api_key text default '';
  816. ALTER TABLE integrations ADD COLUMN omnivore_url text default '';
  817. `
  818. _, err = tx.Exec(sql)
  819. return
  820. },
  821. func(tx *sql.Tx, _ string) (err error) {
  822. sql := `
  823. ALTER TABLE integrations ADD COLUMN linkace_enabled bool default 'f';
  824. ALTER TABLE integrations ADD COLUMN linkace_url text default '';
  825. ALTER TABLE integrations ADD COLUMN linkace_api_key text default '';
  826. ALTER TABLE integrations ADD COLUMN linkace_tags text default '';
  827. ALTER TABLE integrations ADD COLUMN linkace_is_private bool default 't';
  828. ALTER TABLE integrations ADD COLUMN linkace_check_disabled bool default 't';
  829. `
  830. _, err = tx.Exec(sql)
  831. return err
  832. },
  833. func(tx *sql.Tx, _ string) (err error) {
  834. sql := `
  835. ALTER TABLE integrations ADD COLUMN linkwarden_enabled bool default 'f';
  836. ALTER TABLE integrations ADD COLUMN linkwarden_url text default '';
  837. ALTER TABLE integrations ADD COLUMN linkwarden_api_key text default '';
  838. `
  839. _, err = tx.Exec(sql)
  840. return err
  841. },
  842. func(tx *sql.Tx, _ string) (err error) {
  843. sql := `
  844. ALTER TABLE integrations ADD COLUMN readeck_enabled bool default 'f';
  845. ALTER TABLE integrations ADD COLUMN readeck_only_url bool default 'f';
  846. ALTER TABLE integrations ADD COLUMN readeck_url text default '';
  847. ALTER TABLE integrations ADD COLUMN readeck_api_key text default '';
  848. ALTER TABLE integrations ADD COLUMN readeck_labels text default '';
  849. `
  850. _, err = tx.Exec(sql)
  851. return err
  852. },
  853. func(tx *sql.Tx, _ string) (err error) {
  854. sql := `ALTER TABLE feeds ADD COLUMN disable_http2 bool default 'f'`
  855. _, err = tx.Exec(sql)
  856. return err
  857. },
  858. func(tx *sql.Tx, _ string) (err error) {
  859. sql := `ALTER TABLE users ADD COLUMN media_playback_rate numeric default 1;`
  860. _, err = tx.Exec(sql)
  861. return err
  862. },
  863. func(tx *sql.Tx, _ string) (err error) {
  864. // the WHERE part speed-up the request a lot
  865. sql := `UPDATE entries SET tags = array_remove(tags, '') WHERE '' = ANY(tags);`
  866. _, err = tx.Exec(sql)
  867. return err
  868. },
  869. func(tx *sql.Tx, _ string) (err error) {
  870. // Entry URLs can exceeds btree maximum size
  871. // Checking entry existence is now using entries_feed_id_status_hash_idx index
  872. _, err = tx.Exec(`DROP INDEX entries_feed_url_idx`)
  873. return err
  874. },
  875. func(tx *sql.Tx, _ string) (err error) {
  876. sql := `
  877. ALTER TABLE integrations ADD COLUMN raindrop_enabled bool default 'f';
  878. ALTER TABLE integrations ADD COLUMN raindrop_token text default '';
  879. ALTER TABLE integrations ADD COLUMN raindrop_collection_id text default '';
  880. ALTER TABLE integrations ADD COLUMN raindrop_tags text default '';
  881. `
  882. _, err = tx.Exec(sql)
  883. return err
  884. },
  885. func(tx *sql.Tx, _ string) (err error) {
  886. sql := `ALTER TABLE feeds ADD COLUMN description text default ''`
  887. _, err = tx.Exec(sql)
  888. return err
  889. },
  890. func(tx *sql.Tx, _ string) (err error) {
  891. sql := `
  892. ALTER TABLE users
  893. ADD COLUMN block_filter_entry_rules text not null default '',
  894. ADD COLUMN keep_filter_entry_rules text not null default ''
  895. `
  896. _, err = tx.Exec(sql)
  897. return err
  898. },
  899. func(tx *sql.Tx, _ string) (err error) {
  900. sql := `
  901. ALTER TABLE integrations ADD COLUMN betula_url text default '';
  902. ALTER TABLE integrations ADD COLUMN betula_token text default '';
  903. ALTER TABLE integrations ADD COLUMN betula_enabled bool default 'f';
  904. `
  905. _, err = tx.Exec(sql)
  906. return err
  907. },
  908. func(tx *sql.Tx, _ string) (err error) {
  909. sql := `
  910. ALTER TABLE integrations ADD COLUMN ntfy_enabled bool default 'f';
  911. ALTER TABLE integrations ADD COLUMN ntfy_url text default '';
  912. ALTER TABLE integrations ADD COLUMN ntfy_topic text default '';
  913. ALTER TABLE integrations ADD COLUMN ntfy_api_token text default '';
  914. ALTER TABLE integrations ADD COLUMN ntfy_username text default '';
  915. ALTER TABLE integrations ADD COLUMN ntfy_password text default '';
  916. ALTER TABLE integrations ADD COLUMN ntfy_icon_url text default '';
  917. ALTER TABLE feeds ADD COLUMN ntfy_enabled bool default 'f';
  918. ALTER TABLE feeds ADD COLUMN ntfy_priority int default '3';
  919. `
  920. _, err = tx.Exec(sql)
  921. return err
  922. },
  923. func(tx *sql.Tx, _ string) (err error) {
  924. sql := `ALTER TABLE users ADD COLUMN mark_read_on_media_player_completion bool default 'f';`
  925. _, err = tx.Exec(sql)
  926. return err
  927. },
  928. func(tx *sql.Tx, _ string) (err error) {
  929. sql := `ALTER TABLE users ADD COLUMN custom_js text not null default '';`
  930. _, err = tx.Exec(sql)
  931. return err
  932. },
  933. func(tx *sql.Tx, _ string) (err error) {
  934. sql := `ALTER TABLE users ADD COLUMN external_font_hosts text not null default '';`
  935. _, err = tx.Exec(sql)
  936. return err
  937. },
  938. func(tx *sql.Tx, _ string) (err error) {
  939. sql := `
  940. ALTER TABLE integrations ADD COLUMN cubox_enabled bool default 'f';
  941. ALTER TABLE integrations ADD COLUMN cubox_api_link text default '';
  942. `
  943. _, err = tx.Exec(sql)
  944. return err
  945. },
  946. func(tx *sql.Tx, _ string) (err error) {
  947. sql := `
  948. ALTER TABLE integrations ADD COLUMN discord_enabled bool default 'f';
  949. ALTER TABLE integrations ADD COLUMN discord_webhook_link text default '';
  950. `
  951. _, err = tx.Exec(sql)
  952. return err
  953. },
  954. func(tx *sql.Tx, _ string) (err error) {
  955. sql := `ALTER TABLE integrations ADD COLUMN ntfy_internal_links bool default 'f';`
  956. _, err = tx.Exec(sql)
  957. return err
  958. },
  959. }