migrations.go 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  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. func(tx *sql.Tx) (err error) {
  1105. sql := `
  1106. CREATE TYPE linktaco_link_visibility AS ENUM (
  1107. 'PUBLIC',
  1108. 'PRIVATE'
  1109. );
  1110. ALTER TABLE integrations
  1111. ADD COLUMN linktaco_enabled bool default 'f',
  1112. ADD COLUMN linktaco_api_token text default '',
  1113. ADD COLUMN linktaco_org_slug text default '',
  1114. ADD COLUMN linktaco_tags text default '',
  1115. ADD COLUMN linktaco_visibility linktaco_link_visibility default 'PUBLIC';
  1116. `
  1117. _, err = tx.Exec(sql)
  1118. return err
  1119. },
  1120. }