migrations.go 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138
  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 not null,
  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 not null,
  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 not null,
  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 not null,
  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 not null,
  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 not null,
  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 not null,
  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. 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. func(tx *sql.Tx) (err error) {
  120. sql := `
  121. CREATE TABLE tokens (
  122. id text not null,
  123. value text not null,
  124. created_at timestamp with time zone not null default now(),
  125. primary key(id, value)
  126. );
  127. `
  128. _, err = tx.Exec(sql)
  129. return err
  130. },
  131. func(tx *sql.Tx) (err error) {
  132. sql := `
  133. CREATE TYPE entry_sorting_direction AS enum('asc', 'desc');
  134. ALTER TABLE users ADD COLUMN entry_direction entry_sorting_direction default 'asc';
  135. `
  136. _, err = tx.Exec(sql)
  137. return err
  138. },
  139. func(tx *sql.Tx) (err error) {
  140. sql := `
  141. CREATE TABLE integrations (
  142. user_id int not null,
  143. pinboard_enabled bool default 'f',
  144. pinboard_token text default '',
  145. pinboard_tags text default 'miniflux',
  146. pinboard_mark_as_unread bool default 'f',
  147. instapaper_enabled bool default 'f',
  148. instapaper_username text default '',
  149. instapaper_password text default '',
  150. fever_enabled bool default 'f',
  151. fever_username text default '',
  152. fever_password text default '',
  153. fever_token text default '',
  154. primary key(user_id)
  155. );
  156. `
  157. _, err = tx.Exec(sql)
  158. return err
  159. },
  160. func(tx *sql.Tx) (err error) {
  161. sql := `ALTER TABLE feeds ADD COLUMN scraper_rules text default ''`
  162. _, err = tx.Exec(sql)
  163. return err
  164. },
  165. func(tx *sql.Tx) (err error) {
  166. sql := `ALTER TABLE feeds ADD COLUMN rewrite_rules text default ''`
  167. _, err = tx.Exec(sql)
  168. return err
  169. },
  170. func(tx *sql.Tx) (err error) {
  171. sql := `ALTER TABLE feeds ADD COLUMN crawler boolean default 'f'`
  172. _, err = tx.Exec(sql)
  173. return err
  174. },
  175. func(tx *sql.Tx) (err error) {
  176. sql := `ALTER TABLE sessions rename to user_sessions`
  177. _, err = tx.Exec(sql)
  178. return err
  179. },
  180. func(tx *sql.Tx) (err error) {
  181. sql := `
  182. DROP TABLE tokens;
  183. CREATE TABLE sessions (
  184. id text not null,
  185. data jsonb not null,
  186. created_at timestamp with time zone not null default now(),
  187. primary key(id)
  188. );
  189. `
  190. _, err = tx.Exec(sql)
  191. return err
  192. },
  193. func(tx *sql.Tx) (err error) {
  194. sql := `
  195. ALTER TABLE integrations
  196. ADD COLUMN wallabag_enabled bool default 'f',
  197. ADD COLUMN wallabag_url text default '',
  198. ADD COLUMN wallabag_client_id text default '',
  199. ADD COLUMN wallabag_client_secret text default '',
  200. ADD COLUMN wallabag_username text default '',
  201. ADD COLUMN wallabag_password text default '';
  202. `
  203. _, err = tx.Exec(sql)
  204. return err
  205. },
  206. func(tx *sql.Tx) (err error) {
  207. sql := `ALTER TABLE entries ADD COLUMN starred bool default 'f'`
  208. _, err = tx.Exec(sql)
  209. return err
  210. },
  211. func(tx *sql.Tx) (err error) {
  212. sql := `
  213. CREATE INDEX entries_user_status_idx ON entries(user_id, status);
  214. CREATE INDEX feeds_user_category_idx ON feeds(user_id, category_id);
  215. `
  216. _, err = tx.Exec(sql)
  217. return err
  218. },
  219. func(tx *sql.Tx) (err error) {
  220. sql := `
  221. ALTER TABLE integrations
  222. ADD COLUMN nunux_keeper_enabled bool default 'f',
  223. ADD COLUMN nunux_keeper_url text default '',
  224. ADD COLUMN nunux_keeper_api_key text default '';
  225. `
  226. _, err = tx.Exec(sql)
  227. return err
  228. },
  229. func(tx *sql.Tx) (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) (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) (err error) {
  240. sql := `
  241. ALTER TABLE integrations
  242. ADD COLUMN pocket_enabled bool default 'f',
  243. ADD COLUMN pocket_access_token text default '',
  244. ADD COLUMN pocket_consumer_key text default '';
  245. `
  246. _, err = tx.Exec(sql)
  247. return err
  248. },
  249. func(tx *sql.Tx) (err error) {
  250. sql := `
  251. ALTER TABLE user_sessions ALTER COLUMN ip SET DATA TYPE inet using ip::inet;
  252. `
  253. _, err = tx.Exec(sql)
  254. return err
  255. },
  256. func(tx *sql.Tx) (err error) {
  257. sql := `
  258. ALTER TABLE feeds
  259. ADD COLUMN username text default '',
  260. ADD COLUMN password text default '';
  261. `
  262. _, err = tx.Exec(sql)
  263. return err
  264. },
  265. func(tx *sql.Tx) (err error) {
  266. sql := `
  267. ALTER TABLE entries ADD COLUMN document_vectors tsvector;
  268. UPDATE entries SET document_vectors = to_tsvector(substring(title || ' ' || coalesce(content, '') for 1000000));
  269. CREATE INDEX document_vectors_idx ON entries USING gin(document_vectors);
  270. `
  271. _, err = tx.Exec(sql)
  272. return err
  273. },
  274. func(tx *sql.Tx) (err error) {
  275. sql := `ALTER TABLE feeds ADD COLUMN user_agent text default ''`
  276. _, err = tx.Exec(sql)
  277. return err
  278. },
  279. func(tx *sql.Tx) (err error) {
  280. sql := `
  281. UPDATE
  282. entries
  283. SET
  284. document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B')
  285. `
  286. _, err = tx.Exec(sql)
  287. return err
  288. },
  289. func(tx *sql.Tx) (err error) {
  290. sql := `ALTER TABLE users ADD COLUMN keyboard_shortcuts boolean default 't'`
  291. _, err = tx.Exec(sql)
  292. return err
  293. },
  294. func(tx *sql.Tx) (err error) {
  295. sql := `ALTER TABLE feeds ADD COLUMN disabled boolean default 'f';`
  296. _, err = tx.Exec(sql)
  297. return err
  298. },
  299. func(tx *sql.Tx) (err error) {
  300. sql := `
  301. ALTER TABLE users ALTER COLUMN theme SET DEFAULT 'light_serif';
  302. UPDATE users SET theme='light_serif' WHERE theme='default';
  303. UPDATE users SET theme='light_sans_serif' WHERE theme='sansserif';
  304. UPDATE users SET theme='dark_serif' WHERE theme='black';
  305. `
  306. _, err = tx.Exec(sql)
  307. return err
  308. },
  309. func(tx *sql.Tx) (err error) {
  310. sql := `
  311. ALTER TABLE entries ADD COLUMN changed_at timestamp with time zone;
  312. UPDATE entries SET changed_at = published_at;
  313. ALTER TABLE entries ALTER COLUMN changed_at SET not null;
  314. `
  315. _, err = tx.Exec(sql)
  316. return err
  317. },
  318. func(tx *sql.Tx) (err error) {
  319. sql := `
  320. CREATE TABLE api_keys (
  321. id serial not null,
  322. user_id int not null references users(id) on delete cascade,
  323. token text not null unique,
  324. description text not null,
  325. last_used_at timestamp with time zone,
  326. created_at timestamp with time zone default now(),
  327. primary key(id),
  328. unique (user_id, description)
  329. );
  330. `
  331. _, err = tx.Exec(sql)
  332. return err
  333. },
  334. func(tx *sql.Tx) (err error) {
  335. sql := `
  336. ALTER TABLE entries ADD COLUMN share_code text not null default '';
  337. CREATE UNIQUE INDEX entries_share_code_idx ON entries USING btree(share_code) WHERE share_code <> '';
  338. `
  339. _, err = tx.Exec(sql)
  340. return err
  341. },
  342. func(tx *sql.Tx) (err error) {
  343. sql := `CREATE INDEX enclosures_user_entry_url_idx ON enclosures(user_id, entry_id, md5(url))`
  344. _, err = tx.Exec(sql)
  345. return err
  346. },
  347. func(tx *sql.Tx) (err error) {
  348. sql := `
  349. ALTER TABLE feeds ADD COLUMN next_check_at timestamp with time zone default now();
  350. CREATE INDEX entries_user_feed_idx ON entries (user_id, feed_id);
  351. `
  352. _, err = tx.Exec(sql)
  353. return err
  354. },
  355. func(tx *sql.Tx) (err error) {
  356. sql := `ALTER TABLE feeds ADD COLUMN ignore_http_cache bool default false`
  357. _, err = tx.Exec(sql)
  358. return err
  359. },
  360. func(tx *sql.Tx) (err error) {
  361. sql := `ALTER TABLE users ADD COLUMN entries_per_page int default 100`
  362. _, err = tx.Exec(sql)
  363. return err
  364. },
  365. func(tx *sql.Tx) (err error) {
  366. sql := `ALTER TABLE users ADD COLUMN show_reading_time boolean default 't'`
  367. _, err = tx.Exec(sql)
  368. return err
  369. },
  370. func(tx *sql.Tx) (err error) {
  371. sql := `CREATE INDEX entries_id_user_status_idx ON entries USING btree (id, user_id, status)`
  372. _, err = tx.Exec(sql)
  373. return err
  374. },
  375. func(tx *sql.Tx) (err error) {
  376. sql := `ALTER TABLE feeds ADD COLUMN fetch_via_proxy bool default false`
  377. _, err = tx.Exec(sql)
  378. return err
  379. },
  380. func(tx *sql.Tx) (err error) {
  381. sql := `CREATE INDEX entries_feed_id_status_hash_idx ON entries USING btree (feed_id, status, hash)`
  382. _, err = tx.Exec(sql)
  383. return err
  384. },
  385. func(tx *sql.Tx) (err error) {
  386. sql := `CREATE INDEX entries_user_id_status_starred_idx ON entries (user_id, status, starred)`
  387. _, err = tx.Exec(sql)
  388. return err
  389. },
  390. func(tx *sql.Tx) (err error) {
  391. sql := `ALTER TABLE users ADD COLUMN entry_swipe boolean default 't'`
  392. _, err = tx.Exec(sql)
  393. return err
  394. },
  395. func(tx *sql.Tx) (err error) {
  396. sql := `ALTER TABLE integrations DROP COLUMN fever_password`
  397. _, err = tx.Exec(sql)
  398. return err
  399. },
  400. func(tx *sql.Tx) (err error) {
  401. sql := `
  402. ALTER TABLE feeds
  403. ADD COLUMN blocklist_rules text not null default '',
  404. ADD COLUMN keeplist_rules text not null default ''
  405. `
  406. _, err = tx.Exec(sql)
  407. return err
  408. },
  409. func(tx *sql.Tx) (err error) {
  410. sql := `ALTER TABLE entries ADD COLUMN reading_time int not null default 0`
  411. _, err = tx.Exec(sql)
  412. return err
  413. },
  414. func(tx *sql.Tx) (err error) {
  415. sql := `
  416. ALTER TABLE entries ADD COLUMN created_at timestamp with time zone not null default now();
  417. UPDATE entries SET created_at = published_at;
  418. `
  419. _, err = tx.Exec(sql)
  420. return err
  421. },
  422. func(tx *sql.Tx) (err error) {
  423. _, err = tx.Exec(`
  424. ALTER TABLE users
  425. ADD column stylesheet text not null default '',
  426. ADD column google_id text not null default '',
  427. ADD column openid_connect_id text not null default ''
  428. `)
  429. if err != nil {
  430. return err
  431. }
  432. _, err = tx.Exec(`
  433. DECLARE my_cursor CURSOR FOR
  434. SELECT
  435. id,
  436. COALESCE(extra->'custom_css', '') as custom_css,
  437. COALESCE(extra->'google_id', '') as google_id,
  438. COALESCE(extra->'oidc_id', '') as oidc_id
  439. FROM users
  440. FOR UPDATE
  441. `)
  442. if err != nil {
  443. return err
  444. }
  445. defer tx.Exec("CLOSE my_cursor")
  446. for {
  447. var (
  448. userID int64
  449. customStylesheet string
  450. googleID string
  451. oidcID string
  452. )
  453. if err := tx.QueryRow(`FETCH NEXT FROM my_cursor`).Scan(&userID, &customStylesheet, &googleID, &oidcID); err != nil {
  454. if err == sql.ErrNoRows {
  455. break
  456. }
  457. return err
  458. }
  459. _, err := tx.Exec(
  460. `UPDATE
  461. users
  462. SET
  463. stylesheet=$2,
  464. google_id=$3,
  465. openid_connect_id=$4
  466. WHERE
  467. id=$1
  468. `,
  469. userID, customStylesheet, googleID, oidcID)
  470. if err != nil {
  471. return err
  472. }
  473. }
  474. return err
  475. },
  476. func(tx *sql.Tx) (err error) {
  477. if _, err = tx.Exec(`ALTER TABLE users DROP COLUMN extra;`); err != nil {
  478. return err
  479. }
  480. _, err = tx.Exec(`
  481. CREATE UNIQUE INDEX users_google_id_idx ON users(google_id) WHERE google_id <> '';
  482. CREATE UNIQUE INDEX users_openid_connect_id_idx ON users(openid_connect_id) WHERE openid_connect_id <> '';
  483. `)
  484. return err
  485. },
  486. func(tx *sql.Tx) (err error) {
  487. _, err = tx.Exec(`
  488. CREATE INDEX entries_feed_url_idx ON entries(feed_id, url) WHERE length(url) < 2000;
  489. CREATE INDEX entries_user_status_feed_idx ON entries(user_id, status, feed_id);
  490. CREATE INDEX entries_user_status_changed_idx ON entries(user_id, status, changed_at);
  491. `)
  492. return err
  493. },
  494. func(tx *sql.Tx) (err error) {
  495. _, err = tx.Exec(`
  496. CREATE TABLE acme_cache (
  497. key varchar(400) not null primary key,
  498. data bytea not null,
  499. updated_at timestamptz not null
  500. );
  501. `)
  502. return err
  503. },
  504. func(tx *sql.Tx) (err error) {
  505. _, err = tx.Exec(`
  506. ALTER TABLE feeds ADD COLUMN allow_self_signed_certificates boolean not null default false
  507. `)
  508. return err
  509. },
  510. func(tx *sql.Tx) (err error) {
  511. sql := `
  512. CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
  513. ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
  514. `
  515. _, err = tx.Exec(sql)
  516. return err
  517. },
  518. func(tx *sql.Tx) (err error) {
  519. sql := `ALTER TABLE feeds ADD COLUMN cookie text default ''`
  520. _, err = tx.Exec(sql)
  521. return err
  522. },
  523. func(tx *sql.Tx) (err error) {
  524. _, err = tx.Exec(`
  525. ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
  526. `)
  527. return err
  528. },
  529. func(tx *sql.Tx) (err error) {
  530. _, err = tx.Exec(`
  531. ALTER TABLE feeds ADD COLUMN hide_globally boolean not null default false
  532. `)
  533. return err
  534. },
  535. func(tx *sql.Tx) (err error) {
  536. sql := `
  537. ALTER TABLE integrations
  538. ADD COLUMN telegram_bot_enabled bool default 'f',
  539. ADD COLUMN telegram_bot_token text default '',
  540. ADD COLUMN telegram_bot_chat_id text default '';
  541. `
  542. _, err = tx.Exec(sql)
  543. return err
  544. },
  545. func(tx *sql.Tx) (err error) {
  546. sql := `
  547. CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
  548. ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
  549. `
  550. _, err = tx.Exec(sql)
  551. return err
  552. },
  553. func(tx *sql.Tx) (err error) {
  554. sql := `
  555. ALTER TABLE integrations
  556. ADD COLUMN googlereader_enabled bool default 'f',
  557. ADD COLUMN googlereader_username text default '',
  558. ADD COLUMN googlereader_password text default '';
  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 espial_enabled bool default 'f',
  567. ADD COLUMN espial_url text default '',
  568. ADD COLUMN espial_api_key text default '',
  569. ADD COLUMN espial_tags text default 'miniflux';
  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 linkding_enabled bool default 'f',
  578. ADD COLUMN linkding_url text default '',
  579. ADD COLUMN linkding_api_key text default '';
  580. `
  581. _, err = tx.Exec(sql)
  582. return err
  583. },
  584. func(tx *sql.Tx) (err error) {
  585. _, err = tx.Exec(`
  586. ALTER TABLE feeds ADD COLUMN url_rewrite_rules text not null default ''
  587. `)
  588. return err
  589. },
  590. func(tx *sql.Tx) (err error) {
  591. _, err = tx.Exec(`
  592. ALTER TABLE users
  593. ADD COLUMN default_reading_speed int default 265,
  594. ADD COLUMN cjk_reading_speed int default 500;
  595. `)
  596. return
  597. },
  598. func(tx *sql.Tx) (err error) {
  599. _, err = tx.Exec(`
  600. ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
  601. `)
  602. return
  603. },
  604. func(tx *sql.Tx) (err error) {
  605. _, err = tx.Exec(`
  606. ALTER TABLE integrations ADD COLUMN wallabag_only_url bool default 'f';
  607. `)
  608. return
  609. },
  610. func(tx *sql.Tx) (err error) {
  611. _, err = tx.Exec(`
  612. ALTER TABLE users ADD COLUMN categories_sorting_order text not null default 'unread_count';
  613. `)
  614. return
  615. },
  616. func(tx *sql.Tx) (err error) {
  617. sql := `
  618. ALTER TABLE integrations
  619. ADD COLUMN matrix_bot_enabled bool default 'f',
  620. ADD COLUMN matrix_bot_user text default '',
  621. ADD COLUMN matrix_bot_password text default '',
  622. ADD COLUMN matrix_bot_url text default '',
  623. ADD COLUMN matrix_bot_chat_id text default '';
  624. `
  625. _, err = tx.Exec(sql)
  626. return
  627. },
  628. func(tx *sql.Tx) (err error) {
  629. sql := `ALTER TABLE users ADD COLUMN double_tap boolean default 't'`
  630. _, err = tx.Exec(sql)
  631. return err
  632. },
  633. func(tx *sql.Tx) (err error) {
  634. _, err = tx.Exec(`
  635. ALTER TABLE entries ADD COLUMN tags text[] default '{}';
  636. `)
  637. return
  638. },
  639. func(tx *sql.Tx) (err error) {
  640. sql := `
  641. ALTER TABLE users RENAME double_tap TO gesture_nav;
  642. ALTER TABLE users
  643. ALTER COLUMN gesture_nav SET DATA TYPE text using case when gesture_nav = true then 'tap' when gesture_nav = false then 'none' end,
  644. ALTER COLUMN gesture_nav SET default 'tap';
  645. `
  646. _, err = tx.Exec(sql)
  647. return err
  648. },
  649. func(tx *sql.Tx) (err error) {
  650. sql := `
  651. ALTER TABLE integrations ADD COLUMN linkding_tags text default '';
  652. `
  653. _, err = tx.Exec(sql)
  654. return err
  655. },
  656. func(tx *sql.Tx) (err error) {
  657. sql := `
  658. ALTER TABLE feeds ADD COLUMN no_media_player boolean default 'f';
  659. ALTER TABLE enclosures ADD COLUMN media_progression int default 0;
  660. `
  661. _, err = tx.Exec(sql)
  662. return err
  663. },
  664. func(tx *sql.Tx) (err error) {
  665. sql := `
  666. ALTER TABLE integrations ADD COLUMN linkding_mark_as_unread bool default 'f';
  667. `
  668. _, err = tx.Exec(sql)
  669. return err
  670. },
  671. func(tx *sql.Tx) (err error) {
  672. // Delete duplicated rows
  673. sql := `
  674. DELETE FROM enclosures a USING enclosures b
  675. WHERE a.id < b.id
  676. AND a.user_id = b.user_id
  677. AND a.entry_id = b.entry_id
  678. AND a.url = b.url;
  679. `
  680. _, err = tx.Exec(sql)
  681. if err != nil {
  682. return err
  683. }
  684. // Remove previous index
  685. _, err = tx.Exec(`DROP INDEX enclosures_user_entry_url_idx`)
  686. if err != nil {
  687. return err
  688. }
  689. // Create unique index
  690. _, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
  691. if err != nil {
  692. return err
  693. }
  694. return nil
  695. },
  696. func(tx *sql.Tx) (err error) {
  697. sql := `ALTER TABLE users ADD COLUMN mark_read_on_view boolean default 't'`
  698. _, err = tx.Exec(sql)
  699. return err
  700. },
  701. func(tx *sql.Tx) (err error) {
  702. sql := `
  703. ALTER TABLE integrations
  704. ADD COLUMN notion_enabled bool default 'f',
  705. ADD COLUMN notion_token text default '',
  706. ADD COLUMN notion_page_id text default '';
  707. `
  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 readwise_enabled bool default 'f',
  715. ADD COLUMN readwise_api_key text default '';
  716. `
  717. _, err = tx.Exec(sql)
  718. return err
  719. },
  720. func(tx *sql.Tx) (err error) {
  721. sql := `
  722. ALTER TABLE integrations
  723. ADD COLUMN apprise_enabled bool default 'f',
  724. ADD COLUMN apprise_url text default '',
  725. ADD COLUMN apprise_services_url 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 shiori_enabled bool default 'f',
  734. ADD COLUMN shiori_url text default '',
  735. ADD COLUMN shiori_username text default '',
  736. ADD COLUMN shiori_password 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 shaarli_enabled bool default 'f',
  745. ADD COLUMN shaarli_url text default '',
  746. ADD COLUMN shaarli_api_secret text default '';
  747. `
  748. _, err = tx.Exec(sql)
  749. return err
  750. },
  751. func(tx *sql.Tx) (err error) {
  752. _, err = tx.Exec(`
  753. ALTER TABLE feeds ADD COLUMN apprise_service_urls text default '';
  754. `)
  755. return err
  756. },
  757. func(tx *sql.Tx) (err error) {
  758. sql := `
  759. ALTER TABLE integrations
  760. ADD COLUMN webhook_enabled bool default 'f',
  761. ADD COLUMN webhook_url text default '',
  762. ADD COLUMN webhook_secret text default '';
  763. `
  764. _, err = tx.Exec(sql)
  765. return err
  766. },
  767. func(tx *sql.Tx) (err error) {
  768. sql := `
  769. ALTER TABLE integrations
  770. ADD COLUMN telegram_bot_topic_id int,
  771. ADD COLUMN telegram_bot_disable_web_page_preview bool default 'f',
  772. ADD COLUMN telegram_bot_disable_notification bool default 'f';
  773. `
  774. _, err = tx.Exec(sql)
  775. return err
  776. },
  777. func(tx *sql.Tx) (err error) {
  778. sql := `
  779. ALTER TABLE integrations ADD COLUMN telegram_bot_disable_buttons bool default 'f';
  780. `
  781. _, err = tx.Exec(sql)
  782. return err
  783. },
  784. func(tx *sql.Tx) (err error) {
  785. sql := `
  786. -- Speed up has_enclosure
  787. CREATE INDEX enclosures_entry_id_idx ON enclosures(entry_id);
  788. -- Speed up unread page
  789. CREATE INDEX entries_user_status_published_idx ON entries(user_id, status, published_at);
  790. CREATE INDEX entries_user_status_created_idx ON entries(user_id, status, created_at);
  791. CREATE INDEX feeds_feed_id_hide_globally_idx ON feeds(id, hide_globally);
  792. -- Speed up history page
  793. CREATE INDEX entries_user_status_changed_published_idx ON entries(user_id, status, changed_at, published_at);
  794. `
  795. _, err = tx.Exec(sql)
  796. return err
  797. },
  798. func(tx *sql.Tx) (err error) {
  799. sql := `
  800. ALTER TABLE integrations
  801. ADD COLUMN rssbridge_enabled bool default 'f',
  802. ADD COLUMN rssbridge_url text default '';
  803. `
  804. _, err = tx.Exec(sql)
  805. return
  806. },
  807. func(tx *sql.Tx) (err error) {
  808. _, err = tx.Exec(`
  809. CREATE TABLE webauthn_credentials (
  810. handle bytea primary key,
  811. cred_id bytea unique not null,
  812. user_id int references users(id) on delete cascade not null,
  813. public_key bytea not null,
  814. attestation_type varchar(255) not null,
  815. aaguid bytea,
  816. sign_count bigint,
  817. clone_warning bool,
  818. name text,
  819. added_on timestamp with time zone default now(),
  820. last_seen_on timestamp with time zone default now()
  821. );
  822. `)
  823. return
  824. },
  825. func(tx *sql.Tx) (err error) {
  826. sql := `
  827. ALTER TABLE integrations
  828. ADD COLUMN omnivore_enabled bool default 'f',
  829. ADD COLUMN omnivore_api_key text default '',
  830. ADD COLUMN omnivore_url text default '';
  831. `
  832. _, err = tx.Exec(sql)
  833. return
  834. },
  835. func(tx *sql.Tx) (err error) {
  836. sql := `
  837. ALTER TABLE integrations
  838. ADD COLUMN linkace_enabled bool default 'f',
  839. ADD COLUMN linkace_url text default '',
  840. ADD COLUMN linkace_api_key text default '',
  841. ADD COLUMN linkace_tags text default '',
  842. ADD COLUMN linkace_is_private bool default 't',
  843. ADD COLUMN linkace_check_disabled bool default 't';
  844. `
  845. _, err = tx.Exec(sql)
  846. return err
  847. },
  848. func(tx *sql.Tx) (err error) {
  849. sql := `
  850. ALTER TABLE integrations
  851. ADD COLUMN linkwarden_enabled bool default 'f',
  852. ADD COLUMN linkwarden_url text default '',
  853. ADD COLUMN linkwarden_api_key text default '';
  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 readeck_enabled bool default 'f',
  862. ADD COLUMN readeck_only_url bool default 'f',
  863. ADD COLUMN readeck_url text default '',
  864. ADD COLUMN readeck_api_key text default '',
  865. ADD COLUMN readeck_labels text default '';
  866. `
  867. _, err = tx.Exec(sql)
  868. return err
  869. },
  870. func(tx *sql.Tx) (err error) {
  871. sql := `ALTER TABLE feeds ADD COLUMN disable_http2 bool default 'f'`
  872. _, err = tx.Exec(sql)
  873. return err
  874. },
  875. func(tx *sql.Tx) (err error) {
  876. sql := `ALTER TABLE users ADD COLUMN media_playback_rate numeric default 1;`
  877. _, err = tx.Exec(sql)
  878. return err
  879. },
  880. func(tx *sql.Tx) (err error) {
  881. // the WHERE part speed-up the request a lot
  882. sql := `UPDATE entries SET tags = array_remove(tags, '') WHERE '' = ANY(tags);`
  883. _, err = tx.Exec(sql)
  884. return err
  885. },
  886. func(tx *sql.Tx) (err error) {
  887. // Entry URLs can exceeds btree maximum size
  888. // Checking entry existence is now using entries_feed_id_status_hash_idx index
  889. _, err = tx.Exec(`DROP INDEX entries_feed_url_idx`)
  890. return err
  891. },
  892. func(tx *sql.Tx) (err error) {
  893. sql := `
  894. ALTER TABLE integrations
  895. ADD COLUMN raindrop_enabled bool default 'f',
  896. ADD COLUMN raindrop_token text default '',
  897. ADD COLUMN raindrop_collection_id text default '',
  898. ADD COLUMN raindrop_tags text default '';
  899. `
  900. _, err = tx.Exec(sql)
  901. return err
  902. },
  903. func(tx *sql.Tx) (err error) {
  904. sql := `ALTER TABLE feeds ADD COLUMN description text default ''`
  905. _, err = tx.Exec(sql)
  906. return err
  907. },
  908. func(tx *sql.Tx) (err error) {
  909. sql := `
  910. ALTER TABLE users
  911. ADD COLUMN block_filter_entry_rules text not null default '',
  912. ADD COLUMN keep_filter_entry_rules text not null default ''
  913. `
  914. _, err = tx.Exec(sql)
  915. return err
  916. },
  917. func(tx *sql.Tx) (err error) {
  918. sql := `
  919. ALTER TABLE integrations
  920. ADD COLUMN betula_url text default '',
  921. ADD COLUMN betula_token text default '',
  922. ADD COLUMN betula_enabled bool default 'f';
  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 ntfy_enabled bool default 'f',
  931. ADD COLUMN ntfy_url text default '',
  932. ADD COLUMN ntfy_topic text default '',
  933. ADD COLUMN ntfy_api_token text default '',
  934. ADD COLUMN ntfy_username text default '',
  935. ADD COLUMN ntfy_password text default '',
  936. ADD COLUMN ntfy_icon_url text default '';
  937. ALTER TABLE feeds
  938. ADD COLUMN ntfy_enabled bool default 'f',
  939. ADD COLUMN ntfy_priority int default '3';
  940. `
  941. _, err = tx.Exec(sql)
  942. return err
  943. },
  944. func(tx *sql.Tx) (err error) {
  945. sql := `ALTER TABLE users ADD COLUMN mark_read_on_media_player_completion bool default 'f';`
  946. _, err = tx.Exec(sql)
  947. return err
  948. },
  949. func(tx *sql.Tx) (err error) {
  950. sql := `ALTER TABLE users ADD COLUMN custom_js text not null default '';`
  951. _, err = tx.Exec(sql)
  952. return err
  953. },
  954. func(tx *sql.Tx) (err error) {
  955. sql := `ALTER TABLE users ADD COLUMN external_font_hosts text not null default '';`
  956. _, err = tx.Exec(sql)
  957. return err
  958. },
  959. func(tx *sql.Tx) (err error) {
  960. sql := `
  961. ALTER TABLE integrations
  962. ADD COLUMN cubox_enabled bool default 'f',
  963. ADD COLUMN cubox_api_link text default '';
  964. `
  965. _, err = tx.Exec(sql)
  966. return err
  967. },
  968. func(tx *sql.Tx) (err error) {
  969. sql := `
  970. ALTER TABLE integrations
  971. ADD COLUMN discord_enabled bool default 'f',
  972. ADD COLUMN discord_webhook_link text default '';
  973. `
  974. _, err = tx.Exec(sql)
  975. return err
  976. },
  977. func(tx *sql.Tx) (err error) {
  978. sql := `ALTER TABLE integrations ADD COLUMN ntfy_internal_links bool default 'f';`
  979. _, err = tx.Exec(sql)
  980. return err
  981. },
  982. func(tx *sql.Tx) (err error) {
  983. sql := `
  984. ALTER TABLE integrations
  985. ADD COLUMN slack_enabled bool default 'f',
  986. ADD COLUMN slack_webhook_link text default '';
  987. `
  988. _, err = tx.Exec(sql)
  989. return err
  990. },
  991. func(tx *sql.Tx) (err error) {
  992. _, err = tx.Exec(`ALTER TABLE feeds ADD COLUMN webhook_url text default '';`)
  993. return err
  994. },
  995. func(tx *sql.Tx) (err error) {
  996. sql := `
  997. ALTER TABLE integrations
  998. ADD COLUMN pushover_enabled bool default 'f',
  999. ADD COLUMN pushover_user text default '',
  1000. ADD COLUMN pushover_token text default '',
  1001. ADD COLUMN pushover_device text default '',
  1002. ADD COLUMN pushover_prefix text default '';
  1003. ALTER TABLE feeds
  1004. ADD COLUMN pushover_enabled bool default 'f',
  1005. ADD COLUMN pushover_priority int default '0';
  1006. `
  1007. _, err = tx.Exec(sql)
  1008. return err
  1009. },
  1010. func(tx *sql.Tx) (err error) {
  1011. sql := `
  1012. ALTER TABLE feeds ADD COLUMN ntfy_topic text default '';
  1013. `
  1014. _, err = tx.Exec(sql)
  1015. return err
  1016. },
  1017. func(tx *sql.Tx) (err error) {
  1018. sql := `
  1019. ALTER TABLE icons ADD COLUMN external_id text default '';
  1020. CREATE UNIQUE INDEX icons_external_id_idx ON icons USING btree(external_id) WHERE external_id <> '';
  1021. `
  1022. _, err = tx.Exec(sql)
  1023. return err
  1024. },
  1025. func(tx *sql.Tx) (err error) {
  1026. _, err = tx.Exec(`
  1027. DECLARE id_cursor CURSOR FOR
  1028. SELECT
  1029. id
  1030. FROM icons
  1031. WHERE external_id = ''
  1032. FOR UPDATE`)
  1033. if err != nil {
  1034. return err
  1035. }
  1036. defer tx.Exec("CLOSE id_cursor")
  1037. for {
  1038. var id int64
  1039. if err := tx.QueryRow(`FETCH NEXT FROM id_cursor`).Scan(&id); err != nil {
  1040. if err == sql.ErrNoRows {
  1041. break
  1042. }
  1043. return err
  1044. }
  1045. _, err = tx.Exec(
  1046. `
  1047. UPDATE icons SET external_id = $1 WHERE id = $2
  1048. `,
  1049. crypto.GenerateRandomStringHex(20), id)
  1050. if err != nil {
  1051. return err
  1052. }
  1053. }
  1054. return nil
  1055. },
  1056. func(tx *sql.Tx) (err error) {
  1057. _, err = tx.Exec(`ALTER TABLE feeds ADD COLUMN proxy_url text default ''`)
  1058. return err
  1059. },
  1060. func(tx *sql.Tx) (err error) {
  1061. sql := `
  1062. ALTER TABLE integrations ADD COLUMN rssbridge_token text default '';
  1063. `
  1064. _, err = tx.Exec(sql)
  1065. return err
  1066. },
  1067. func(tx *sql.Tx) (err error) {
  1068. _, err = tx.Exec(`ALTER TABLE users ADD COLUMN always_open_external_links bool default 'f'`)
  1069. return err
  1070. },
  1071. func(tx *sql.Tx) (err error) {
  1072. sql := `
  1073. ALTER TABLE integrations
  1074. ADD COLUMN karakeep_enabled bool default 'f',
  1075. ADD COLUMN karakeep_api_key text default '',
  1076. ADD COLUMN karakeep_url text default '';
  1077. `
  1078. _, err = tx.Exec(sql)
  1079. return err
  1080. },
  1081. func(tx *sql.Tx) (err error) {
  1082. _, err = tx.Exec(`ALTER TABLE users ADD COLUMN open_external_links_in_new_tab bool default 't'`)
  1083. return err
  1084. },
  1085. func(tx *sql.Tx) (err error) {
  1086. sql := `
  1087. ALTER TABLE integrations
  1088. DROP COLUMN pocket_enabled,
  1089. DROP COLUMN pocket_access_token,
  1090. DROP COLUMN pocket_consumer_key;
  1091. `
  1092. _, err = tx.Exec(sql)
  1093. return err
  1094. },
  1095. func(tx *sql.Tx) (err error) {
  1096. sql := `
  1097. ALTER TABLE feeds
  1098. ADD COLUMN block_filter_entry_rules text not null default '',
  1099. ADD COLUMN keep_filter_entry_rules text not null default ''
  1100. `
  1101. _, err = tx.Exec(sql)
  1102. return err
  1103. },
  1104. }