@cocreate/file
Version:
A versatile, configurable headless file uploader supporting local and server operations. Accessible via a JavaScript API and HTML5 attributes, it provides seamless file reading, writing, and uploading with fallbacks to the standard HTML5 file input API. I
532 lines (469 loc) • 12.8 kB
JavaScript
const crud = require("@cocreate/crud-client");
const Config = require("@cocreate/config");
const fs = require("fs");
const realpathAsync = fs.promises.realpath;
const path = require("path");
const mimeTypes = {
".aac": "audio/aac",
".abw": "application/x-abiword",
".arc": "application/x-freearc",
".avi": "video/x-msvideo",
".azw": "application/vnd.amazon.ebook",
".bin": "application/octet-stream",
".bmp": "image/bmp",
".bz": "application/x-bzip",
".bz2": "application/x-bzip2",
".csh": "application/x-csh",
".css": "text/css",
".csv": "text/csv",
".doc": "application/msword",
".docx":
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".eot": "application/vnd.ms-fontobject",
".epub": "application/epub+zip",
".gif": "image/gif",
".htm": "text/html",
".html": "text/html",
".ico": "image/x-icon",
".ics": "text/calendar",
".jar": "application/java-archive",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript",
".json": "application/json",
".jsonld": "application/ld+json",
".mid": "audio/midi",
".midi": "audio/midi",
".mjs": "text/javascript",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".mpeg": "video/mpeg",
".mpkg": "application/vnd.apple.installer+xml",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogv": "video/ogg",
".ogx": "application/ogg",
".otf": "font/otf",
".png": "image/png",
".pdf": "application/pdf",
".ppt": "application/vnd.ms-powerpoint",
".pptx":
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
".rar": "application/x-rar-compressed",
".rtf": "application/rtf",
".sh": "application/x-sh",
".svg": "image/svg+xml",
".swf": "application/x-shockwave-flash",
".tar": "application/x-tar",
".tif": "image/tiff",
".tiff": "image/tiff",
".ts": "video/mp2t",
".ttf": "font/ttf",
".txt": "text/plain",
".vsd": "application/vnd.visio",
".wav": "audio/wav",
".weba": "audio/webm",
".webm": "video/webm",
".webmanifest": "application/manifest+json",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".xhtml": "application/xhtml+xml",
".xls": "application/vnd.ms-excel",
".xlsx":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xml": "application/xml",
".xul": "application/vnd.mozilla.xul+xml",
".zip": "application/zip",
".3gp": "video/3gpp",
".3g2": "video/3gpp2",
".7z": "application/x-7z-compressed"
};
module.exports = async function file(CoCreateConfig, configPath, match) {
let directories = CoCreateConfig.directories;
let sources = CoCreateConfig.sources;
let configDirectoryPath = path.dirname(configPath);
if (match && !Array.isArray(match)) match = [match];
else if (!match) match = [];
let config = await Config(
{
organization_id: {
prompt: "Enter your organization_id: "
},
host: {
prompt: "Enter the host: "
},
prompt: {
prompt: "Choose an authentication option: \n1.apikey\n2.Sign In\n",
choices: {
1: {
apikey: {
prompt: "Enter your apikey: "
}
},
2: {
email: {
prompt: "Enter your email: "
},
password: {
prompt: "Enter your password: "
}
}
}
}
},
null,
null,
configPath
);
if (
!config.organization_id ||
(!config.apikey && (!config.password || config.email))
) {
console.log("One or more required config params could not be found");
process.exit();
}
if (config.email && config.password) {
let request = {
method: "signIn",
array: "users",
$filter: {
query: {
email: config.email,
password: config.password
}
},
...config
};
let response = await crud.send(request);
let { success, token } = response;
if (success) {
console.log("succesful sign in");
// apply token to socket
} else {
console.log("The email or password you entered is incorrect");
process.exit();
}
}
// console.log('Uploading files...')
/**
* Store files by config directories
**/
let errorLog = [];
async function runDirectories() {
for (const directory of directories) {
const entry = directory.entry;
const exclude = directory.exclude || [];
await runFiles(directory, entry, exclude);
}
return;
}
async function runFiles(directory, entry, exclude, Path, directoryName) {
const entryPath = path.resolve(configDirectoryPath, entry);
let files = fs.readdirSync(entryPath);
for (let file of files) {
let skip = false;
for (let i = 0; i < exclude.length; i++) {
if (file.includes(exclude)) {
skip = true;
break;
}
}
if (skip) continue;
let isDirectory;
let isSymlink = fs
.lstatSync(`${entryPath}/${file}`)
.isSymbolicLink();
if (isSymlink) {
let symlinkPath = await realpathAsync(`${entryPath}/${file}`);
isDirectory =
fs.existsSync(symlinkPath) &&
fs.lstatSync(symlinkPath).isDirectory();
} else
isDirectory =
fs.existsSync(`${entryPath}/${file}`) &&
fs.lstatSync(`${entryPath}/${file}`).isDirectory();
let name = file;
let source = "";
for (let i = 0; i < match.length; i++) {
skip = true;
const filePath = path.resolve(entryPath, file);
if (filePath.startsWith(match[i])) {
skip = false;
break;
} else if (isDirectory && match[i].startsWith(filePath)) {
skip = "directory";
break;
}
}
if (skip === true) continue;
const fileExtension = path.extname(file);
let mimeType = mimeTypes[fileExtension];
if (!directoryName) {
if (directory.object && directory.object.directory) {
if (directory.object.directory === "{{directory}}") {
directoryName = entry.split("/");
directoryName = directoryName[directoryName.length - 1];
} else directoryName = directory.object.directory;
} else directoryName = "/";
}
if (exclude && exclude.includes(directoryName)) continue;
if (!Path) {
if (directoryName === "/") Path = directoryName;
else Path = "/" + directoryName;
}
let pathname;
if (Path === "/") pathname = Path + name;
else pathname = Path + "/" + name;
if (isDirectory) mimeType = "text/directory";
else
source = await getSource(
`${entryPath}/${file}`,
mimeType,
isSymlink
);
let values = {
"{{name}}": name || "",
"{{source}}": source || "",
"{{directory}}": directoryName || "",
"{{path}}": Path || "",
"{{pathname}}": pathname,
"{{content-type}}": mimeType || ""
};
let object = { ...directory.object };
if (!object.name) object.name = "{{name}}";
if (!object.src) object.src = "{{source}}";
if (!object.directory) object.directory = "{{directory}}";
if (!object.path) object.path = "{{path}}";
if (!object.pathname) object.pathname = "{{pathname}}";
if (!object["content-type"])
object["content-type"] = "{{content-type}}";
if (
!object.public &&
object.public != false &&
object.public != "false"
)
object.public = "true";
let newObject = {
array: directory.array || "files",
object
};
if (directory.storage) newObject.storage = directory.storage;
if (directory.database) newObject.database = directory.database;
if (directory.array) newObject.array = directory.array || "files";
for (const key of Object.keys(directory.object)) {
if (typeof directory.object[key] == "string") {
let variables = directory.object[key].match(
/{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g
);
if (variables) {
for (let variable of variables) {
newObject.object[key] = newObject.object[
key
].replace(variable, values[variable]);
}
}
}
}
if (skip !== "directory") {
if (!newObject.object._id)
newObject.$filter = {
query: {
pathname
}
};
response = await runStore(newObject);
console.log(
`Uploaded: ${entryPath}/${file}`,
`To: ${pathname}`
);
if (response.error) errorLog.push(response.error);
}
if (isDirectory && pathname) {
let newEntry;
if (entry.endsWith("/")) newEntry = entry + name;
else newEntry = entry + "/" + name;
await runFiles(directory, newEntry, exclude, pathname, name);
}
}
// if (errorLog.length)
// console.log(...errorLog)
}
async function getSource(path, mimeType, isSymlink) {
let readType = "utf8";
if (mimeType === "image/svg+xml") {
readType = "utf8";
} else if (/^(image|audio|video)\/[-+.\w]+/.test(mimeType)) {
readType = "base64";
}
if (isSymlink) path = await realpathAsync(path);
let binary = fs.readFileSync(path);
let content = new Buffer.from(binary).toString(readType);
return content;
}
/**
* Store files by config sources
**/
async function runSources() {
let updatedSources = [];
for (let i = 0; i < sources.length; i++) {
const { array, object } = sources[i];
let source = { ...sources[i] };
let keys = new Map();
let response = {};
let isMatch = false;
try {
if (array) {
if (!object) object = {};
else
for (const key of Object.keys(object)) {
if (typeof object[key] != "string") continue;
let variables = object[key].match(
/{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g
);
if (variables) {
let originalValue = object[key];
keys.set(key, originalValue);
let value = "";
for (let variable of variables) {
let entry = /{{\s*([\w\W]+)\s*}}/g.exec(
variable
);
entry = entry[1].trim();
if (entry) {
if (!fs.existsSync(entry)) continue;
if (!isMatch) {
const filePath = path.resolve(
configDirectoryPath,
entry
);
for (
let i = 0;
i < match.length;
i++
) {
if (
filePath.startsWith(
match[i]
)
) {
console.log(
"Source saved",
sources[i]
);
isMatch = true;
break;
}
}
}
let read_type = "utf8";
const fileExtension =
path.extname(entry);
let mime_type =
mimeTypes[fileExtension] ||
"text/html";
if (
/^(image|audio|video)\/[-+.\w]+/.test(
mime_type
)
) {
read_type = "base64";
}
let binary = fs.readFileSync(entry);
let content = new Buffer.from(
binary
).toString(read_type);
if (content) value += content;
// object[key] = object[key].replace(variable, content);
}
}
object[key] = value;
}
}
let data = { array, object };
if (!object._id && object.pathname)
data.$filter = {
query: {
$or: [{ pathname: object.pathname }]
}
};
if (match.length && isMatch)
response = await runStore(data);
}
} catch (err) {
console.log(err);
process.exit();
}
if (
response.object &&
response.object[0] &&
response.object[0]._id
) {
source.object._id = response.object[0]._id;
}
for (const [key, value] of keys) {
source.object[key] = value;
}
updatedSources.push(source);
}
return updatedSources;
}
async function runStore(data) {
try {
let response;
if (!data.object._id && !data.$filter) {
response = await crud.send({
method: "object.create",
...config,
...data
});
} else {
response = await crud.send({
method: "object.update",
...config,
...data,
upsert: true
});
}
if (response) {
return response;
}
} catch (err) {
console.log(err);
return null;
}
}
async function run() {
if (directories) await runDirectories();
if (sources && sources.length) {
let sources = await runSources();
let newConfig = { ...CoCreateConfig };
if (directories && directories.length)
newConfig.directories = directories;
newConfig.sources = sources;
if (newConfig.repositories)
newConfig.repositories.forEach((obj) => {
for (const key in obj) {
if (!["path", "repo", "exclude"].includes(key)) {
delete obj[key];
}
}
});
delete newConfig.url;
delete newConfig.broadcast;
fs.writeFileSync(
configPath,
`module.exports = ${JSON.stringify(newConfig, null, 4)};`
);
}
if (!match.length) {
console.log("upload complete!");
setTimeout(function () {
process.exit();
}, 2000);
}
}
run();
};