-- PostgreSQL 17 schema for Amnezia VPN Share Panel (Go)

CREATE TABLE IF NOT EXISTS users (
    id              SERIAL PRIMARY KEY,
    username        VARCHAR(64) NOT NULL UNIQUE,
    password_hash   VARCHAR(255) NOT NULL,
    role            TEXT NOT NULL DEFAULT 'admin' CHECK (role IN ('admin')),
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS site_settings (
    skey        VARCHAR(64) PRIMARY KEY,
    svalue      TEXT,
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS share_links (
    id                      SERIAL PRIMARY KEY,
    token                   CHAR(32) NOT NULL UNIQUE,
    server_id               INT NULL DEFAULT NULL,
    expires_at              TIMESTAMPTZ NULL DEFAULT NULL,
    max_uses                SMALLINT NOT NULL DEFAULT 5,
    validity_days           SMALLINT NULL DEFAULT NULL,
    use_count               SMALLINT NOT NULL DEFAULT 0,
    created_at              TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    cleaned_at              TIMESTAMPTZ NULL DEFAULT NULL,
    traffic_quota_gb        NUMERIC(12,3) NULL DEFAULT NULL,
    traffic_used_gb         NUMERIC(12,3) NOT NULL DEFAULT 0,
    traffic_quota_blocked_at TIMESTAMPTZ NULL DEFAULT NULL,
    subscription_until      DATE NULL DEFAULT NULL,
    allowed_server_ids      JSONB NULL DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_share_expires ON share_links (expires_at);

CREATE TABLE IF NOT EXISTS share_link_creations (
    id                          BIGSERIAL PRIMARY KEY,
    share_link_id               INT NOT NULL REFERENCES share_links(id) ON DELETE CASCADE,
    server_id                   INT NOT NULL,
    protocol                    VARCHAR(32) NOT NULL,
    client_id                   VARCHAR(512) NOT NULL,
    connection_name             VARCHAR(128) NOT NULL,
    response_json               TEXT NULL DEFAULT NULL,
    traffic_bytes_baseline      BIGINT NOT NULL DEFAULT 0,
    panel_traffic_bytes_total   BIGINT NULL DEFAULT NULL,
    panel_traffic_synced_at     TIMESTAMPTZ NULL DEFAULT NULL,
    created_at                  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at                  TIMESTAMPTZ NULL DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_share_link ON share_link_creations (share_link_id);
CREATE INDEX IF NOT EXISTS idx_share_creations_deleted ON share_link_creations (share_link_id, deleted_at);

CREATE TABLE IF NOT EXISTS panel_server_labels (
    panel_server_id INT PRIMARY KEY,
    title           VARCHAR(512) NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_panel_server_labels_updated ON panel_server_labels (updated_at);

CREATE TABLE IF NOT EXISTS share_renewal_codes (
    id              SERIAL PRIMARY KEY,
    code            VARCHAR(64) NOT NULL UNIQUE,
    add_days        SMALLINT NOT NULL DEFAULT 30,
    max_uses        INT NOT NULL DEFAULT 1,
    use_count       INT NOT NULL DEFAULT 0,
    share_link_id   INT NULL DEFAULT NULL,
    code_expires_at TIMESTAMPTZ NULL DEFAULT NULL,
    note            VARCHAR(255) NULL DEFAULT NULL,
    code_target     TEXT NOT NULL DEFAULT 'guest' CHECK (code_target IN ('guest', 'member')),
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_share_renewal_link ON share_renewal_codes (share_link_id);

CREATE TABLE IF NOT EXISTS share_renewal_redemptions (
    id               BIGSERIAL PRIMARY KEY,
    renewal_code_id  INT NOT NULL REFERENCES share_renewal_codes(id) ON DELETE CASCADE,
    share_link_id    INT NOT NULL REFERENCES share_links(id) ON DELETE CASCADE,
    add_days         SMALLINT NOT NULL,
    redeemed_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (renewal_code_id, share_link_id)
);
CREATE INDEX IF NOT EXISTS idx_redemption_link ON share_renewal_redemptions (share_link_id);

CREATE TABLE IF NOT EXISTS members (
    id              SERIAL PRIMARY KEY,
    username        VARCHAR(64) NOT NULL UNIQUE,
    email           VARCHAR(255) NULL UNIQUE,
    password_hash   VARCHAR(255) NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_login_at   TIMESTAMPTZ NULL DEFAULT NULL
);

CREATE TABLE IF NOT EXISTS member_subscriptions (
    member_id       INT PRIMARY KEY REFERENCES members(id) ON DELETE CASCADE,
    expires_at      TIMESTAMPTZ NULL DEFAULT NULL,
    validity_days   SMALLINT NULL DEFAULT NULL,
    max_configs     SMALLINT NOT NULL DEFAULT 5,
    config_count    SMALLINT NOT NULL DEFAULT 0,
    cleaned_at      TIMESTAMPTZ NULL DEFAULT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS member_configs (
    id              BIGSERIAL PRIMARY KEY,
    member_id       INT NOT NULL REFERENCES members(id) ON DELETE CASCADE,
    server_id       INT NOT NULL,
    protocol        VARCHAR(32) NOT NULL,
    client_id       VARCHAR(512) NOT NULL,
    connection_name VARCHAR(128) NOT NULL,
    response_json   TEXT NULL DEFAULT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at      TIMESTAMPTZ NULL DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_member_configs_member ON member_configs (member_id);

CREATE TABLE IF NOT EXISTS member_renewal_redemptions (
    id               BIGSERIAL PRIMARY KEY,
    renewal_code_id  INT NOT NULL REFERENCES share_renewal_codes(id) ON DELETE CASCADE,
    member_id        INT NOT NULL REFERENCES members(id) ON DELETE CASCADE,
    add_days         SMALLINT NOT NULL,
    redeemed_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (renewal_code_id, member_id)
);

-- scs session store
CREATE TABLE IF NOT EXISTS sessions (
    token  TEXT PRIMARY KEY,
    data   BYTEA NOT NULL,
    expiry TIMESTAMPTZ NOT NULL
);
CREATE INDEX IF NOT EXISTS sessions_expiry_idx ON sessions (expiry);
