Source: saltcorn-data/models/plugin.js

const db = require("../db");
const { contract, is } = require("contractis");
const View = require("./view");
const { is_stale } = require("./pack");
const fetch = require("node-fetch");
const { stringToJSON } = require("../utils");

/**
 * Plugin Class
 */
class Plugin {
  /**
   * Plugin constructor
   * @param o
   */
  constructor(o) {
    this.id = o.id ? +o.id : o.id;
    this.name = o.name;
    this.source = o.source;
    this.location = o.location;
    this.version = o.version;
    this.description = o.description;
    this.documentation_link = o.documentation_link;
    this.has_theme = o.has_theme;
    this.has_auth = o.has_auth;
    this.configuration = stringToJSON(o.configuration);
    contract.class(this);
  }

  /**
   * Find one plugin
   * @param where - where object
   * @returns {Promise<Plugin|null|*>} return existing plugin or new plugin
   */
  static async findOne(where) {
    const p = await db.selectMaybeOne("_sc_plugins", where);
    return p ? new Plugin(p) : p;
  }

  /**
   * Find plugins
   * @param where - where object
   * @returns {Promise<*>} returns plugins list
   */
  static async find(where) {
    return (await db.select("_sc_plugins", where)).map((p) => new Plugin(p));
  }

  /**
   * Update or Insert plugin
   * @returns {Promise<void>}
   */
  async upsert() {
    const row = {
      name: this.name,
      source: this.source,
      location: this.location,
      version: this.version,
      configuration: this.configuration,
    };
    if (typeof this.id === "undefined") {
      // insert
      await db.insert("_sc_plugins", row);
    } else {
      await db.update("_sc_plugins", row, this.id);
    }
  }

  /**
   * Delete plugin
   * @returns {Promise<void>}
   */
  async delete() {
    await db.deleteWhere("_sc_plugins", { id: this.id });
    const { getState } = require("../db/state");
    await getState().remove_plugin(this.name);
  }

  /**
   * Upgrade plugin version
   * @param requirePlugin
   * @returns {Promise<void>}
   */
  async upgrade_version(requirePlugin) {
    if (this.source === "npm") {
      const old_version = this.version;
      this.version = "latest";
      const { version } = await requirePlugin(this, true);
      if (version && version !== old_version) {
        this.version = version;
        this.upsert();
      }
    } else {
      await requirePlugin(this, true);
    }
  }

  /**
   * List of views relay on this plugin
   * @returns {Promise<*[]|*>}
   */
  async dependant_views() {
    const views = await View.find({});
    const { getState } = require("../db/state");
    if (!getState().plugins[this.name]) return [];
    const myViewTemplates = getState().plugins[this.name].viewtemplates || [];
    const vt_names = Array.isArray(myViewTemplates)
      ? myViewTemplates.map((vt) => vt.name)
      : typeof myViewTemplates === "function"
      ? myViewTemplates(getState().plugin_cfgs[this.name]).map((vt) => vt.name)
      : Object.keys(myViewTemplates);
    return views
      .filter((v) => vt_names.includes(v.viewtemplate))
      .map((v) => v.name);
  }

  /**
   * List plugins availabe in store
   * @returns {Promise<*>}
   */
  static async store_plugins_available() {
    const { getState } = require("../db/state");
    const stored = getState().getConfig("available_plugins", false);
    const stored_at = getState().getConfig(
      "available_plugins_fetched_at",
      false
    );
    const isRoot = db.getTenantSchema() === db.connectObj.default_schema;

    if (!stored || !stored_at || is_stale(stored_at)) {
      try {
        const from_api = await Plugin.store_plugins_available_from_store();
        await getState().setConfig("available_plugins", from_api);
        await getState().setConfig("available_plugins_fetched_at", new Date());
        return from_api.filter((p) => isRoot || !p.has_auth);
      } catch (e) {
        console.error(e);
        if (stored)
          return stored
            .map((p) => new Plugin(p))
            .filter((p) => isRoot || !p.has_auth);
        else throw e;
      }
    } else
      return stored
        .map((p) => new Plugin(p))
        .filter((p) => isRoot || !p.has_auth);
  }

  /**
   *
   * @returns {Promise<*>}
   */
  static async store_plugins_available_from_store() {
    //console.log("fetch plugins");
    // TODO support of other store URLs
    const response = await fetch("http://store.saltcorn.com/api/extensions");
    const json = await response.json();
    return json.success.map((p) => new Plugin(p));
  }

  /**
   *
   * @param name
   * @returns {Promise<null|Plugin>}
   */
  static async store_by_name(name) {
    // TODO support of other store URLs
    const response = await fetch(
      "http://store.saltcorn.com/api/extensions?name=" +
        encodeURIComponent(name)
    );
    const json = await response.json();
    if (json.success.length == 1)
      return new Plugin({ version: "latest", ...json.success[0] });
    else return null;
  }
}

Plugin.contract = {
  variables: {
    id: is.maybe(is.posint),
    location: is.str,
    name: is.str,
    version: is.maybe(is.str),
    documentation_link: is.maybe(is.str),
    configuration: is.maybe(is.obj()),
    source: is.one_of(["npm", "github", "local"]),
  },
  methods: {
    upsert: is.fun([], is.promise(is.eq(undefined))),
    delete: is.fun([], is.promise(is.eq(undefined))),
    dependant_views: is.fun([], is.promise(is.array(is.str))),
  },
  static_methods: {
    find: is.fun(is.maybe(is.obj()), is.promise(is.array(is.class("Plugin")))),
    findOne: is.fun(is.obj(), is.promise(is.maybe(is.class("Plugin")))),
    store_by_name: is.fun(is.str, is.promise(is.maybe(is.class("Plugin")))),
    store_plugins_available_from_store: is.fun(
      [],
      is.promise(is.array(is.class("Plugin")))
    ),
    store_plugins_available: is.fun(
      [],
      is.promise(is.array(is.class("Plugin")))
    ),
  },
};

module.exports = Plugin;