-- SPDX-License-Identifier: GPL-3.0-or-later

-- SQLite tables definitions for Haketilo proxy.
--
-- This file is part of Hydrilla&Haketilo.
--
-- Copyright (C) 2022 Wojtek Kosior
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--
--
-- I, Wojtek Kosior, thereby promise not to sue for violation of this
-- file's license. Although I request that you do not make use this code
-- in a proprietary program, I am not going to enforce this in court.

BEGIN TRANSACTION;

CREATE TABLE general(
        haketilo_version      VARCHAR NOT NULL,
        default_allow_scripts BOOLEAN NOT NULL,
	repo_refresh_seconds  INTEGER NOT NULL,
	-- "mapping_use_mode" determines whether current mode is AUTO,
	-- WHEN_ENABLED or QUESTION.
	mapping_use_mode      CHAR(1) NOT NULL,

        CHECK (rowid = 1),
	CHECK (mapping_use_mode IN ('A', 'W', 'Q')),
        CHECK (haketilo_version = '3.0b1')
);

INSERT INTO general(
        rowid,
        haketilo_version,
        default_allow_scripts,
	repo_refresh_seconds,
	mapping_use_mode
)
VALUES(
        1,
        '3.0b1',
        FALSE,
	24 * 60 * 60,
	'Q'
);

CREATE TABLE rules(
        rule_id       INTEGER PRIMARY KEY,

        pattern       VARCHAR NOT NULL,
        allow_scripts BOOLEAN NOT NULL,

	UNIQUE (pattern)
);

CREATE TABLE repos(
        repo_id             INTEGER PRIMARY KEY,

        name                VARCHAR NOT NULL,
        url                 VARCHAR NOT NULL,
	deleted             BOOLEAN NOT NULL,
	next_iteration      INTEGER NOT NULL,
	active_iteration_id INTEGER NULL,
	last_refreshed      INTEGER NULL,

        UNIQUE (name),
	-- The local semi-repo used for packages installed offline is always
	-- marked as deleted. Semi-repo's name is chosen as an empty string so
	-- as not to collide with other names (which are required to be
	-- non-empty).
	CHECK  ((repo_id = 1) = (name = '')),
	CHECK  (repo_id != 1 OR deleted = TRUE),
	-- All deleted repos shall have "url" set to an empty string. All other
	-- repos shall have a valid http(s) URL.
	CHECK  (deleted = (url = '')),
	-- Only non-deleted repos are allowed to have an active iteration.
	CHECK  (NOT deleted OR active_iteration_id IS NULL),
	-- Only non-deleted repos are allowed to have last refresh timestamp.
	CHECK  (NOT deleted OR last_refreshed IS NULL),

	FOREIGN KEY (active_iteration_id)
		REFERENCES repo_iterations(repo_iteration_id)
		ON DELETE SET NULL
);

INSERT INTO repos(repo_id, name, url, deleted, next_iteration)
VALUES(1, '', '', TRUE, 1);

CREATE TABLE repo_iterations(
        repo_iteration_id INTEGER PRIMARY KEY,

	repo_id           INTEGER NOT NULL,
	iteration         INTEGER NOT NULL,

	UNIQUE (repo_id, iteration),

	FOREIGN KEY (repo_id)
		 REFERENCES repos (repo_id)
);

CREATE VIEW orphan_iterations
AS
SELECT
        ri.repo_iteration_id,
	ri.repo_id,
	ri.iteration
FROM
	     repo_iterations AS ri
        JOIN repos           AS r  USING (repo_id)
WHERE
	COALESCE(r.active_iteration_id != ri.repo_iteration_id, TRUE);

CREATE TABLE items(
        item_id         INTEGER PRIMARY KEY,

        -- "type" determines whether it's resource or mapping.
        type            CHAR(1) NOT NULL,
        identifier      VARCHAR NOT NULL,

	UNIQUE (type, identifier),
        CHECK  (type IN ('R', 'M'))
);

CREATE TABLE mapping_statuses(
        -- The item with this id shall be a mapping ("type" = 'M'). For each
	-- mapping row in "items" there must be an accompanying row in this
	-- table.
        item_id            INTEGER PRIMARY KEY,

        -- "enabled" determines whether mapping's status is ENABLED,
	-- DISABLED or NO_MARK.
        enabled            CHAR(1) NOT NULL,
	-- "frozen" determines whether an enabled mapping is to be kept in its
	-- EXACT_VERSION, is to be updated only with versions from the same
	-- REPOSITORY or is NOT_FROZEN at all.
	frozen             CHAR(1) NULL,
	-- The last 2 fields defined below shall be updated when dependency tree
	-- is recomputed.
	-- When "required" is TRUE, the mapping is assumed to either be enabled
	-- or be (directly or indirectly) required by another mapping which is
	-- enabled (i.e. has "enabled" set to 'E').
	required           BOOLEAN NOT NULL,
	-- Only one version of a mapping is allowed to be active at any time.
	-- "active_version_id" indicates which version it is. Only a mapping
	-- version referenced by "active_version_id" is allowed to have rows
	-- in the "payloads" table reference it.
	active_version_id  INTEGER NULL,

	CHECK (enabled IN ('E', 'D', 'N')),
	CHECK ((frozen IS NULL) = (enabled != 'E')),
	CHECK (frozen IS NULL OR frozen in ('E', 'R', 'N')),
	CHECK (enabled != 'E' OR required),
	CHECK (enabled != 'D' OR NOT required),
	CHECK (not required OR active_version_id IS NOT NULL),
	CHECK (enabled != 'D' OR active_version_id IS NULL),

	FOREIGN KEY (item_id)
                REFERENCES items (item_id)
		ON DELETE CASCADE,
	FOREIGN KEY (active_version_id, item_id)
                REFERENCES item_versions (item_version_id, item_id)
		ON DELETE SET NULL
);

