migrations.go 32 KB

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