| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625 |
- // Copyright 2020 Frédéric Guillot. All rights reserved.
- // Use of this source code is governed by the Apache 2.0
- // license that can be found in the LICENSE file.
- package database // import "miniflux.app/database"
- import (
- "database/sql"
- )
- var schemaVersion = len(migrations)
- // Order is important. Add new migrations at the end of the list.
- var migrations = []func(tx *sql.Tx) error{
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TABLE schema_version (
- version text not null
- );
- CREATE TABLE users (
- id serial not null,
- username text not null unique,
- password text,
- is_admin bool default 'f',
- language text default 'en_US',
- timezone text default 'UTC',
- theme text default 'default',
- last_login_at timestamp with time zone,
- primary key (id)
- );
- CREATE TABLE sessions (
- id serial not null,
- user_id int not null,
- token text not null unique,
- created_at timestamp with time zone default now(),
- user_agent text,
- ip text,
- primary key (id),
- unique (user_id, token),
- foreign key (user_id) references users(id) on delete cascade
- );
- CREATE TABLE categories (
- id serial not null,
- user_id int not null,
- title text not null,
- primary key (id),
- unique (user_id, title),
- foreign key (user_id) references users(id) on delete cascade
- );
- CREATE TABLE feeds (
- id bigserial not null,
- user_id int not null,
- category_id int not null,
- title text not null,
- feed_url text not null,
- site_url text not null,
- checked_at timestamp with time zone default now(),
- etag_header text default '',
- last_modified_header text default '',
- parsing_error_msg text default '',
- parsing_error_count int default 0,
- primary key (id),
- unique (user_id, feed_url),
- foreign key (user_id) references users(id) on delete cascade,
- foreign key (category_id) references categories(id) on delete cascade
- );
- CREATE TYPE entry_status as enum('unread', 'read', 'removed');
- CREATE TABLE entries (
- id bigserial not null,
- user_id int not null,
- feed_id bigint not null,
- hash text not null,
- published_at timestamp with time zone not null,
- title text not null,
- url text not null,
- author text,
- content text,
- status entry_status default 'unread',
- primary key (id),
- unique (feed_id, hash),
- foreign key (user_id) references users(id) on delete cascade,
- foreign key (feed_id) references feeds(id) on delete cascade
- );
- CREATE INDEX entries_feed_idx on entries using btree(feed_id);
- CREATE TABLE enclosures (
- id bigserial not null,
- user_id int not null,
- entry_id bigint not null,
- url text not null,
- size int default 0,
- mime_type text default '',
- primary key (id),
- foreign key (user_id) references users(id) on delete cascade,
- foreign key (entry_id) references entries(id) on delete cascade
- );
- CREATE TABLE icons (
- id bigserial not null,
- hash text not null unique,
- mime_type text not null,
- content bytea not null,
- primary key (id)
- );
- CREATE TABLE feed_icons (
- feed_id bigint not null,
- icon_id bigint not null,
- primary key(feed_id, icon_id),
- foreign key (feed_id) references feeds(id) on delete cascade,
- foreign key (icon_id) references icons(id) on delete cascade
- );
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE EXTENSION IF NOT EXISTS hstore;
- ALTER TABLE users ADD COLUMN extra hstore;
- CREATE INDEX users_extra_idx ON users using gin(extra);
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TABLE tokens (
- id text not null,
- value text not null,
- created_at timestamp with time zone not null default now(),
- primary key(id, value)
- );
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TYPE entry_sorting_direction AS enum('asc', 'desc');
- ALTER TABLE users ADD COLUMN entry_direction entry_sorting_direction default 'asc';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TABLE integrations (
- user_id int not null,
- pinboard_enabled bool default 'f',
- pinboard_token text default '',
- pinboard_tags text default 'miniflux',
- pinboard_mark_as_unread bool default 'f',
- instapaper_enabled bool default 'f',
- instapaper_username text default '',
- instapaper_password text default '',
- fever_enabled bool default 'f',
- fever_username text default '',
- fever_password text default '',
- fever_token text default '',
- primary key(user_id)
- );
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN scraper_rules text default ''`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN rewrite_rules text default ''`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN crawler boolean default 'f'`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE sessions rename to user_sessions`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- DROP TABLE tokens;
- CREATE TABLE sessions (
- id text not null,
- data jsonb not null,
- created_at timestamp with time zone not null default now(),
- primary key(id)
- );
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN wallabag_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN wallabag_url text default '';
- ALTER TABLE integrations ADD COLUMN wallabag_client_id text default '';
- ALTER TABLE integrations ADD COLUMN wallabag_client_secret text default '';
- ALTER TABLE integrations ADD COLUMN wallabag_username text default '';
- ALTER TABLE integrations ADD COLUMN wallabag_password text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE entries ADD COLUMN starred bool default 'f'`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE INDEX entries_user_status_idx ON entries(user_id, status);
- CREATE INDEX feeds_user_category_idx ON feeds(user_id, category_id);
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN nunux_keeper_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN nunux_keeper_url text default '';
- ALTER TABLE integrations ADD COLUMN nunux_keeper_api_key text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE enclosures ALTER COLUMN size SET DATA TYPE bigint`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE entries ADD COLUMN comments_url text default ''`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN pocket_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN pocket_access_token text default '';
- ALTER TABLE integrations ADD COLUMN pocket_consumer_key text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE user_sessions ALTER COLUMN ip SET DATA TYPE inet using ip::inet;
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE feeds ADD COLUMN username text default '';
- ALTER TABLE feeds ADD COLUMN password text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE entries ADD COLUMN document_vectors tsvector;
- UPDATE entries SET document_vectors = to_tsvector(substring(title || ' ' || coalesce(content, '') for 1000000));
- CREATE INDEX document_vectors_idx ON entries USING gin(document_vectors);
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN user_agent text default ''`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- UPDATE
- entries
- SET
- document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B')
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE users ADD COLUMN keyboard_shortcuts boolean default 't'`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN disabled boolean default 'f';`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE users ALTER COLUMN theme SET DEFAULT 'light_serif';
- UPDATE users SET theme='light_serif' WHERE theme='default';
- UPDATE users SET theme='light_sans_serif' WHERE theme='sansserif';
- UPDATE users SET theme='dark_serif' WHERE theme='black';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE entries ADD COLUMN changed_at timestamp with time zone;
- UPDATE entries SET changed_at = published_at;
- ALTER TABLE entries ALTER COLUMN changed_at SET not null;
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TABLE api_keys (
- id serial not null,
- user_id int not null references users(id) on delete cascade,
- token text not null unique,
- description text not null,
- last_used_at timestamp with time zone,
- created_at timestamp with time zone default now(),
- primary key(id),
- unique (user_id, description)
- );
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE entries ADD COLUMN share_code text not null default '';
- CREATE UNIQUE INDEX entries_share_code_idx ON entries USING btree(share_code) WHERE share_code <> '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `CREATE INDEX enclosures_user_entry_url_idx ON enclosures(user_id, entry_id, md5(url))`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE feeds ADD COLUMN next_check_at timestamp with time zone default now();
- CREATE INDEX entries_user_feed_idx ON entries (user_id, feed_id);
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN ignore_http_cache bool default false`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE users ADD COLUMN entries_per_page int default 100`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE users ADD COLUMN show_reading_time boolean default 't'`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `CREATE INDEX entries_id_user_status_idx ON entries USING btree (id, user_id, status)`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN fetch_via_proxy bool default false`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `CREATE INDEX entries_feed_id_status_hash_idx ON entries USING btree (feed_id, status, hash)`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `CREATE INDEX entries_user_id_status_starred_idx ON entries (user_id, status, starred)`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE users ADD COLUMN entry_swipe boolean default 't'`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE integrations DROP COLUMN fever_password`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE feeds
- ADD COLUMN blocklist_rules text not null default '',
- ADD COLUMN keeplist_rules text not null default ''
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE entries ADD COLUMN reading_time int not null default 0`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE entries ADD COLUMN created_at timestamp with time zone not null default now();
- UPDATE entries SET created_at = published_at;
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE users
- ADD column stylesheet text not null default '',
- ADD column google_id text not null default '',
- ADD column openid_connect_id text not null default ''
- `)
- if err != nil {
- return err
- }
- _, err = tx.Exec(`
- DECLARE my_cursor CURSOR FOR
- SELECT
- id,
- COALESCE(extra->'custom_css', '') as custom_css,
- COALESCE(extra->'google_id', '') as google_id,
- COALESCE(extra->'oidc_id', '') as oidc_id
- FROM users
- FOR UPDATE
- `)
- if err != nil {
- return err
- }
- defer tx.Exec("CLOSE my_cursor")
- for {
- var (
- userID int64
- customStylesheet string
- googleID string
- oidcID string
- )
- if err := tx.QueryRow(`FETCH NEXT FROM my_cursor`).Scan(&userID, &customStylesheet, &googleID, &oidcID); err != nil {
- if err == sql.ErrNoRows {
- break
- }
- return err
- }
- _, err := tx.Exec(
- `UPDATE
- users
- SET
- stylesheet=$2,
- google_id=$3,
- openid_connect_id=$4
- WHERE
- id=$1
- `,
- userID, customStylesheet, googleID, oidcID)
- if err != nil {
- return err
- }
- }
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE users DROP COLUMN extra;
- CREATE UNIQUE INDEX users_google_id_idx ON users(google_id) WHERE google_id <> '';
- CREATE UNIQUE INDEX users_openid_connect_id_idx ON users(openid_connect_id) WHERE openid_connect_id <> '';
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- CREATE INDEX entries_feed_url_idx ON entries(feed_id, url);
- CREATE INDEX entries_user_status_feed_idx ON entries(user_id, status, feed_id);
- CREATE INDEX entries_user_status_changed_idx ON entries(user_id, status, changed_at);
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- CREATE TABLE acme_cache (
- key varchar(400) not null primary key,
- data bytea not null,
- updated_at timestamptz not null
- );
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE feeds ADD COLUMN allow_self_signed_certificates boolean not null default false
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
- ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `ALTER TABLE feeds ADD COLUMN cookie text default ''`
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE feeds ADD COLUMN hide_globally boolean not null default false
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN telegram_bot_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN telegram_bot_token text default '';
- ALTER TABLE integrations ADD COLUMN telegram_bot_chat_id text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
- ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN googlereader_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN googlereader_username text default '';
- ALTER TABLE integrations ADD COLUMN googlereader_password text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN espial_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN espial_url text default '';
- ALTER TABLE integrations ADD COLUMN espial_api_key text default '';
- ALTER TABLE integrations ADD COLUMN espial_tags text default 'miniflux';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- sql := `
- ALTER TABLE integrations ADD COLUMN linkding_enabled bool default 'f';
- ALTER TABLE integrations ADD COLUMN linkding_url text default '';
- ALTER TABLE integrations ADD COLUMN linkding_api_key text default '';
- `
- _, err = tx.Exec(sql)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE feeds ADD COLUMN url_rewrite_rules text not null default ''
- `)
- return err
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE users ADD COLUMN default_reading_speed int default 265;
- ALTER TABLE users ADD COLUMN cjk_reading_speed int default 500;
- `)
- return
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
- `)
- return
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE integrations ADD COLUMN wallabag_only_url bool default 'f';
- `)
- return
- },
- func(tx *sql.Tx) (err error) {
- _, err = tx.Exec(`
- ALTER TABLE users ADD COLUMN categories_sort_order text not null default 'unread_count';
- `)
- return
- },
- }
|