CREATE TABLE item_versions(
        item_version_id     INTEGER PRIMARY KEY,

	item_id             INTEGER NOT NULL,
        version             VARCHAR NOT NULL,
	repo_iteration_id   INTEGER NOT NULL,
	definition          BLOB    NOT NULL,

        UNIQUE (item_id, version, repo_iteration_id),
	-- Constraint below needed to allow foreign key from "mapping_statuses".
	UNIQUE (item_version_id, item_id),

	FOREIGN KEY (item_id)
                REFERENCES items (item_id),
	FOREIGN KEY (repo_iteration_id)
                REFERENCES repo_iterations (repo_iteration_id)
);

CREATE VIEW repo_display_infos
AS
SELECT
	r.repo_id, r.name, r.url, r.deleted, r.last_refreshed,
	COALESCE(SUM(i.type = 'R'), 0) AS resource_count,
	COALESCE(SUM(i.type = 'M'), 0) AS mapping_count
FROM
		  repos           AS r
	LEFT JOIN repo_iterations AS ir USING (repo_id)
	LEFT JOIN item_versions   AS iv USING (repo_iteration_id)
	LEFT JOIN items           AS i  USING (item_id)
GROUP BY
        r.repo_id, r.name, r.url, r.deleted, r.last_refreshed;

-- Every time a repository gets refreshed or a mapping gets enabled/disabled,
-- the dependency tree is recomputed. In the process the "payloads" table gets
-- cleare and repopulated together with the "resolved_depended_resources" that
-- depends on it.
CREATE TABLE payloads(
        payload_id          INTEGER PRIMARY KEY,

        mapping_item_id     INTEGER NOT NULL,
        pattern             VARCHAR NOT NULL,
	-- What privileges should be granted on pages where this
	-- resource/mapping is used.
	eval_allowed        BOOLEAN NOT NULL,
        cors_bypass_allowed BOOLEAN NOT NULL,

        UNIQUE (mapping_item_id, pattern),

	FOREIGN KEY (mapping_item_id)
                REFERENCES item_versions (item_version_id)
		ON DELETE CASCADE
);

CREATE VIEW mapping_display_infos
AS
SELECT
        CASE WHEN
                ms.enabled = 'N' AND COUNT(p.payload_id) > 0
        THEN
                'A' -- AUTO_ENABLED mapping
        ELSE
                ms.enabled
        END AS enabled,
        iv.item_version_id, iv.definition,
        r.name AS repo,
        ri.iteration AS repo_iteration,
        COALESCE(
                r.active_iteration_id != ri.repo_iteration_id,
                TRUE
        ) AND r.repo_id != 1 AS is_orphan
FROM
                  item_versions    AS iv
        LEFT JOIN payloads         AS p
                ON iv.item_version_id = p.mapping_item_id
        JOIN      items            AS i
                USING (item_id)
        JOIN      mapping_statuses AS ms
                USING (item_id)
        JOIN      repo_iterations  AS ri
                USING (repo_iteration_id)
        JOIN      repos            AS r
                USING (repo_id)
WHERE
        i.type = 'M'
GROUP BY
        ms.enabled,
        iv.item_version_id, iv.definition,
        r.name, ri.iteration;

CREATE TABLE resolved_depended_resources(
        payload_id       INTEGER,
        resource_item_id INTEGER,

        -- "idx" determines the ordering of resources.
        idx              INTEGER,

        PRIMARY KEY (payload_id, resource_item_id),

        FOREIGN KEY (payload_id)
                REFERENCES payloads (payload_id)
		ON DELETE CASCADE,
        FOREIGN KEY (resource_item_id)
                REFERENCES item_versions (item_version_id)
		ON DELETE CASCADE
) WITHOUT ROWID;

CREATE TABLE files(
        file_id INTEGER PRIMARY KEY,

        sha256  CHAR(64) NOT NULL,
	data    BLOB     NULL,

	UNIQUE (sha256)
);

CREATE TABLE file_uses(
        file_use_id     INTEGER PRIMARY KEY,

	item_version_id INTEGER NOT NULL,
        file_id         INTEGER NOT NULL,
        name            VARCHAR NOT NULL,
        -- "type" determines whether it's license file or web resource.
        type            CHAR(1) NOT NULL,
	mime_type       VARCHAR NOT NULL,
        -- "idx" determines the ordering of item's files of given type.
        idx             INTEGER NOT NULL,

        CHECK (type IN ('L', 'W')),
	UNIQUE(item_version_id, type, idx),
	UNIQUE(item_version_id, type, name),

        FOREIGN KEY (item_version_id)
                REFERENCES item_versions(item_version_id)
		ON DELETE CASCADE,
        FOREIGN KEY (file_id)
                REFERENCES files(file_id)
);

COMMIT TRANSACTION;
