migrations.go 32 KB

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