2023-07-21 14:40:27 +05:30
|
|
|
import AdmZip from 'adm-zip';
|
2023-07-21 15:00:36 +05:30
|
|
|
import { getAppPath, getPluginFolderNameFromInfo } from 'backend/helpers';
|
2023-07-21 14:40:27 +05:30
|
|
|
import fs from 'fs-extra';
|
|
|
|
import { DatabaseError } from 'fyo/utils/errors';
|
|
|
|
import type { Knex } from 'knex';
|
|
|
|
import path from 'path';
|
|
|
|
import { getSchemas } from 'schemas/index';
|
|
|
|
import { SchemaStub } from 'schemas/types';
|
|
|
|
import { PluginInfo } from 'utils/types';
|
|
|
|
import type DatabaseCore from './core';
|
2023-07-24 14:53:57 +05:30
|
|
|
import { PluginConfig } from './types';
|
2023-07-21 14:40:27 +05:30
|
|
|
|
|
|
|
export async function executeFirstMigration(
|
|
|
|
db: DatabaseCore,
|
|
|
|
countryCode: string
|
|
|
|
) {
|
|
|
|
if (!db.knex) {
|
|
|
|
throw new DatabaseError('Database not initialized');
|
|
|
|
}
|
|
|
|
|
|
|
|
const isFirstRun = await getIsFirstRun(db.knex);
|
|
|
|
if (!isFirstRun) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const schemas = getSchemas(countryCode);
|
|
|
|
db.setSchemaMap(schemas);
|
|
|
|
await db.migrate();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getIsFirstRun(knex: Knex): Promise<boolean> {
|
|
|
|
const query = await knex('sqlite_master').where({
|
|
|
|
type: 'table',
|
|
|
|
name: 'PatchRun',
|
|
|
|
});
|
|
|
|
return !query.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getPluginInfoList(knex: Knex): Promise<PluginInfo[]> {
|
2023-07-24 14:53:57 +05:30
|
|
|
let plugins: { info: string }[];
|
|
|
|
try {
|
|
|
|
plugins = (await knex('Plugin').select(['info'])) as {
|
|
|
|
info: string;
|
|
|
|
}[];
|
|
|
|
} catch {
|
|
|
|
return [];
|
|
|
|
}
|
2023-07-21 14:40:27 +05:30
|
|
|
|
|
|
|
return plugins.map(({ info }) => JSON.parse(info) as PluginInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function unzipPluginsIfDoesNotExist(
|
|
|
|
knex: Knex,
|
|
|
|
infoList: PluginInfo[]
|
|
|
|
): Promise<void> {
|
|
|
|
for (const info of infoList) {
|
|
|
|
const pluginsRootPath = getAppPath('plugins');
|
|
|
|
const folderName = getPluginFolderNameFromInfo(info);
|
|
|
|
const pluginPath = path.join(pluginsRootPath, folderName);
|
|
|
|
|
|
|
|
if (fs.existsSync(pluginPath)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
deletePluginFolder(info);
|
|
|
|
fs.ensureDirSync(pluginPath);
|
|
|
|
const data = (await knex('Plugin')
|
|
|
|
.select('data')
|
|
|
|
.where({ name: info.name })) as {
|
|
|
|
data: string;
|
|
|
|
}[];
|
|
|
|
|
|
|
|
const pluginZipBase64 = data[0].data;
|
|
|
|
const zipBuffer = Buffer.from(pluginZipBase64, 'base64');
|
|
|
|
const pluginFilePath = path.join(pluginPath, `${folderName}.books_plugin`);
|
|
|
|
|
|
|
|
fs.writeFileSync(pluginFilePath, zipBuffer);
|
|
|
|
const zip = new AdmZip(pluginFilePath);
|
|
|
|
zip.extractAllTo(pluginPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function deletePluginFolder(info: PluginInfo) {
|
|
|
|
const pluginsRootPath = getAppPath('plugins');
|
|
|
|
const folderNamePrefix = getPluginFolderNameFromInfo(info, true) + '-';
|
2023-07-24 14:53:57 +05:30
|
|
|
let folderNames: string[] = [];
|
|
|
|
try {
|
|
|
|
folderNames = fs.readdirSync(pluginsRootPath);
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
for (const folderName of folderNames) {
|
2023-07-21 14:40:27 +05:30
|
|
|
if (!folderName.startsWith(folderNamePrefix)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.removeSync(path.join(pluginsRootPath, folderName));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-24 14:53:57 +05:30
|
|
|
export async function getPluginConfig(info: PluginInfo): Promise<PluginConfig> {
|
|
|
|
const folderName = getPluginFolderNameFromInfo(info);
|
|
|
|
const pluginRoot = path.join(getAppPath('plugins'), folderName);
|
|
|
|
|
|
|
|
const config: PluginConfig = {
|
|
|
|
info,
|
|
|
|
schemas: [],
|
|
|
|
paths: {
|
|
|
|
folderName,
|
|
|
|
root: pluginRoot,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const schemasPath = path.resolve(pluginRoot, 'schemas.js');
|
|
|
|
if (fs.existsSync(schemasPath)) {
|
|
|
|
config.paths.schemas = schemasPath;
|
|
|
|
config.schemas = await importSchemas(schemasPath);
|
2023-07-21 14:40:27 +05:30
|
|
|
}
|
|
|
|
|
2023-07-24 14:53:57 +05:30
|
|
|
const modelsPath = path.resolve(pluginRoot, 'models.js');
|
|
|
|
if (fs.existsSync(schemasPath)) {
|
|
|
|
config.paths.models = modelsPath;
|
|
|
|
}
|
2023-07-21 14:40:27 +05:30
|
|
|
|
2023-07-24 14:53:57 +05:30
|
|
|
return config;
|
|
|
|
}
|
2023-07-21 14:40:27 +05:30
|
|
|
|
2023-07-24 14:53:57 +05:30
|
|
|
async function importSchemas(schemasPath: string): Promise<SchemaStub[]> {
|
|
|
|
try {
|
2023-07-21 14:40:27 +05:30
|
|
|
const {
|
2023-07-24 14:53:57 +05:30
|
|
|
default: { default: exportedSchemas },
|
|
|
|
} = (await import(schemasPath)) as {
|
2023-07-21 14:40:27 +05:30
|
|
|
default: { default: unknown };
|
|
|
|
};
|
|
|
|
|
2023-07-24 14:53:57 +05:30
|
|
|
if (isSchemaStubList(exportedSchemas)) {
|
|
|
|
return exportedSchemas;
|
2023-07-21 14:40:27 +05:30
|
|
|
}
|
2023-07-24 14:53:57 +05:30
|
|
|
} catch {}
|
2023-07-21 14:40:27 +05:30
|
|
|
|
2023-07-24 14:53:57 +05:30
|
|
|
return [];
|
2023-07-21 14:40:27 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function isSchemaStubList(schemas: unknown): schemas is SchemaStub[] {
|
|
|
|
if (!Array.isArray(schemas)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return schemas.every(
|
|
|
|
(sch) =>
|
|
|
|
typeof sch === 'object' && typeof (sch as SchemaStub)?.name === 'string'
|
|
|
|
);
|
|
|
|
}
|