commit b0a268e02b496e918c7d897ae88b821956846326 Author: subnub Date: Wed Mar 4 17:10:56 2020 -0500 init commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..104d174 Binary files /dev/null and b/.DS_Store differ diff --git a/.babelrc b/.babelrc new file mode 100755 index 0000000..d48d90d --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@babel/env", "@babel/react"], + "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-object-rest-spread"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..a13efa6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +node_modules/ +public/dist/ +config/ +.env.development +.env.test +.env.production +.well-known/ +.eslintrc.js +.eslintrc.json +changeEncrytionPassword/ +certificate.ca-bundle +certificate.crt +certificate.key +package-lock.json \ No newline at end of file diff --git a/jest.config.json b/jest.config.json new file mode 100755 index 0000000..74387ea --- /dev/null +++ b/jest.config.json @@ -0,0 +1,9 @@ +{ + "setupFiles": [ + "raf/polyfill", + "/tests/setupTests.js" + ], + "snapshotSerializers": [ + "enzyme-to-json/serializer" + ] +} \ No newline at end of file diff --git a/key/getKey.js b/key/getKey.js new file mode 100644 index 0000000..e9e3a38 --- /dev/null +++ b/key/getKey.js @@ -0,0 +1,25 @@ +const prompt = require('password-prompt') +const env = require("../src/enviroment/env") +const crypto = require("crypto"); + +const getKey = async() => { + + if (process.env.NODE_ENV) { + + let password = await prompt("Enter Server Encryption Password: ", {method: "hide"}); + + password = crypto.createHash("md5").update(password).digest("hex"); + + env.key = password; + + } else { + + let password = "1234"; + + password = crypto.createHash("md5").update(password).digest("hex"); + + env.key = password; + } +} + +module.exports = getKey; \ No newline at end of file diff --git a/key/getNewKey.js b/key/getNewKey.js new file mode 100644 index 0000000..39ccb75 --- /dev/null +++ b/key/getNewKey.js @@ -0,0 +1,22 @@ +const prompt = require('password-prompt') +const env = require("../src/enviroment/env") +const crypto = require("crypto"); + +const getKey = async() => { + + let password = await prompt("Enter New Server Encryption Password: ", {method: "hide"}); + + let confirmPassword = await prompt("Verify New Server Encryption Password: ", {method: "hide"}); + + if (password !== confirmPassword) { + console.log("New Passwords do not match, exiting..."); + process.exit(); + } + + password = crypto.createHash("md5").update(password).digest("hex"); + + env.newKey = password; + +} + +module.exports = getKey; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100755 index 0000000..287e34a --- /dev/null +++ b/package.json @@ -0,0 +1,108 @@ +{ + "name": "my-drive", + "version": "1.0.0", + "main": "index.js", + "license": "", + "scripts": { + "server": "live-server public/", + "build:dev": "webpack", + "build:prod": "cross-env NODE_ENV=production webpack -p --env production", + "dev-server": "webpack-dev-server", + "test": "cross-env NODE_ENV=test env-cmd -f ./config/test.env jest --config=jest.config.json --watchAll --runInBand", + "start:dev": "env-cmd -f ./config/dev.env node server/serverStart.js", + "start": "env-cmd -f ./config/prod.env node server/serverStart.js", + "clean-database": "env-cmd -f ./config/prod.env node serverUtils/cleanDatabase.js", + "clean-database:dev": "env-cmd -f ./config/dev.env node serverUtils/cleanDatabase.js", + "backup-database": "env-cmd -f ./config/prod.env node serverUtils/backupDatabase.js", + "backup-database:dev": "env-cmd -f ./config/dev.env node serverUtils/backupDatabase.js", + "restore-database": "env-cmd -f ./config/prod.env node serverUtils/restoreDatabase.js", + "restore-database:dev": "env-cmd -f ./config/dev.env node serverUtils/restoreDatabase.js", + "delete-temp-database": "env-cmd -f ./config/prod.env node serverUtils/deleteTempDatabase.js", + "delete-temp-database:dev": "env-cmd -f ./config/dev.env node serverUtils/deleteTempDatabase.js", + "delete-database": "env-cmd -f ./config/prod.env node serverUtils/deleteDatabase.js", + "delete-database:dev": "env-cmd -f ./config/dev.env node serverUtils/deleteDatabase.js", + "change-password-database": "env-cmd -f ./config/prod.env node serverUtils/changeEncryptionPassword.js", + "change-password-database:dev": "env-cmd -f ./config/dev.env node serverUtils/changeEncryptionPassword.js", + "create-indexes-database": "env-cmd -f ./config/prod.env node serverUtils/createIndexes.js", + "create-indexes-database:dev": "env-cmd -f ./config/dev.env node serverUtils/createIndexes.js", + "heroku-postbuild": "yarn run build:prod" + }, + "dependencies": { + "@babel/core": "^7.8.4", + "@babel/plugin-proposal-class-properties": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.8.3", + "@babel/preset-env": "^7.8.4", + "@babel/preset-react": "^7.8.3", + "axios": "^0.19.2", + "babel-polyfill": "^6.26.0", + "bcrypt": "^3.0.8", + "body-parser": "^1.19.0", + "bytes": "^3.1.0", + "cli-progress": "^3.6.0", + "compression": "^1.7.4", + "concat-stream": "^2.0.0", + "connect-busboy": "0.0.2", + "copy-text-to-clipboard": "^2.1.1", + "core-js": "^3.6.4", + "crypto": "^1.0.1", + "diskusage": "^1.1.3", + "express": "^4.17.1", + "fluent-ffmpeg": "^2.1.2", + "helmet": "^3.21.2", + "history": "^4.10.1", + "jsonwebtoken": "^8.5.1", + "mini-css-extract-plugin": "^0.8.2", + "moment": "^2.24.0", + "mongodb": "^3.5.3", + "mongoose": "^5.9.1", + "normalize.css": "^8.0.1", + "password-prompt": "^1.1.2", + "progress-stream": "^2.0.0", + "prompts": "^2.3.1", + "raf": "^3.4.1", + "react": "^16.12.0", + "react-circular-progressbar": "^2.0.3", + "react-contextmenu": "^2.13.0", + "react-dom": "^16.12.0", + "react-infinite-scroller": "^1.2.4", + "react-redux": "^7.2.0", + "react-router-dom": "^5.1.1", + "redux": "^4.0.5", + "redux-thunk": "^2.3.0", + "regenerator-runtime": "^0.13.3", + "sharp": "^0.23.4", + "sweetalert2": "^9.7.2", + "temp": "^0.9.1", + "uuid": "^3.4.0", + "validator": "^12.2.0", + "webpack": "^4.41.6" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.20.0", + "@typescript-eslint/parser": "^2.20.0", + "babel-loader": "^8.0.6", + "compression-webpack-plugin": "^3.1.0", + "cross-env": "^6.0.3", + "css-loader": "^3.4.2", + "dart-sass": "^1.25.0", + "dotenv": "^8.2.0", + "env-cmd": "^10.1.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "enzyme-to-json": "^3.4.4", + "eslint": "^6.8.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-react": "^7.18.3", + "jest": "^24.9.0", + "json-loader": "^0.5.7", + "live-server": "^1.2.1", + "react-test-renderer": "^16.12.0", + "sass-loader": "^8.0.2", + "style-loader": "^1.1.3", + "superagent-binary-parser": "^1.0.1", + "supertest": "^4.0.2", + "webpack-bundle-analyzer": "^3.6.0", + "webpack-cli": "^3.3.11", + "webpack-dev-server": "^3.10.3" + } +} diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100755 index 0000000..53ec8a2 Binary files /dev/null and b/public/.DS_Store differ diff --git a/public/images/.DS_Store b/public/images/.DS_Store new file mode 100755 index 0000000..5008ddf Binary files /dev/null and b/public/images/.DS_Store differ diff --git a/public/images/add-blue.png b/public/images/add-blue.png new file mode 100644 index 0000000..c3867f6 Binary files /dev/null and b/public/images/add-blue.png differ diff --git a/public/images/back.svg b/public/images/back.svg new file mode 100644 index 0000000..e11e92f --- /dev/null +++ b/public/images/back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/bg.jpg b/public/images/bg.jpg new file mode 100755 index 0000000..f0d2e47 Binary files /dev/null and b/public/images/bg.jpg differ diff --git a/public/images/check-green.png b/public/images/check-green.png new file mode 100644 index 0000000..7aaeabd Binary files /dev/null and b/public/images/check-green.png differ diff --git a/public/images/close-circle-outline.svg b/public/images/close-circle-outline.svg new file mode 100644 index 0000000..0018809 --- /dev/null +++ b/public/images/close-circle-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/close-white-png.png b/public/images/close-white-png.png new file mode 100644 index 0000000..1f4efb9 Binary files /dev/null and b/public/images/close-white-png.png differ diff --git a/public/images/close_icon.png b/public/images/close_icon.png new file mode 100644 index 0000000..51b4401 Binary files /dev/null and b/public/images/close_icon.png differ diff --git a/public/images/cloud-svg.svg b/public/images/cloud-svg.svg new file mode 100644 index 0000000..578422c --- /dev/null +++ b/public/images/cloud-svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/copy.svg b/public/images/copy.svg new file mode 100644 index 0000000..0bd7d93 --- /dev/null +++ b/public/images/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/down-arrow-grey-small.png b/public/images/down-arrow-grey-small.png new file mode 100644 index 0000000..8f0b509 Binary files /dev/null and b/public/images/down-arrow-grey-small.png differ diff --git a/public/images/down-arrow-white-1.svg b/public/images/down-arrow-white-1.svg new file mode 100644 index 0000000..0390623 --- /dev/null +++ b/public/images/down-arrow-white-1.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/down-arrow-white-svg.svg b/public/images/down-arrow-white-svg.svg new file mode 100644 index 0000000..e1cbb7c --- /dev/null +++ b/public/images/down-arrow-white-svg.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/public/images/down-arrow-white.png b/public/images/down-arrow-white.png new file mode 100644 index 0000000..190344e Binary files /dev/null and b/public/images/down-arrow-white.png differ diff --git a/public/images/download.svg b/public/images/download.svg new file mode 100644 index 0000000..7e6c456 --- /dev/null +++ b/public/images/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/edit.svg b/public/images/edit.svg new file mode 100644 index 0000000..77d7e94 --- /dev/null +++ b/public/images/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/error-red.png b/public/images/error-red.png new file mode 100644 index 0000000..db9bd9d Binary files /dev/null and b/public/images/error-red.png differ diff --git a/public/images/favicon.png b/public/images/favicon.png new file mode 100755 index 0000000..5cc1281 Binary files /dev/null and b/public/images/favicon.png differ diff --git a/public/images/file-svg.svg b/public/images/file-svg.svg new file mode 100644 index 0000000..2bc0e5e --- /dev/null +++ b/public/images/file-svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/folder-svg.svg b/public/images/folder-svg.svg new file mode 100644 index 0000000..747cf96 --- /dev/null +++ b/public/images/folder-svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/folder-upload-grey.png b/public/images/folder-upload-grey.png new file mode 100644 index 0000000..3d49f45 Binary files /dev/null and b/public/images/folder-upload-grey.png differ diff --git a/public/images/gear_icon.png b/public/images/gear_icon.png new file mode 100644 index 0000000..4f93899 Binary files /dev/null and b/public/images/gear_icon.png differ diff --git a/public/images/grey-add-folder.png b/public/images/grey-add-folder.png new file mode 100644 index 0000000..7d951f2 Binary files /dev/null and b/public/images/grey-add-folder.png differ diff --git a/public/images/grey-folder.png b/public/images/grey-folder.png new file mode 100644 index 0000000..1e96333 Binary files /dev/null and b/public/images/grey-folder.png differ diff --git a/public/images/grey_file.png b/public/images/grey_file.png new file mode 100644 index 0000000..4667895 Binary files /dev/null and b/public/images/grey_file.png differ diff --git a/public/images/grid-icon-svg.svg b/public/images/grid-icon-svg.svg new file mode 100644 index 0000000..ac0b02c --- /dev/null +++ b/public/images/grid-icon-svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/home.svg b/public/images/home.svg new file mode 100644 index 0000000..c7a76fc --- /dev/null +++ b/public/images/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/info-icon-grey.png b/public/images/info-icon-grey.png new file mode 100644 index 0000000..ae2cb72 Binary files /dev/null and b/public/images/info-icon-grey.png differ diff --git a/public/images/info.svg b/public/images/info.svg new file mode 100644 index 0000000..af1e5f2 --- /dev/null +++ b/public/images/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/list-icon-svg.svg b/public/images/list-icon-svg.svg new file mode 100644 index 0000000..2e26816 --- /dev/null +++ b/public/images/list-icon-svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/loader.gif b/public/images/loader.gif new file mode 100755 index 0000000..6d88879 Binary files /dev/null and b/public/images/loader.gif differ diff --git a/public/images/move.svg b/public/images/move.svg new file mode 100644 index 0000000..907aef1 --- /dev/null +++ b/public/images/move.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/my-drive-dropdown.png b/public/images/my-drive-dropdown.png new file mode 100644 index 0000000..32943d6 Binary files /dev/null and b/public/images/my-drive-dropdown.png differ diff --git a/public/images/my-drive-icon.png b/public/images/my-drive-icon.png new file mode 100644 index 0000000..35a0f18 Binary files /dev/null and b/public/images/my-drive-icon.png differ diff --git a/public/images/my-drive-icon2.png b/public/images/my-drive-icon2.png new file mode 100644 index 0000000..50b4e41 Binary files /dev/null and b/public/images/my-drive-icon2.png differ diff --git a/public/images/plus-grey.png b/public/images/plus-grey.png new file mode 100644 index 0000000..e2933f2 Binary files /dev/null and b/public/images/plus-grey.png differ diff --git a/public/images/rename.svg b/public/images/rename.svg new file mode 100644 index 0000000..cd6c95c --- /dev/null +++ b/public/images/rename.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/right-arrow-svg.svg b/public/images/right-arrow-svg.svg new file mode 100644 index 0000000..8e7175d --- /dev/null +++ b/public/images/right-arrow-svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/share.svg b/public/images/share.svg new file mode 100644 index 0000000..60fa607 --- /dev/null +++ b/public/images/share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/trash.svg b/public/images/trash.svg new file mode 100644 index 0000000..c458c86 --- /dev/null +++ b/public/images/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/up-arrow-grey-small.png b/public/images/up-arrow-grey-small.png new file mode 100644 index 0000000..fe7775c Binary files /dev/null and b/public/images/up-arrow-grey-small.png differ diff --git a/public/images/up-arrow-white.png b/public/images/up-arrow-white.png new file mode 100644 index 0000000..0a0b46b Binary files /dev/null and b/public/images/up-arrow-white.png differ diff --git a/public/images/upload-file-grey.png b/public/images/upload-file-grey.png new file mode 100644 index 0000000..7c5fc27 Binary files /dev/null and b/public/images/upload-file-grey.png differ diff --git a/public/index.html b/public/index.html new file mode 100755 index 0000000..8eab094 --- /dev/null +++ b/public/index.html @@ -0,0 +1,21 @@ + + + + + + + + MyDrive + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100755 index 0000000..f301945 --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ +# MyDrive + +Coming Soon :) + +yarn install +yarn run build:prod +yarn start \ No newline at end of file diff --git a/rootpath.js b/rootpath.js new file mode 100644 index 0000000..68db50e --- /dev/null +++ b/rootpath.js @@ -0,0 +1 @@ +module.exports = __dirname \ No newline at end of file diff --git a/server/server.js b/server/server.js new file mode 100755 index 0000000..03be213 --- /dev/null +++ b/server/server.js @@ -0,0 +1,75 @@ +const express = require("express"); +const app = express(); +const path = require("path") +const publicPath = path.join(__dirname, "..", "public"); +const userRouter = require("../src/express-routers/user") +const fileRouter = require("../src/express-routers/file") +const folderRouter = require("../src/express-routers/folder"); +const storageRouter = require("../src/express-routers/storage"); +const bodyParser = require('body-parser'); +const https = require("https"); +const fs = require("fs"); +const helmet = require("helmet"); +const busboy = require("connect-busboy") +const compression = require("compression"); + + +const cert = fs.readFileSync("certificate.crt") +const ca = fs.readFileSync("certificate.ca-bundle"); +const key = fs.readFileSync("certificate.key"); + + +const options = { + cert, + ca, + key +} + +const http = require("http") + +const server = http.createServer(app); +const serverHttps = https.createServer( options, app ); + + +require("../src/db/mongoose") + + +app.use(helmet()) +app.use(compression()); +app.use(express.json()); +app.use(express.static(publicPath)); +app.use(bodyParser.json({limit: "50mb"})); +app.use(bodyParser.urlencoded({limit: "50mb", extended: true, parameterLimit:50000})) + +app.use(busboy({ + highWaterMark: 2 * 1024 * 1024, +})); + +app.use(userRouter, fileRouter, folderRouter, storageRouter); + + +const nodeMode = process.env.NODE_ENV ? "Production" : "Development/Testing"; + +console.log("Node Enviroment Mode:", nodeMode); + + +app.get("*", (req, res) => { + + res.sendFile(path.join(publicPath, "index.html")) +}) + + +if (process.env.NODE_ENV === 'production') { + + module.exports = {server, serverHttps}; + +} else { + + module.exports = server; +} + + + + + + diff --git a/server/serverStart.js b/server/serverStart.js new file mode 100644 index 0000000..7fd1430 --- /dev/null +++ b/server/serverStart.js @@ -0,0 +1,33 @@ +const getKey = require("../key/getKey"); + +const serverStart = async() => { + + await getKey(); + + if (process.env.NODE_ENV === 'production') { + + const {server, serverHttps} = require("./server"); + + server.listen(process.env.HTTP_PORT,process.env.URL, () => { + console.log("Http Server Running On Port:", process.env.HTTP_PORT); + }) + + serverHttps.listen(process.env.HTTPS_PORT, function () { + console.log( 'Https Server Running On Port:', process.env.HTTPS_PORT); + } ); + + } else { + + const server = require("./server"); + + server.listen(process.env.HTTP_PORT,process.env.URL, () => { + console.log("Development Server Running On Port:", process.env.HTTP_PORT); + }) + } +} + +serverStart(); + + + + diff --git a/serverUtils/backupDatabase.js b/serverUtils/backupDatabase.js new file mode 100644 index 0000000..c199ba0 --- /dev/null +++ b/serverUtils/backupDatabase.js @@ -0,0 +1,56 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const conn = mongoose.connection; +const prompts = require("prompts"); +const createTempDirectory = require("./createTempDirectory"); + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const copyDatabase = async() => { + + console.log("Waiting For Database Connection..."); + await waitForDatabase(); + console.log("Connected To Database\n"); + + const userConfimation = await prompts({ + type: 'text', + message: "Warning: This will create a new Database backup, overwriting\n" + + "the current database backup. Only ONE Database backup\n" + + "can Be Stored At A Time.\n" + + "For more permanent backups, use MongoExport, or \n" + + "Backup data manually. \n" + + "Would you like to continue? (Yes/No)", + name: "value" + }) + + if (!userConfimation.value || userConfimation.value.toLowerCase() !== "yes") { + + console.log("Exiting...") + process.exit() + return; + } + + await createTempDirectory(); + + console.log("Finished Copying Database, Exiting..."); + process.exit(); +} + +copyDatabase() \ No newline at end of file diff --git a/serverUtils/changeEncryptionPassword.js b/serverUtils/changeEncryptionPassword.js new file mode 100644 index 0000000..a7976d5 --- /dev/null +++ b/serverUtils/changeEncryptionPassword.js @@ -0,0 +1,295 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const conn = mongoose.connection; +const ObjectID = require('mongodb').ObjectID +const imageChecker = require("../src/utils/imageChecker"); +const createThumbnail = require("./createThumbnailBuffer"); +const prompts = require("prompts"); +const getKey = require("../key/getKey"); +const getNewKey = require("../key/getNewKey"); +const crypto = require("crypto"); +const env = require("../src/enviroment/env"); +const cliProgress = require('cli-progress'); +const createTempDirectory = require("./createTempDirectory"); + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const reencryptFile = (file, newKey, user) => { + + return new Promise(async(resolve, reject) => { + + const fileID = file._id; + const filename = file.filename; + + let decryptBucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255, + bucketName: "temp-fs" + }); + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + const metadata = file.metadata; + + const readStream = decryptBucket.openDownloadStream(ObjectID(fileID)); + + const writeStream = bucket.openUploadStream(filename, {metadata}); + + const foundOldUser = await conn.db.collection("temp-users").findOne({_id: user._id}); + + const password = getOldEncryptionKey(foundOldUser); + + const IV = file.metadata.IV.buffer + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + const NEW_CIPHER_KEY = crypto.createHash('sha256').update(newKey).digest() + + const cipher = crypto.createCipheriv('aes256', NEW_CIPHER_KEY, IV); + + cipher.on("error", (e) => { + console.log("de", e); + }) + + readStream.pipe(decipher).pipe(cipher).pipe(writeStream); + + writeStream.on("finish", async(newFile) => { + + const imageCheck = imageChecker(filename); + + if (file.length < 15728640 && imageCheck) { + + try { + await createThumbnail(newFile, filename, user, newKey); + } catch (e) { + console.log("Cannot create thumbnail", e); + } + + resolve(); + + } else { + + resolve(); + } + + }) + + }) +} + + +const findFiles = async() => { + + const userListCursor = await conn.db.collection("users").find({}); + const userListCount = await conn.db.collection("users").find({}).count(); + const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + + progressBar.start(userListCount, 0); + + for await (const currentUser of userListCursor) { + + const currentUserID = currentUser._id; + + const newEncrpytionKey = getEncryptionKey(currentUser); + + const listCursor = await conn.db.collection("temp-fs.files").find({"metadata.owner": ObjectID(currentUserID)}); + + for await (const currentFile of listCursor) { + + await reencryptFile(currentFile, newEncrpytionKey, currentUser); + } + + progressBar.increment() + } + + progressBar.stop(); +} + +const generateEncryptionKeys = async(user) => { + + const userPassword = user.password; + const masterPassword = env.newKey; + + const randomKey = crypto.randomBytes(32); + + const iv = crypto.randomBytes(16); + const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); + const cipher = crypto.createCipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let encryptedText = cipher.update(randomKey); + encryptedText = Buffer.concat([encryptedText, cipher.final()]); + + const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest(); + const masterCipher = crypto.createCipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv); + let masterEncryptedText = masterCipher.update(encryptedText); + masterEncryptedText = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex"); + + user.privateKey = masterEncryptedText; + user.publicKey = iv.toString("hex"); + + return user; +} + +const getOldEncryptionKey = (user) => { + + const userPassword = user.password; + const masterEncryptedText = user.privateKey; + const masterPassword = env.key; + const iv = Buffer.from(user.publicKey, "hex"); + + const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); + const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest(); + + const unhexMasterText = Buffer.from(masterEncryptedText, "hex"); + const masterDecipher = crypto.createDecipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv) + let masterDecrypted = masterDecipher.update(unhexMasterText); + masterDecrypted = Buffer.concat([masterDecrypted, masterDecipher.final()]) + + let decipher = crypto.createDecipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let decrypted = decipher.update(masterDecrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + + return decrypted; +} + +const getEncryptionKey = (user) => { + + const userPassword = user.password; + const masterEncryptedText = user.privateKey; + const masterPassword = env.newKey; + const iv = Buffer.from(user.publicKey, "hex"); + + const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); + const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest(); + + const unhexMasterText = Buffer.from(masterEncryptedText, "hex"); + const masterDecipher = crypto.createDecipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv) + let masterDecrypted = masterDecipher.update(unhexMasterText); + masterDecrypted = Buffer.concat([masterDecrypted, masterDecipher.final()]) + + let decipher = crypto.createDecipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let decrypted = decipher.update(masterDecrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + + return decrypted; +} + +const findUsers = async() => { + + const listCursor = await conn.db.collection("temp-users").find({}); + const listCount = await conn.db.collection("temp-users").find({}).count(); + const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + + progressBar.start(listCount, 0); + + for await (const currentUser of listCursor) { + + try { + + const newUser = await generateEncryptionKeys(currentUser); + + await conn.db.collection("users").insertOne(newUser); + + progressBar.increment() + + } catch (e) { + console.log("e", e); + } + } + + progressBar.stop(); + +} + +const changeEncryptionPassword = async() => { + + console.log("Waiting For Database..."); + await waitForDatabase(); + console.log("Connected To Database...\n"); + + const userConfimation = await prompts({ + type: 'text', + message: "Warning: This will automatically run Backup-Database,\n" + + "overwriting the current Backup. And will also clear all file chunks\n" + + "other than the Data Backup. Then it will re-encrypt files and move them back over.\n" + + "(Optional) Create a manual Backup for additional safety.\n" + + "Would you like to continue? (Yes/No)", + name: "value" + }) + + if (!userConfimation.value || userConfimation.value.toLowerCase() !== "yes") { + + console.log("Exiting...") + process.exit() + return; + } + + console.log("\nGetting Old Password..."); + await getKey(); + console.log("Got Key\n") + + console.log("Getting New Password..."); + await getNewKey(); + console.log("Got New Key\n"); + + console.log("Creating Temporary Collection...\n"); + await createTempDirectory(); + console.log("Temporary Collection Completed\n") + + console.log("Created New Backup Sucessfully\n") + + console.log("Deleting Current Chunks Collection..."); + try { + await conn.db.collection("fs.chunks").drop(); + } catch (e) {} + console.log("Current Chunk Collection Deleted\n"); + + console.log("Deleting Current File Collection..."); + try { + await conn.db.collection("fs.files").drop(); + } catch (e) {} + console.log("Deleted Current File Collection\n") + + console.log("Delete Current Users..."); + try { + await conn.db.collection("users").drop(); + } catch (e) {} + console.log("Current Users Deleted\n"); + + console.log("Deleting Current Thumbnails..."); + try { + await conn.db.collection("thumbnails").drop(); + } catch (e) {} + console.log("Deleted Current Thumbnails\n"); + + console.log("Generating User Encryption Keys..."); + await findUsers(); + console.log("Generated User Encryption Keys\n") + + console.log("Moving Files By User..."); + await findFiles(); + console.log("Moved All Files...\n") + + process.exit(); +} + +changeEncryptionPassword(); \ No newline at end of file diff --git a/serverUtils/cleanDatabase.js b/serverUtils/cleanDatabase.js new file mode 100644 index 0000000..8393201 --- /dev/null +++ b/serverUtils/cleanDatabase.js @@ -0,0 +1,118 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const conn = mongoose.connection; +const ObjectID = require('mongodb').ObjectID +const prompts = require("prompts"); +const cliProgress = require('cli-progress'); +const createTempDirectory = require("./createTempDirectory"); + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const moveFileChunks = async(fileID, oldDatabaseChunks, newDatabaseChunks) => { + + const listChunkCursor = await conn.db.collection(oldDatabaseChunks).find({files_id: ObjectID(fileID)}); + + for await (const currentChunk of listChunkCursor) { + + await conn.db.collection(newDatabaseChunks).insertOne(currentChunk); + } +} + +const findFiles = async(oldDatabaseList, oldDatabaseChunks, newDatabaseChunks) => { + + const listCursor = await conn.db.collection(oldDatabaseList).find({}); + const listCount = await conn.db.collection(oldDatabaseList).find({}).count(); + const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + + progressBar.start(listCount, 0); + + for await (const currentFile of listCursor) { + + const fileID = currentFile._id; + await moveFileChunks(fileID, oldDatabaseChunks, newDatabaseChunks); + progressBar.increment(); + } + + progressBar.stop(); +} + +const cleanDatabase = async() => { + + console.log("Waiting For Database Connection..."); + await waitForDatabase(); + console.log("Connected To Database\n"); + + const userConfimation = await prompts({ + type: 'text', + message: "Warning: This will automatically run Backup-Database,\n" + + "overwriting the current Backup. And will also clear all file chunks\n" + + "other than the Data Backup. Then it will move only used file chunks\n" + + "over to the Main Database. If this process fails AFTER the Automatic Backup\n" + + "use the Restore-Database feature. \n" + + "Would you like to continue? (Yes/No)", + name: "value" + }) + + if (!userConfimation.value || userConfimation.value.toLowerCase() !== "yes") { + + console.log("Exiting...") + process.exit() + return; + } + + console.log("Creating Temporary Collection...\n"); + await createTempDirectory(); + console.log("Temporary Collection Completed\n") + + console.log("Created New Backup Sucessfully\n") + + console.log("Deleting Current Chunks Collection..."); + try { + await conn.db.collection("fs.chunks").drop(); + } catch (e) {} + console.log("Current Chunk Collection Deleted\n"); + + console.log("Moving Used Files..."); + await findFiles("temp-fs.files", "temp-fs.chunks", "fs.chunks"); + console.log("Moved All Used Files\n"); + + console.log("Creating File Chunks Index..."); + await conn.db.collection("fs.chunks").createIndex({ files_id: 1, n: 1 }, { unique: true }); + console.log("Created File Chunks Index\n"); + + console.log("Deleteing Current Transcoded Video Chunks Collection..."); + try { + await conn.db.collection("videos.chunks").drop(); + } catch (e) {} + console.log("Deleted Current Transcoded Video Chunks Collection\n") + + console.log("Moving Used Video Files..."); + await findFiles("temp-videos.files", "temp-videos.chunks", "videos.chunks"); + console.log("Moved All Used Video Files\n") + + console.log("Creating Transcoded Video Chunks Index..."); + await conn.db.collection("videos.chunks").createIndex({ files_id: 1, n: 1 }, { unique: true }); + console.log("Created Transcoded Video Chunks Index") + + process.exit(); + +} + +cleanDatabase(); \ No newline at end of file diff --git a/serverUtils/createIndexes.js b/serverUtils/createIndexes.js new file mode 100644 index 0000000..dc035d9 --- /dev/null +++ b/serverUtils/createIndexes.js @@ -0,0 +1,50 @@ +const mongoose = require("../src/db/mongoose"); +const conn = mongoose.connection; + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const createIndexes = async() => { + + console.log("Waiting For Database..."); + await waitForDatabase(); + console.log("Connected To Database\n"); + + console.log("Creating Indexes...") + await conn.db.collection("fs.files").createIndex({uploadDate: 1}); + await conn.db.collection("fs.files").createIndex({uploadDate: -1}); + await conn.db.collection("fs.files").createIndex({filename: 1}); + await conn.db.collection("fs.files").createIndex({filename: -1}); + await conn.db.collection("fs.files").createIndex({"metadata.owner": 1}); + + await conn.db.collection("folders").createIndex({createdAt: 1}) + await conn.db.collection("folders").createIndex({createdAt: -1}) + await conn.db.collection("folders").createIndex({name: 1}); + await conn.db.collection("folders").createIndex({name: -1}) + await conn.db.collection("folders").createIndex({owner: 1}) + + await conn.db.collection("thumbnails").createIndex({owner: 1}) + console.log("Indexes Created"); + + process.exit(); + +} + +createIndexes(); \ No newline at end of file diff --git a/serverUtils/createTempDirectory.js b/serverUtils/createTempDirectory.js new file mode 100644 index 0000000..7d14880 --- /dev/null +++ b/serverUtils/createTempDirectory.js @@ -0,0 +1,103 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const conn = mongoose.connection; +const cliProgress = require('cli-progress'); + +const clearTempDirectory = async() => { + + console.log("Removing Temporary Collections..."); + + try { + await conn.db.collection("temp-fs.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-fs.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-thumbnails").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-folders").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-videos.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-videos.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-users").drop(); + } catch (e) {} + + console.log("Removed Temporary Collections\n") +} + +const moveItem = async(oldPath, newPath) => { + + const listCursor = await conn.db.collection(oldPath).find({}); + const listCount = await conn.db.collection(oldPath).find({}).count(); + const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + + progressBar.start(listCount, 0); + + for await (const currentFile of listCursor) { + + await conn.db.collection(newPath).insertOne(currentFile); + progressBar.increment(); + } + + progressBar.stop(); +} + +const createTempDirectory = async() => { + + await clearTempDirectory(); + + console.log("Moving Files...") + await moveItem("fs.files", "temp-fs.files") + console.log("Moved All Files\n"); + + + console.log(`Moving File Chunks...`); + await moveItem("fs.chunks", "temp-fs.chunks"); + console.log("Moved All Chunks \n"); + + console.log("Creating Temp File Chunks Index..."); + await conn.db.collection("temp-fs.chunks").createIndex({ files_id: 1, n: 1 }, { unique: true }); + console.log("Created Temp File Chunks Index\n"); + + console.log(`Moving Thumbnails...`) + await moveItem("thumbnails", "temp-thumbnails"); + console.log("Moved All Thumbnails \n") + + + console.log(`Moving Folders...`); + await moveItem("folders", "temp-folders"); + console.log("All Folders Moved \n"); + + + console.log(`Moving Transcoded Video Files...`); + await moveItem("videos.files", "temp-videos.files") + console.log("All Transcoded Video Files Moved \n"); + + + console.log(`Moving Transcoded Video Chunks...`) + await moveItem("videos.chunks", "temp-videos.chunks") + console.log("All Transcoded Video Chunks Moved \n"); + + console.log("Creating Temp Transcoded Video Chunks Index..."); + await conn.db.collection("videos.chunks").createIndex({ files_id: 1, n: 1 }, { unique: true }) + console.log("Created Temp Transcoded Video Chunks Index \n"); + + + console.log(`Moving Users...`) + await moveItem("users", "temp-users") + console.log("All Users Moved\n"); +} + +module.exports = createTempDirectory; diff --git a/serverUtils/createThumbnailBuffer.js b/serverUtils/createThumbnailBuffer.js new file mode 100644 index 0000000..480e168 --- /dev/null +++ b/serverUtils/createThumbnailBuffer.js @@ -0,0 +1,82 @@ +const mongoose = require("../src/db/mongooseServerUtils") +const conn = mongoose.connection; +const crypto= require("crypto"); +const env = require("../src/enviroment/env"); +const Thumbnail = require("../src/models/thumbnail"); +const ObjectID = require('mongodb').ObjectID +const sharp = require("sharp"); +const concat = require("concat-stream") + +const createThumbnail = async(file, filename, user, newKey) => { + + return new Promise((resolve) => { + + try { + + const password = newKey; + + let CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255, + }) + + const readStream = bucket.openDownloadStream(ObjectID(file._id)) + + readStream.on("error", (e) => { + console.log("File service upload thumbnail error", e); + resolve(file); + }) + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, file.metadata.IV.buffer); + + decipher.on("error", (e) => { + console.log("File service upload thumbnail decipher error", e); + resolve(file) + }) + + const concatStream = concat(async(bufferData) => { + + const thumbnailIV = crypto.randomBytes(16); + + const thumbnailCipher = crypto.createCipheriv("aes256", CIPHER_KEY, thumbnailIV); + + bufferData = Buffer.concat([thumbnailIV, thumbnailCipher.update(bufferData), thumbnailCipher.final()]); + + const thumbnailModel = new Thumbnail({name: filename, owner: user._id, data: bufferData}); + + await thumbnailModel.save(); + + let updatedFile = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": file._id}, {"$set": {"metadata.hasThumbnail": true, "metadata.thumbnailID": thumbnailModel._id}}) + + updatedFile = updatedFile.value; + + updatedFile = {...updatedFile, metadata: {...updatedFile.metadata, hasThumbnail: true, thumbnailID: thumbnailModel._id}} + + resolve(updatedFile); + + }).on("error", (e) => { + console.log("File service upload concat stream error", e); + resolve(file); + }) + + const imageResize = sharp().resize(300).on("error", (e) => { + + console.log("resize error", e); + resolve(file); + }) + + readStream.pipe(decipher).pipe(imageResize).pipe(concatStream); + + + } catch (e) { + + console.log(e); + resolve(file); + } + + }) +} + +module.exports = createThumbnail; \ No newline at end of file diff --git a/serverUtils/deleteDatabase.js b/serverUtils/deleteDatabase.js new file mode 100644 index 0000000..f635232 --- /dev/null +++ b/serverUtils/deleteDatabase.js @@ -0,0 +1,81 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const prompts = require("prompts"); +const conn = mongoose.connection; + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const deleteTempDatabase = async() => { + + console.log("Waiting For Database..."); + await waitForDatabase(); + console.log("Connected To Database\n") + + const userConfimation = await prompts({ + type: 'text', + message: "Warning: This will delete all the data in the Main Database,\n" + + "this will not delete any data in the Database Backup.\n" + + "Would you like to continue? (Yes/No)", + name: "value" + }) + + if (!userConfimation.value || userConfimation.value.toLowerCase() !== "yes") { + + console.log("Exiting...") + process.exit() + return; + } + + console.log("Removing Collections..."); + + try { + await conn.db.collection("fs.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("fs.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("thumbnails").drop(); + } catch (e) {} + + try { + await conn.db.collection("folders").drop(); + } catch (e) {} + + try { + await conn.db.collection("videos.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("videos.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("users").drop(); + } catch (e) {} + + console.log("Removed Collections\n") + + process.exit(); +} + +deleteTempDatabase(); \ No newline at end of file diff --git a/serverUtils/deleteTempDatabase.js b/serverUtils/deleteTempDatabase.js new file mode 100644 index 0000000..2662cad --- /dev/null +++ b/serverUtils/deleteTempDatabase.js @@ -0,0 +1,79 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const prompts = require("prompts"); +const conn = mongoose.connection; + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const deleteTempDatabase = async() => { + + console.log("Waiting For Database..."); + await waitForDatabase(); + console.log("Connected To Database\n") + + const userConfimation = await prompts({ + type: 'text', + message: "Warning: Deleting the Backup Database cannot be undone,\n" + + "Would you like to continue? (Yes/No)", + name: "value" + }) + + if (!userConfimation.value || userConfimation.value.toLowerCase() !== "yes") { + + console.log("Exiting...") + process.exit() + return; + } + + console.log("Removing Temporary Collections..."); + + try { + await conn.db.collection("temp-fs.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-fs.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-thumbnails").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-folders").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-videos.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-videos.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("temp-users").drop(); + } catch (e) {} + + console.log("Removed Temporary Collections, Exiting..."); + process.exit(); +} + +deleteTempDatabase(); \ No newline at end of file diff --git a/serverUtils/restoreDatabase.js b/serverUtils/restoreDatabase.js new file mode 100644 index 0000000..bd28159 --- /dev/null +++ b/serverUtils/restoreDatabase.js @@ -0,0 +1,55 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const prompts = require("prompts"); +const restoreFromTempDirectory = require("./restoreFromTempDirectory"); +const conn = mongoose.connection; + +const waitForDatabase = () => { + + return new Promise((resolve, reject) => { + + if (conn.readyState !== 1) { + + conn.once("open", () => { + + resolve(); + + }) + + } else { + + resolve(); + } + + }) +} + +const restoreDatabase = async() => { + + const userConfimation = await prompts({ + type: 'text', + message: "Warning: This will delete ALL data," + + " other than the Data Backup created by CopyDatabase. \nMake sure to first run CopyDatabase, and backup" + + " your data, \nWould you like to continue? (Yes/No)", + name: "value" + }) + + if (!userConfimation.value || userConfimation.value.toLowerCase() !== "yes") { + + console.log("Exiting...") + process.exit() + return; + + } else { + + await waitForDatabase(); + + await restoreFromTempDirectory(); + + console.log("Finished Restoring Data, Exiting..."); + process.exit(); + } + +} + +restoreDatabase() + diff --git a/serverUtils/restoreFromTempDirectory.js b/serverUtils/restoreFromTempDirectory.js new file mode 100644 index 0000000..2749966 --- /dev/null +++ b/serverUtils/restoreFromTempDirectory.js @@ -0,0 +1,105 @@ +const mongoose = require("../src/db/mongooseServerUtils"); +const cliProgress = require('cli-progress'); +const conn = mongoose.connection; + +const clearDirectory = async() => { + + console.log("Removing Collections..."); + + try { + await conn.db.collection("fs.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("fs.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("thumbnails").drop(); + } catch (e) {} + + try { + await conn.db.collection("folders").drop(); + } catch (e) {} + + try { + await conn.db.collection("videos.files").drop(); + } catch (e) {} + + try { + await conn.db.collection("videos.chunks").drop(); + } catch (e) {} + + try { + await conn.db.collection("users").drop(); + } catch (e) {} + + console.log("Removed Collections\n") +} + +const moveItem = async(oldPath, newPath) => { + + const listCursor = await conn.db.collection(oldPath).find({}); + const listCount = await conn.db.collection(oldPath).find({}).count(); + const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); + + progressBar.start(listCount, 0); + + for await (const currentFile of listCursor) { + + await conn.db.collection(newPath).insertOne(currentFile); + progressBar.increment(); + } + + progressBar.stop(); +} + + +const restoreFromTempDirectory = async() => { + + console.log("\n"); + await clearDirectory(); + + console.log("Moving Files...") + await moveItem("temp-fs.files", "fs.files") + console.log("Moved All Files\n"); + + + console.log(`Moving File Chunks...`); + await moveItem("temp-fs.chunks","fs.chunks"); + console.log("Moved All Chunks \n"); + + console.log("Creating File Index..."); + await conn.db.collection("fs.chunks").createIndex({ files_id: 1, n: 1 }, { unique: true }) + console.log("File Index Created \n"); + + console.log(`Moving Thumbnails...`) + await moveItem("temp-thumbnails", "thumbnails"); + console.log("Moved All Thumbnails \n") + + + console.log(`Moving Folders...`); + await moveItem("temp-folders", "folders"); + console.log("All Folders Moved \n"); + + + console.log(`Moving Transcoded Video Files...`); + await moveItem("temp-videos.files", "videos.files") + console.log("All Transcoded Video Files Moved \n"); + + + console.log(`Moving Transcoded Video Chunks...`) + await moveItem("temp-videos.chunks", "videos.chunks") + console.log("All Transcoded Video Chunks Moved \n"); + + console.log("Creating Transcoded Video Chunks Index..."); + await conn.db.collection("videos.chunks").createIndex({ files_id: 1, n: 1 }, { unique: true }) + console.log("Created Transcoded Video Chunks Index \n"); + + + console.log(`Moving Users...`) + await moveItem("temp-users", "users") + console.log("All Users Moved\n"); +} + +module.exports = restoreFromTempDirectory; \ No newline at end of file diff --git a/src/actions/addOptions.js b/src/actions/addOptions.js new file mode 100644 index 0000000..c849504 --- /dev/null +++ b/src/actions/addOptions.js @@ -0,0 +1,9 @@ +export const showAddOptions = (show) => ({ + type: "SHOW_ADD_OPTIONS", + show +}) + +export const showAddOptions2 = (show) => ({ + type: "SHOW_ADD_OPTIONS2", + show +}) \ No newline at end of file diff --git a/src/actions/auth.js b/src/actions/auth.js new file mode 100755 index 0000000..fdbd909 --- /dev/null +++ b/src/actions/auth.js @@ -0,0 +1,163 @@ +import {setLoginFailed} from "./main"; +import {history} from "../routers/AppRouter" +import {resetUpload} from "./uploads" +import axios from "axios"; +import env from "../enviroment/envFrontEnd"; + +const currentURL = env.url; + +export const login = (id) => ({ + type: "LOGIN", + id +}) + +export const logout = () => ({ + type: "LOGOUT" +}) + +export const startLogin = (email, password, currentRoute) => { + + return (dispatch) => { + + const dt = {email, password}; + + axios.post(currentURL+"/user-service/login", dt).then((response) => { + + const token = response.data.token; + const id = response.data.user._id; + + window.localStorage.setItem("token", token); + + dispatch(setLoginFailed(false)) + dispatch(login(id)); + history.push(currentRoute); + + }).catch((err) => { + dispatch(setLoginFailed("Incorrect Email or Password")) + console.log(err); + }) + } +} + +export const startCreateAccount = (email, password) => { + + return (dispatch) => { + + + const dt = {email, password}; + axios.post(currentURL+"/user-service/create", dt).then((response) => { + + const token = response.data.token; + const id = response.data.user._id; + + window.localStorage.setItem("token", token); + + dispatch(setLoginFailed(false)) + dispatch(login(id)); + history.push("/home"); + + }).catch((err) => { + + console.log(err); + + if (err.response) { + + const errStatus = err.response.status; + + if (errStatus === 401) { + + dispatch(setLoginFailed("Create Blocked By Admin")) + + } else { + + dispatch(setLoginFailed("Duplicate Email, or Invalid Password")) + } + + } else { + + dispatch(setLoginFailed("Duplicate Email, or Invalid Password")) + } + }) + } +} + +export const startLoginCheck = (token, currentRoute) => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + token} + }; + + axios.get(currentURL+"/user-service/user", config).then((response) => { + + const id = response.data._id; + + dispatch(setLoginFailed(false)) + dispatch(login(id)) + history.push(currentRoute); + + }).catch((err) => { + + console.log("login check error", err); + window.localStorage.removeItem("token") + dispatch(setLoginFailed("Login Expired")) + // history.push("/login") + }) + } +} + +export const startLogoutAll = () => { + + return (dispatch) => { + + const token = window.localStorage.getItem("token") + + const config = { + headers: {'Authorization': "Bearer " + token} + }; + + axios.post(currentURL+"/user-service/logout-all", undefined,config).then(() => { + + window.localStorage.removeItem("token") + + dispatch(resetUpload()) + dispatch(setLoginFailed(false)) + dispatch(logout()) + + history.push("/") + + }).catch((err) => { + console.log(err); + }) + + } + +} + +export const startLogout = () => { + + return (dispatch) => { + + const token = window.localStorage.getItem("token") + + const config = { + headers: {'Authorization': "Bearer " + token} + }; + + axios.post(currentURL+"/user-service/logout", undefined,config).then(() => { + + window.localStorage.removeItem("token") + + dispatch(resetUpload()) + dispatch(setLoginFailed(false)) + dispatch(logout()) + + history.push("/") + + }).catch((err) => { + console.log(err); + }) + + } +} \ No newline at end of file diff --git a/src/actions/files.js b/src/actions/files.js new file mode 100644 index 0000000..8e3e452 --- /dev/null +++ b/src/actions/files.js @@ -0,0 +1,216 @@ +import {addUpload, editUpload} from "./uploads"; +import {loadMoreItems, setLoading} from "./main" +import {resetSelected} from "./selectedItem"; +import {addQuickFile, startSetQuickFiles} from "./quickFiles"; +import {startSetStorage} from "./storage" +import uuid from "uuid"; +import axios from "axios"; +import env from "../enviroment/envFrontEnd"; +const http = require('http'); +const https = require('https'); + +const currentURL = env.url; + +export const setFiles = (files) => ({ + type: "SET_FILES", + files +}) + +export const editFile = (id, file) => ({ + type: "EDIT_FILE", + id, + file +}) + +export const editFileMetadata = (id, metadata) => ({ + type: "EDIT_FILE_METADATA", + id, + metadata +}) + +export const startSetFiles = (parent="/", sortby="DEFAULT", search="") => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + let limit = window.localStorage.getItem("list-size") || 50 + limit = parseInt(limit) + + dispatch(setFiles([])) + + axios.get(currentURL +`/file-service/list?parent=${parent}&sortby=${sortby}&search=${search}&limit=${limit}`, config).then((results) => { + + dispatch(setLoading(true)) + dispatch(setFiles(results.data)) + dispatch(setLoading(false)) + + if (results.data.length === limit) { + dispatch(loadMoreItems(true)) + } else { + dispatch(loadMoreItems(false)) + } + + }).catch((err) => { + console.log(err) + }) + } +} + +export const loadMoreFiles = (files) => ({ + type: "LOAD_MORE_FILES", + files +}) + +export const startLoadMoreFiles = (parent="/", sortby="DEFAULT", search="", startAtDate, startAtName) => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + let limit = window.localStorage.getItem("list-size") || 50 + limit = parseInt(limit) + + axios.get(currentURL +`/file-service/list?limit=${limit}&parent=${parent}&sortby=${sortby}&search=${search}&startAt=${true}&startAtDate=${startAtDate}&startAtName=${startAtName}`, config).then((results) => { + console.log("load more results", results); + + if (results.data.length !== limit) { + + dispatch(loadMoreItems(false)) + + } else { + dispatch(loadMoreItems(true)) + } + + dispatch(loadMoreFiles(results.data)) + + }).catch((err) => { + console.log(err) + }) + } +} + +export const addFile = (file) => ({ + type: "ADD_FILE", + file +}) + +export const startAddFile = (uploadInput, parent, parentList) => { + + return (dispatch, getState) => { + + // Store the parent, incase it changes. + const prevParent = getState().parent.parent; + + for (let i = 0; i < uploadInput.files.length; i++) { + const currentFile = uploadInput.files[i]; + const currentID = uuid(); + + const CancelToken = axios.CancelToken; + const source = CancelToken.source(); + + const httpAgent = new http.Agent({ keepAlive: true }); + const httpsAgent = new https.Agent({ keepAlive: true }); + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token"), + httpAgent, + httpsAgent, + 'Content-Type': 'multipart/form-data', + 'Transfere-Encoding': "chunked"}, + onUploadProgress: (progressEvent) => { + + const currentProgress = Math.round(((progressEvent.loaded / progressEvent.total) * 100)); + + if (currentProgress !== 100) { + + dispatch(editUpload(currentID, currentProgress)) + } + }, + cancelToken: source.token + }; + + dispatch(addUpload({id: currentID, progress: 0, name: currentFile.name, completed: false, source, canceled: false})) + + const data = new FormData(); + + data.append('filename', currentFile.name); + data.append("parent", parent) + data.append("parentList", parentList) + data.append("currentID", currentID) + data.append("size", currentFile.size) + data.append('file', currentFile); + + axios.post(currentURL +'/file-service/upload', data, config) + .then(function (response) { + + const currentParent = getState().parent.parent; + // This can change by the time the file uploads + if (prevParent === currentParent) { + dispatch(addFile(response.data)); + } + + dispatch(addQuickFile(response.data)) + dispatch(editUpload(currentID, 100, true)) + dispatch(resetSelected()) + dispatch(startSetStorage()) + + }) + .catch(function (error) { + console.log(error); + }); + + } + } +} + +export const removeFile = (id) => ({ + type: "REMOVE_FILE", + id +}) + +export const startRemoveFile = (id) => { + + return (dispatch) => { + + const headers = {'Authorization': "Bearer " + window.localStorage.getItem("token")}; + const data = {id} + + axios.delete(currentURL+"/file-service/remove", { + headers, + data + }).then(() => { + dispatch(removeFile(id)) + dispatch(startSetStorage()) + dispatch(startSetQuickFiles()); + }).catch((err) => { + console.log(err) + }) + } +} + + +export const startRenameFile = (id, title) => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + const data = {id, title} + + axios.patch(currentURL+"/file-service/rename", data, config).then(() => { + + dispatch(editFile(id, {filename: title})) + + }).catch((err) => { + console.log(err) + }) + + } +} diff --git a/src/actions/filter.js b/src/actions/filter.js new file mode 100644 index 0000000..c2a8df5 --- /dev/null +++ b/src/actions/filter.js @@ -0,0 +1,29 @@ +export const setSortBy = (sortBy) => ({ + type: "SET_SORT_BY", + sortBy +}) + +export const setSearch = (search) => ({ + type: "SET_SEARCH", + search +}) + +export const resetSearch = () => ({ + type: "RESET_SEARCH" +}) + +export const enableListView = () => ({ + type: "ENABLE_LIST_VIEW" +}) + +export const disableListView = () => ({ + type: "DISABLE_LIST_VIEW" +}) + +export const setCurrentlySearching = () => ({ + type: "SET_CURRENTLY_SEARCHING" +}) + +export const resetCurrentlySearching = () => ({ + type: "RESET_CURRENTLY_SEARCHING" +}) \ No newline at end of file diff --git a/src/actions/folders.js b/src/actions/folders.js new file mode 100644 index 0000000..2ec5dce --- /dev/null +++ b/src/actions/folders.js @@ -0,0 +1,115 @@ +import {startSetQuickFiles} from "./quickFiles"; +import env from "../enviroment/envFrontEnd"; +const axios = require("axios"); + +const currentURL = env.url; + +export const addFolder = (folder) => ({ + type: "ADD_FOLDER", + folder +}) + +export const startAddFolder = (name, owner, parent, parentList) => { + + return (dispatch) => { + + if (name.length === 0) { + return; + } + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + const body = {name, parent, owner, parentList}; + + axios.post(currentURL+"/folder-service/upload", body, config).then((response) => { + + const folder = response.data; + + dispatch(addFolder(folder)) + + }).catch((err) => { + console.log(err) + }) + } +} + +export const setFolders = (folders) => ({ + type: "SET_FOLDERS", + folders +}) + +export const startSetFolders = (parent = "/", sortby="DEFAULT", search="") => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + dispatch(setFolders([])) + + axios.get(currentURL+`/folder-service/list?parent=${parent}&sortby=${sortby}&search=${search}`, config).then((response) => { + + const folders = response.data; + dispatch(setFolders(folders)) + + }).catch((err) => { + console.log(err); + }) + } +} + +export const removeFolder = (id) => ({ + type: "REMOVE_FOLDER", + id +}) + +export const startRemoveFolder = (id, parentList) => { + + return (dispatch) => { + + const data = {id, parentList}; + + const headers = {'Authorization': "Bearer " + window.localStorage.getItem("token")} + + axios.delete(currentURL+`/folder-service/remove`, { + headers, + data + }).then((response) => { + + dispatch(removeFolder(id)); + dispatch(startSetQuickFiles()) + + }).catch((err) => { + console.log(err); + }) + } +} + +export const editFolder = (id, folder) => ({ + type: "EDIT_FOLDER", + id, + folder +}) + +export const startRenameFolder = (id, title) => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + const data = {id, title} + + axios.patch(currentURL+"/folder-service/rename", data, config).then((response) => { + + dispatch(editFolder(id, {name: title})) + + }).catch((err) => { + console.log(err) + }) + } +} diff --git a/src/actions/main.js b/src/actions/main.js new file mode 100644 index 0000000..531fe75 --- /dev/null +++ b/src/actions/main.js @@ -0,0 +1,38 @@ +export const showSideBar = () => ({ + type: "SHOW_SIDEBAR" +}) + +export const hideSideBar = () => ({ + type: "HIDE_SIDEBAR" +}) + +export const goneSideBar = () => ({ + type: "GONE_SIDEBAR" +}) + +export const resetItems = () => ({ + type: "RESET_ITEMS" +}) + +export const showUploader = () => ({ + type: "SHOW_UPLOADER" +}) + +export const hideUploader = () => ({ + type: "HIDE_UPLOADER" +}) + +export const loadMoreItems = (load) => ({ + type: "LOAD_MORE_ITEMS", + load +}) + +export const setLoading = (load) => ({ + type: "SET_LOADING", + load +}) + +export const setLoginFailed = (message) => ({ + type: "SET_LOGIN_FAILED", + message +}) diff --git a/src/actions/mover.js b/src/actions/mover.js new file mode 100644 index 0000000..d295911 --- /dev/null +++ b/src/actions/mover.js @@ -0,0 +1,10 @@ +export const setMoverID = (id, parent, isFile) => ({ + type: "SET_MOVER_ID", + id, + parent, + isFile +}) + +export const resetMoverID = () => ({ + type: "RESET_MOVER_ID" +}) \ No newline at end of file diff --git a/src/actions/parent.js b/src/actions/parent.js new file mode 100644 index 0000000..7c974d5 --- /dev/null +++ b/src/actions/parent.js @@ -0,0 +1,83 @@ +import axios from "axios"; +import env from "../enviroment/envFrontEnd"; + +const currentURL = env.url; + +export const setParent = (parent="/") => ({ + type: "SET_PARENT", + parent +}) + +export const addParentList = (parent, name) => ({ + type: "ADD_PARENT_LIST", + parent, + name +}) + +export const setParentList = (parentList, parentNameList) => ({ + type: "SET_PARENT_LIST", + parentList, + parentNameList +}) + +export const startSetParentList = (id) => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + axios.get(currentURL+`/folder-service/subfolder-list/?id=${id}`, config).then((response) => { + + const parentList = response.data.folderIDList; + const parentNameList = response.data.folderNameList; + + dispatch(setParentList(parentList, parentNameList)) + + }).catch((err) => { + console.log(err); + }) + + } +} + +export const adjustParentList = (parentList, parentNameList) => ({ + type: "ADJUST_PARENT_LIST", + parentList, + parentNameList +}) + +export const startAdjustParentList = (id, parentList, parentNameList) => { + + return (dispatch) => { + + const adjustedParentList = []; + const adjustedParentNameList = []; + + for (let i = 0; i < parentList.length; i++) { + + const currentID = parentList[i]; + const currentName = parentNameList[i]; + + adjustedParentList.push(currentID) + adjustedParentNameList.push(currentName) + + if (currentID === id) { + break; + } + } + + dispatch(adjustParentList(adjustedParentList, adjustedParentNameList)); + + } +} + +export const addParentNameList = (name) => ({ + type: "ADD_PARENT_NAME_LIST", + name +}) + +export const resetParentList = () => ({ + type: "RESET_PARENT_LIST" +}) \ No newline at end of file diff --git a/src/actions/photoViewer.js b/src/actions/photoViewer.js new file mode 100644 index 0000000..2fddfd6 --- /dev/null +++ b/src/actions/photoViewer.js @@ -0,0 +1,8 @@ +export const setPhotoID = (id) => ({ + type: "SET_PHOTO_ID", + id +}) + +export const resetPhotoID = () => ({ + type: "RESET_PHOTO_ID" +}) \ No newline at end of file diff --git a/src/actions/popupFile.js b/src/actions/popupFile.js new file mode 100644 index 0000000..fd03c6c --- /dev/null +++ b/src/actions/popupFile.js @@ -0,0 +1,8 @@ +export const setPopupFile = (file) => ({ + type: "SET_POPUP_FILE", + file +}) + +export const hidePopup = () => ({ + type: "HIDE_POPUP" +}) \ No newline at end of file diff --git a/src/actions/quickFiles.js b/src/actions/quickFiles.js new file mode 100644 index 0000000..3577950 --- /dev/null +++ b/src/actions/quickFiles.js @@ -0,0 +1,32 @@ +import axios from "axios"; +import env from "../enviroment/envFrontEnd"; + +const currentURL = env.url; + +export const setQuickFiles = (files) => ({ + type: "SET_QUICK_FILES", + files +}) + +export const startSetQuickFiles = () => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + axios.get(currentURL +`/file-service/quick-list`, config).then((results) => { + + dispatch(setQuickFiles(results.data)) + + }).catch((err) => { + console.log(err) + }) + } +} + +export const addQuickFile = (file) => ({ + type: "ADD_QUICK_FILE", + file +}) \ No newline at end of file diff --git a/src/actions/routes.js b/src/actions/routes.js new file mode 100644 index 0000000..cac9940 --- /dev/null +++ b/src/actions/routes.js @@ -0,0 +1,8 @@ +export const setCurrentRoute = (route) => ({ + type: "SET_CURRENT_ROUTE", + route +}) + +export const resetCurrentRoute = () => ({ + type: "RESET_CURRENT_ROUTE" +}) \ No newline at end of file diff --git a/src/actions/selectedItem.js b/src/actions/selectedItem.js new file mode 100644 index 0000000..a97ccc9 --- /dev/null +++ b/src/actions/selectedItem.js @@ -0,0 +1,92 @@ +import axios from "axios"; +import env from "../enviroment/envFrontEnd"; + +const currentURL = env.url; + +export const setSelectedItem = (selectedItem) => ({ + type: "SET_SELECTED_ITEM", + selectedItem +}) + +export const startSetSelectedItem = (id, file, fromQuickItems) => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + const currentDate = Date.now(); + + dispatch(setLastSelected(currentDate)); + + if (!fromQuickItems) { + dispatch(setSelected(id)) + } else { + dispatch(setSelected("quick-"+id)) + } + + if (file) { + + axios.get(currentURL +`/file-service/info/${id}`, config).then((results) => { + + const {filename: name, length: size, uploadDate: date, parentName: location, metadata, _id: id} = results.data; + + dispatch(setSelectedItem({name, size, date, file, location, transcoded: metadata.transcoded, isVideo: metadata.isVideo, id, linkType: metadata.linkType, link: metadata.link})) + + }).catch((err) => { + console.log(err) + }) + + } else { + + axios.get(currentURL +`/folder-service/info/${id}`, config).then((results) => { + + const {name, 0: size, createdAt: date, parentName: location} = results.data; + + dispatch(setSelectedItem({name, size, date, file, location})) + + }).catch((err) => { + console.log(err) + }) + } + } +} + +export const editSelectedItem = (item) => ({ + type: "EDIT_SELECTED_ITEM", + item +}) + +export const resetSelectedItem = () => ({ + type: "RESET_SELECTED_ITEM" +}) + +export const resetSelected = () => ({ + type: "RESET_SELECTED" +}) + +export const setLastSelected = (lastSelected) => ({ + type: "SET_LAST_SELECTED", + lastSelected +}) + +export const setRightSelected = (selected) => ({ + type: "SET_RIGHT_SELECTED", + selected +}) + +export const setShareSelected = (selected) => ({ + type: "SET_SHARE_SELECTED", + selected +}) + +export const editShareSelected = (selected) => ({ + type: "EDIT_SHARE_SELECTED", + selected +}) + +export const setSelected = (selected) => ({ + type: "SET_SELECTED", + selected +}) \ No newline at end of file diff --git a/src/actions/settings.js b/src/actions/settings.js new file mode 100644 index 0000000..06a86fd --- /dev/null +++ b/src/actions/settings.js @@ -0,0 +1,7 @@ +export const showSettings = () => ({ + type: "SHOW_SETTINGS" +}) + +export const hideSettings = () => ({ + type: "HIDE_SETTINGS" +}) \ No newline at end of file diff --git a/src/actions/storage.js b/src/actions/storage.js new file mode 100644 index 0000000..8805bbe --- /dev/null +++ b/src/actions/storage.js @@ -0,0 +1,29 @@ +import axios from "axios"; +import env from "../enviroment/envFrontEnd"; + +const currentURL = env.url; + +export const setStorage = (info) => ({ + type: "SET_STORAGE", + info +}) + +export const startSetStorage = () => { + + return (dispatch) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + axios.get(currentURL +`/storage-service/info`, config).then((results) => { + + dispatch(setStorage(results.data)) + + }).catch((err) => { + console.log(err) + }) + + } + +} \ No newline at end of file diff --git a/src/actions/uploads.js b/src/actions/uploads.js new file mode 100644 index 0000000..15b8817 --- /dev/null +++ b/src/actions/uploads.js @@ -0,0 +1,40 @@ +export const addUpload = (upload) => ({ + type: "ADD_UPLOAD", + upload +}) + +export const resetUpload = () => ({ + type: "RESET_UPLOADS" +}) + +export const editUpload = (id, progress, completed = false) => ({ + type: "EDIT_UPLOAD", + id, + progress, + completed +}) + +export const cancelUpload = (id) => ({ + type: "CANCEL_UPLOAD", + id +}) + +export const startCancelAllUploads = (uploads) => { + + return (dispatch) => { + + for (let i = 0; i < uploads.length; i++) { + + const upload = uploads[i]; + + if (!upload.completed) { + + upload.source.cancel("All uploads cancelling") + + dispatch(cancelUpload(upload.id)) + } + } + + dispatch(resetUpload()); + } +} \ No newline at end of file diff --git a/src/app.js b/src/app.js new file mode 100755 index 0000000..7f23880 --- /dev/null +++ b/src/app.js @@ -0,0 +1,26 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import {Provider} from "react-redux" +import configStore from "./store/configureStore" +import AppRouter from "./routers/AppRouter" +import "normalize.css/normalize.css" +import "./styles/styles.scss"; +import "core-js/stable"; +import "regenerator-runtime/runtime"; +import 'react-circular-progressbar/dist/styles.css'; + + + +const store = configStore(); + +const jsxWrapper = ( + + + + +) + +ReactDOM.render(jsxWrapper, document.getElementById("app")) + + + diff --git a/src/components/ContextItem/ContextItem.js b/src/components/ContextItem/ContextItem.js new file mode 100644 index 0000000..b35378d --- /dev/null +++ b/src/components/ContextItem/ContextItem.js @@ -0,0 +1,11 @@ +import React from "react"; + +const ContextItem = (props) => ( +
+ +

{props.title}

+
+) + +export default ContextItem; + diff --git a/src/components/ContextItem/index.js b/src/components/ContextItem/index.js new file mode 100644 index 0000000..9fee57f --- /dev/null +++ b/src/components/ContextItem/index.js @@ -0,0 +1,83 @@ +import ContextItem from "./ContextItem"; +import {setRightSelected, setSelected, setShareSelected} from "../../actions/selectedItem" +import {startRemoveFile, startRenameFile} from "../../actions/files" +import {setMoverID} from "../../actions/mover"; +import Swal from "sweetalert2" +import {connect} from "react-redux" +import React from "react"; + +class ContextItemContainer extends React.Component { + + constructor(props) { + super(props); + } + + onClick = async(e) => { + e.stopPropagation(); + + this.props.dispatch(setSelected("")) + this.props.dispatch(setRightSelected("")) + + const itemTitle = this.props.title; + + if (itemTitle === "Download") { + + this.props.downloadFile(this.props._id, undefined, false, true) + + } else if (itemTitle === "Delete") { + + Swal.fire({ + title: 'Confirm Deletion', + text: "You cannot undo this action", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, delete' + }).then((result) => { + if (result.value) { + this.props.dispatch(startRemoveFile(this.props._id)) + } + }) + + } else if (itemTitle === "Rename") { + + let inputValue = this.props.filename; + + const { value: folderName} = await Swal.fire({ + title: 'Enter A File Name', + input: 'text', + inputValue: inputValue, + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return 'Please Enter a Name' + } + } + }) + + if (folderName === undefined || folderName === null) { + + return; + } + + this.props.dispatch(startRenameFile(this.props._id, folderName)) + + } else if (itemTitle === "Share") { + + this.props.dispatch(setShareSelected({...this.props.file})) + + } else if (itemTitle === "Move") { + + this.props.dispatch(setMoverID(this.props._id, this.props.parent, this.props.isFile)); + } + } + + render() { + return + } +} + + +export default connect()(ContextItemContainer); + diff --git a/src/components/ContextItemFolder/ContextItemFolder.js b/src/components/ContextItemFolder/ContextItemFolder.js new file mode 100644 index 0000000..b35378d --- /dev/null +++ b/src/components/ContextItemFolder/ContextItemFolder.js @@ -0,0 +1,11 @@ +import React from "react"; + +const ContextItem = (props) => ( +
+ +

{props.title}

+
+) + +export default ContextItem; + diff --git a/src/components/ContextItemFolder/index.js b/src/components/ContextItemFolder/index.js new file mode 100644 index 0000000..0b3a4b8 --- /dev/null +++ b/src/components/ContextItemFolder/index.js @@ -0,0 +1,83 @@ +import ContextItemFolder from "./ContextItemFolder"; +import {startRemoveFolder, startRenameFolder} from "../../actions/folders" +import {setRightSelected, setSelected, setShareSelected} from "../../actions/selectedItem"; +import {setMoverID} from "../../actions/mover" +import Swal from "sweetalert2" +import {connect} from "react-redux" +import React from "react"; + +class ContextItemFolderContainer extends React.Component { + + constructor(props) { + + super(props); + } + + onClick = async(e) => { + e.stopPropagation(); + + this.props.dispatch(setSelected("")) + this.props.dispatch(setRightSelected("")) + + const itemTitle = this.props.title; + + if (itemTitle === "Download") { + + this.props.downloadFile(this.props._id, undefined, false, true) + + } else if (itemTitle === "Delete") { + + Swal.fire({ + title: 'Confirm Deletion', + text: "You cannot undo this action", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, delete' + }).then((result) => { + if (result.value) { + this.props.dispatch(startRemoveFolder(this.props._id, [...this.props.parentList, this.props._id])) + } + }) + + } else if (itemTitle === "Rename") { + + let inputValue = this.props.name; + + const { value: folderName} = await Swal.fire({ + title: 'Enter Folder Name', + input: 'text', + inputValue: inputValue, + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return 'Please Enter a Name' + } + } + }) + + if (folderName === undefined || folderName === null) { + + return; + } + + this.props.dispatch(startRenameFolder(this.props._id, folderName)) + + } else if (itemTitle === "Share") { + + this.props.dispatch(setShareSelected({...this.props.file})) + + } else if (itemTitle === "Move") { + + this.props.dispatch(setMoverID(this.props._id, this.props.parent, this.props.isFile)); + } + } + + render() { + + return + } +} + +export default connect()(ContextItemFolderContainer); \ No newline at end of file diff --git a/src/components/ContextMenu/ContextMenu.js b/src/components/ContextMenu/ContextMenu.js new file mode 100644 index 0000000..1d82ed4 --- /dev/null +++ b/src/components/ContextMenu/ContextMenu.js @@ -0,0 +1,20 @@ +import ContextItem from "../ContextItem"; +import React from "react"; + +const ContextMenu = React.forwardRef((props, ref) => ( + +
+ + + + + {props.quickFile ? undefined : + + } + + +
+)) + +export default ContextMenu + diff --git a/src/components/ContextMenu/index.js b/src/components/ContextMenu/index.js new file mode 100644 index 0000000..9f28a21 --- /dev/null +++ b/src/components/ContextMenu/index.js @@ -0,0 +1,37 @@ +import ContextMenu from "./ContextMenu"; +import {setRightSelected, setSelected} from "../../actions/selectedItem" +import {connect} from "react-redux" +import React from "react"; + +class ContextMenuContainer extends React.Component { + + constructor(props) { + super(props); + + this.wrapperRef = React.createRef(); + } + + handleClickOutside = (e) => { + + if (this.wrapperRef && !this.wrapperRef.current.contains(e.target)) { + + this.props.dispatch(setSelected("")); + this.props.dispatch(setRightSelected("")); + } + } + + componentDidMount = () => { + document.addEventListener('mousedown', this.handleClickOutside); + } + + componentWillUnmount = () => { + document.removeEventListener('mousedown', this.handleClickOutside); + } + + render() { + + return + } +} + +export default connect()(ContextMenuContainer); \ No newline at end of file diff --git a/src/components/ContextMenuFolder/ContextMenuFolder.js b/src/components/ContextMenuFolder/ContextMenuFolder.js new file mode 100644 index 0000000..f6efbe5 --- /dev/null +++ b/src/components/ContextMenuFolder/ContextMenuFolder.js @@ -0,0 +1,14 @@ +import ContextItemFolder from "../ContextItemFolder"; +import React from "react"; + +const ContextMenuFolder = React.forwardRef((props, ref) => ( + +
+ + + +
+)) + +export default ContextMenuFolder; + diff --git a/src/components/ContextMenuFolder/index.js b/src/components/ContextMenuFolder/index.js new file mode 100644 index 0000000..352b51d --- /dev/null +++ b/src/components/ContextMenuFolder/index.js @@ -0,0 +1,37 @@ +import ContextMenuFolder from "./ContextMenuFolder"; +import {setRightSelected, setSelected} from "../../actions/selectedItem"; +import {connect} from "react-redux" +import React from "react"; + +class ContextMenuFolderContainer extends React.Component { + + constructor(props) { + super(props); + + this.wrapperRef = React.createRef() + } + + handleClickOutside = (e) => { + + if (this.wrapperRef && !this.wrapperRef.current.contains(e.target)) { + + this.props.dispatch(setSelected("")); + this.props.dispatch(setRightSelected("")); + } + } + + componentDidMount = () => { + document.addEventListener('mousedown', this.handleClickOutside); + } + + componentWillUnmount = () => { + document.removeEventListener('mousedown', this.handleClickOutside); + } + + render() { + + return + } +} + +export default connect()(ContextMenuFolderContainer); \ No newline at end of file diff --git a/src/components/Dataform/DataForm.js b/src/components/Dataform/DataForm.js new file mode 100644 index 0000000..804b71c --- /dev/null +++ b/src/components/Dataform/DataForm.js @@ -0,0 +1,52 @@ +import FileItem from ".././FileItem" +import FolderItem from ".././FolderItem" +import Spacer from ".././Spacer"; +import React from "react"; + +const DataForm = (props) => ( + +
+ +
+ + {props.folders.length === 0 ? + + (undefined) + : + (
+ {props.folders.map((folder) => + ())} +
)} +
+ + {props.files.length !== 0 ? : undefined} + +
+ + {props.files.length === 0 ? + + (undefined) + : + (
+ + {props.files.map((file) => + ( ))} + +
)} +
+ +
+) + +export default DataForm; \ No newline at end of file diff --git a/src/components/Dataform/index.js b/src/components/Dataform/index.js new file mode 100644 index 0000000..8192ca0 --- /dev/null +++ b/src/components/Dataform/index.js @@ -0,0 +1,26 @@ +import DataForm from "./DataForm"; +import {connect} from "react-redux"; +import React from "react"; + +class DataFormContainer extends React.Component { + + constructor(props) { + + super(props); + } + + render() { + + return + + } +} + +const mapStateToProp = (state) => ({ + files: state.files, + folders: state.folders, + selected: state.selectedItem.selected, + resetItems: state.main.resetItems +}) + +export default connect(mapStateToProp)(DataFormContainer); \ No newline at end of file diff --git a/src/components/DownloadPage/DownloadPage.js b/src/components/DownloadPage/DownloadPage.js new file mode 100644 index 0000000..47f0d57 --- /dev/null +++ b/src/components/DownloadPage/DownloadPage.js @@ -0,0 +1,35 @@ +import Spinner from ".././SpinnerLogin"; +import React from "react"; + +const DownloadPage = ({download,state}) => { + + if (state.size === "" && !state.error) { + + return
+ +
+ } + + return ( + +
+ + {!state.error ? + + (
+

{state.title}

+

Type: {state.type}

+

Size: {state.size}

+ +
) + : +
+

Unauthorized/Not Found Download

+
+ } + +
) +} + +export default DownloadPage; + diff --git a/src/components/DownloadPage/index.js b/src/components/DownloadPage/index.js new file mode 100644 index 0000000..a2d8ac0 --- /dev/null +++ b/src/components/DownloadPage/index.js @@ -0,0 +1,89 @@ +import DownloadPage from "./DownloadPage"; +import capitalize from "../../utils/capitalize"; +import env from "../../enviroment/envFrontEnd"; +import axios from "axios"; +import bytes from "bytes"; +import React from "react"; + +const currentURL = env.url; + +class DownloadPageContainer extends React.Component { + + constructor(props) { + super(props); + + this.state = { + error: false, + title: "", + type: "", + size: "", + } + } + + getFileExtension = (filename) => { + + const filenameSplit = filename.split("."); + + if (filenameSplit.length > 1) { + + const extension = filenameSplit[filenameSplit.length - 1] + + return extension.toUpperCase(); + + } else { + + return "Unknown" + } + + } + + componentDidMount = () => { + + const _id = this.props.match.params.id; + const tempToken = this.props.match.params.tempToken + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + axios.get(currentURL +`/file-service/public/info/${_id}/${tempToken}`, config).then((results) => { + + const data = results.data; + + const title = capitalize(data.filename); + const size = bytes(data.length); + const type = this.getFileExtension(title); + + this.setState(() => ({ + ...this.state, + title, + type, + size + })) + + }).catch((err) => { + console.log(err) + this.setState(() => ({...this.state, error: true})) + }) + } + + download = () => { + + const _id = this.props.match.params.id; + const tempToken = this.props.match.params.tempToken + const finalUrl = currentURL + `/file-service/public/download/${_id}/${tempToken}` + + const link = document.createElement('a'); + document.body.appendChild(link); + link.href = finalUrl; + link.setAttribute('type', 'hidden'); + link.click(); + } + + render() { + + return + } +} + +export default DownloadPageContainer; \ No newline at end of file diff --git a/src/components/FileItem/FileItem.js b/src/components/FileItem/FileItem.js new file mode 100644 index 0000000..bff1cdc --- /dev/null +++ b/src/components/FileItem/FileItem.js @@ -0,0 +1,64 @@ +import ContextMenu from "../ContextMenu"; +import capitalize from "../../utils/capitalize"; +import bytes from "bytes"; +import moment from "moment"; +import React from "react" + + +const FileItem = (props) => { + + const wrapperClassname = props.getWrapperClassname() + + return ( +
+ + {props.listView ? + +
{props.fileClick(props._id, props)}} + onContextMenu={(e) => props.getContextMenu(e)}> + +
+ {props.image.src = "/images/file-svg.svg"; props.image.className="file__image__listview"; props.failedToLoad = true}}/> +
{capitalize(props.filename)}
+
+
{bytes(props.length)}
+
{moment(props.uploadDate).format("L")}
+ + {(props.rightSelected === props._id && props._id === props.selected) ? + + + : + undefined} + +
+ + : + +
{props.fileClick(props._id, props)}} + onContextMenu={(e) => props.getContextMenu(e)}> + +
+ {props.image.src = "/images/file-svg.svg"; props.image.className="file__image"; props.failedToLoad = true;}}/> +
+ +
{capitalize(props.filename)}
+ + {(props.rightSelected === props._id && props._id === props.selected) ? + + + : + undefined} + + +
+ + } + +
+ ) + +} + +export default FileItem; diff --git a/src/components/FileItem/index.js b/src/components/FileItem/index.js new file mode 100644 index 0000000..cd1ce06 --- /dev/null +++ b/src/components/FileItem/index.js @@ -0,0 +1,182 @@ +import FileItem from "./FileItem"; +import {startSetSelectedItem, setRightSelected, setLastSelected} from "../../actions/selectedItem" +import mobileCheck from "../../utils/mobileCheck" +import env from "../../enviroment/envFrontEnd"; +import axios from "axios"; +import {connect} from "react-redux"; +import React from "react"; + +const currentURL = env.url; + +class FileItemContainer extends React.Component { + + constructor(props) { + super(props); + + this.failedToLoad = false; + + this.state = { + contextMenuPos: {}, + imageSrc: "/images/file-svg.svg", + imageClassname: this.props.listView ? "file__image__listview" : "file__image" + } + } + + getThumbnail = async() => { + + const thumbnailID = this.props.metadata.thumbnailID; + const imageClassname = this.props.listView ? "file__image__listview--no-opacity" : "file__image--no-opacity" + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")}, + responseType: 'arraybuffer' + }; + + await this.setState(() => ({ + ...this.state, + imageSrc: "/images/file-svg.svg", + imageClassname: this.props.listView ? "file__image__listview" : "file__image" + })) + + axios.get(currentURL +`/file-service/thumbnail/${thumbnailID}`, config).then((results) => { + + const imgFile = new Blob([results.data]); + const imgUrl = URL.createObjectURL(imgFile); + + this.setState(() => ({ + ...this.state, + imageSrc: imgUrl, + imageClassname: imageClassname + })) + + + }).catch((err) => { + console.log(err) + }) + } + + componentDidMount = () => { + + const hasThumbnail = this.props.metadata.hasThumbnail; + + if (hasThumbnail && !this.failedToLoad) { + this.getThumbnail(); + } + } + + shouldComponentUpdate = (nextProp, nextState) => { + + return (nextProp.itemSelected !== this.props.itemSelected + || nextProp.listView !== this.props.listView + || nextProp.rightSelected !== this.props.rightSelected + || nextState.imageSrc !== this.state.imageSrc + || nextState.imageClassname !== this.state.imageClassname + || this.props.filename !== nextProp.filename + || this.props.metadata.transcoded !== nextProp.metadata.transcoded) + } + + + componentDidUpdate = (nextProp) => { + + console.log("file item component updated") + + const hasThumbnail = this.props.metadata.hasThumbnail; + + if (hasThumbnail && !this.failedToLoad && nextProp.listView !== this.props.listView) { + + this.getThumbnail(); + + } else if (nextProp.listView !== this.props.listView) { + + this.setState(() => ({ + ...this.state, + imageClassname: this.props.listView ? "file__image__listview" : "file__image" + })) + } + } + + getContextMenu = (e) => { + + e.preventDefault(); + + const isMobile = mobileCheck(); + + const windowX = window.innerWidth; + const windowY = window.innerHeight; + + let styleObj = {right:0, left:0, top: "-38px", bottom: 0} + + const clientY = e.nativeEvent.clientY; + const clientX = e.nativeEvent.clientX; + + if (clientY < (windowY / 3)) { + + styleObj = {bottom:"-190px", top:"unset"} + } + + if (clientY > ((windowY / 4) * 3.5)) { + + styleObj = {bottom:"unset", top: "-190px"} + } + + if (clientX > windowX / 2) { + + styleObj = {...styleObj, left:"unset", right:0} + + } else { + + styleObj = {...styleObj, left:0, right:"unset"} + } + + if (isMobile) { + + styleObj = {bottom: 0, left: "2px", top: "unset", right: "unset"} + } + + this.setState(() => ({...this.state, contextMenuPos: styleObj})) + + this.props.dispatch(startSetSelectedItem(this.props._id, true, false)) + this.props.dispatch(setLastSelected(0)); + this.props.dispatch(setRightSelected(this.props._id)) + + } + + getWrapperClassname = () => { + + let classname = ""; + + if (this.props.listView) { + + classname += "file__item__listview" + + } else { + + classname += "file__item" + } + + if (this.props._id === this.props.selected) { + + classname += " file__item--selected" + } + + return classname; + } + + render() { + + return + } +} + +const connectStateToProp = (state) => ({ + listView: state.filter.listView, + rightSelected: state.selectedItem.rightSelected, + resetSelected: state.selectedItem.resetSelected, + selected: state.selectedItem.selected +}) + +export default connect(connectStateToProp)(FileItemContainer) \ No newline at end of file diff --git a/src/components/Filter/Filter.js b/src/components/Filter/Filter.js new file mode 100644 index 0000000..152861f --- /dev/null +++ b/src/components/Filter/Filter.js @@ -0,0 +1,48 @@ +import React from "react"; + +const Filter = (props) => { + + const filterOptionType = props.getFilterOptionType(); + const filterOptionValue = props.getFilterOptionValue(); + + return ( + +
+ +
+ + {props.folderListLength === 0 ? + + undefined + : + (
+

Folders

+
)} + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ ) +} + + +export default Filter diff --git a/src/components/Filter/index.js b/src/components/Filter/index.js new file mode 100644 index 0000000..a28c498 --- /dev/null +++ b/src/components/Filter/index.js @@ -0,0 +1,115 @@ +import Filter from "./Filter"; +import {setSortBy} from "../../actions/filter"; +import {startSetFiles} from "../../actions/files" +import {startSetFolders} from "../../actions/folders"; +import {resetItems} from "../../actions/main" +import {connect} from "react-redux"; +import React from "react"; + +class FilterContainer extends React.Component { + + constructor(props) { + super(props); + } + + changeOrder = () => { + + const sortBySwitch = this.props.filter.sortBy.split("_"); + const sortByPrefix = sortBySwitch[0]; + + if (this.props.filter.sortBy.includes("asc")) { + + return sortByPrefix + "_desc" + + } else { + + return sortByPrefix + "_asc" + } + } + + changeSortBy = async() => { + + const sortByString = this.changeOrder(); + + const parent = this.props.parent; + const search = this.props.filter.search; + + this.props.dispatch(setSortBy(sortByString)) + this.props.dispatch(startSetFiles(parent, sortByString, search)) + this.props.dispatch(startSetFolders(parent, sortByString, search)) + this.props.dispatch(resetItems()) + } + + changeSelected = (e) => { + + const sortBySwitch = this.props.filter.sortBy.split("_"); + const sortBySuffix = sortBySwitch[1]; + + if (sortBySwitch.includes("date")) { + + return "alp_" + sortBySuffix; + + } else { + + return "date_" + sortBySuffix; + } + + } + + selectedOnChange = async(e) => { + + const sortByString = this.changeSelected(); + + const parent = this.props.parent; + const search = this.props.filter.search; + + this.props.dispatch(setSortBy(sortByString)) + this.props.dispatch(startSetFiles(parent, sortByString, search)) + this.props.dispatch(startSetFolders(parent, sortByString, search)) + this.props.dispatch(resetItems()) + } + + getFilterOptionType = () => { + + if (this.props.filter.sortBy.includes("alp")) { + + return "name" + + } else { + + return "date" + } + } + + getFilterOptionValue = () => { + + if (this.props.filter.sortBy.includes("asc")) { + + return false; + + } else { + + return true; + } + } + + render() { + + return + + } +} + +const connectPropToStore = (state) => ({ + filter: state.filter, + folderListLength: state.folders.length, + parent: state.parent.parent +}) + + +export default connect(connectPropToStore)(FilterContainer); \ No newline at end of file diff --git a/src/components/FolderItem/FolderItem.js b/src/components/FolderItem/FolderItem.js new file mode 100644 index 0000000..fce7027 --- /dev/null +++ b/src/components/FolderItem/FolderItem.js @@ -0,0 +1,55 @@ +import ContextMenuFolder from ".././ContextMenuFolder"; +import moment from "moment"; +import React from "react"; + +const FolderItem = (props) => { + + const wrapperClassname = props.getClassName(); + + return ( + +
+ + {props.listView ? + +
props.folderClick(props._id, props.name)} + onContextMenu={(e) => props.getContextMenu(e)}> + +
+ +
{props.name}
+
+
{moment(props.createdAt).format("L")}
+ + {(props.rightSelected === props._id && props._id === props.selected) ? + + + : + undefined} +
+ + : + +
props.folderClick(props._id, props.name)} + onContextMenu={(e) => props.getContextMenu(e)}> + + +

{props.name}

+ + {(props.rightSelected === props._id && props._id === props.selected) ? + + + : + undefined} + +
+ + } + +
+ ) +} + +export default FolderItem; \ No newline at end of file diff --git a/src/components/FolderItem/index.js b/src/components/FolderItem/index.js new file mode 100644 index 0000000..1e82711 --- /dev/null +++ b/src/components/FolderItem/index.js @@ -0,0 +1,112 @@ +import FolderItem from "./FolderItem" +import {startSetSelectedItem, setRightSelected, setLastSelected} from "../../actions/selectedItem" +import mobileCheck from "../../utils/mobileCheck" +import {connect} from "react-redux"; +import React from "react"; + +class FolderItemContainer extends React.Component { + + constructor(props) { + super(props) + } + + shouldComponentUpdate = (nextProp) => { + + return (nextProp.itemSelected !== this.props.itemSelected + || nextProp.listView !== this.props.listView + || nextProp.rightSelected !== this.props.rightSelected + || nextProp.name !== this.props.name + || nextProp.quickFilesLength !== this.props.quickFilesLength) + } + + getContextMenu = (e) => { + + e.preventDefault(); + + const isMobile = mobileCheck(); + + const windowX = window.innerWidth; + const windowY = window.innerHeight; + + let styleObj = {right:0, left:0, top: "-14px", bottom: 0} + + const clientY = e.nativeEvent.clientY; + const clientX = e.nativeEvent.clientX; + + + if (clientY < (windowY / 3)) { + + const bottomSize = this.props.quickFilesLength === 0 ? "-126px" : "-190px" + + styleObj = {bottom: bottomSize, top:"unset"} + } + + if (clientY > ((windowY / 4) * 3.5)) { + + styleObj = {bottom:"unset", top: "-190px"} + } + + if (clientX > windowX / 2) { + + styleObj = {...styleObj, left:"unset", right:0} + + } else { + + styleObj = {...styleObj, left:0, right:"unset"} + } + + if (isMobile) { + + styleObj = {bottom: 0, left: "2px", top:"unset"} + } + + this.setState(() => styleObj) + + this.props.dispatch(startSetSelectedItem(this.props._id, false)) + this.props.dispatch(setLastSelected(0)); + this.props.dispatch(setRightSelected(this.props._id)) + + } + + getClassName = () => { + + let classname = ""; + + if (this.props.listView) { + + classname += "file__item__listview" + + } else { + + classname += "folder__item__wrapper" + } + + if (this.props._id === this.props.selected) { + + classname += " file__item--selected" + } + + return classname; + } + + + render() { + + return + } + +} + +const connectPropToState = (state) => ({ + listView: state.filter.listView, + rightSelected: state.selectedItem.rightSelected, + resetSelected: state.selectedItem.resetSelected, + selected: state.selectedItem.selected, + quickFilesLength: state.quickFiles.length +}) + +export default connect(connectPropToState)(FolderItemContainer); \ No newline at end of file diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js new file mode 100644 index 0000000..566e34d --- /dev/null +++ b/src/components/Header/Header.js @@ -0,0 +1,39 @@ +import SuggestedItem from ".././SuggestedItem"; +import React from "react"; +import uuid from "uuid"; + +const Header = (props) => ( +
+ + + +
+ + + +
+ + {props.state.suggestedList.folderList.map((suggested) => { + + return + })} + + {props.state.suggestedList.fileList.map((suggested) => { + + return + })} + +
+ +
+ + +
+) + +export default Header \ No newline at end of file diff --git a/src/components/Header/index.js b/src/components/Header/index.js new file mode 100644 index 0000000..65b8f0f --- /dev/null +++ b/src/components/Header/index.js @@ -0,0 +1,156 @@ +import Header from "./Header"; +import {startLogout} from "../../actions/auth" +import {showSettings} from "../../actions/settings"; +import {setSearch} from "../../actions/filter" +import {loadMoreItems} from "../../actions/main"; +import {setParent, resetParentList} from "../../actions/parent"; +import {startSetFiles} from "../../actions/files"; +import {startSetFolders} from "../../actions/folders"; +import env from "../../enviroment/envFrontEnd"; +import {history} from "../../routers/AppRouter"; +import axios from "axios"; +import {connect} from "react-redux"; +import React from "react"; + +const currentURL = env.url; + +class HeaderContainer extends React.Component { + + constructor(props) { + super(props); + + this.searchValue = ""; + + this.state = { + focused: false, + suggestedList: { + fileList: [], + folderList: [] + } + } + } + + searchEvent = (e) => { + e.preventDefault(); + + const value = this.props.search; + + const parent = "/" + this.props.dispatch(setParent(parent)) + this.props.dispatch(loadMoreItems(true)) + this.props.dispatch(startSetFiles(undefined, undefined, value)); + this.props.dispatch(startSetFolders(undefined, undefined, value)); + this.props.dispatch(resetParentList()) + + + } + + searchOnChange = (e) => { + + const value = e.target.value; + this.searchValue = value; + + this.props.dispatch(setSearch(value)) + this.searchSuggested() + } + + showSuggested = () => { + + this.setState(() => { + return { + ...this.state, + focused: true + } + }) + } + + hideSuggested = () => { + + this.setState(() => { + return { + ...this.state, + focused: false + } + }) + } + + selectSuggested = (value) => { + + history.push(`/search/${value}`) + + this.setState(() => { + return { + ...this.state, + suggestedList: { + fileList: [], + folderList: [] + } + } + }) + } + + searchSuggested = () => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + + if (this.searchValue === "") { + + return this.setState(() => { + return { + ...this.state, + suggestedList: { + fileList: [], + folderList: [] + } + } + }) + } + + axios.get(currentURL +`/file-service/suggested-list?search=${this.searchValue}`, config).then((results) => { + + this.setState(() => { + return { + ...this.state, + suggestedList: results.data + } + }) + + + }).catch((err) => { + console.log(err) + }) + } + + showSettings = () => { + + this.props.dispatch(showSettings()) + } + + logoutUser = () => { + + this.props.dispatch(startLogout()) + } + + render() { + + return
+ } +} + +const connectPropToStore = (state) => ({ + search: state.filter.search +}) + +export default connect(connectPropToStore)(HeaderContainer); \ No newline at end of file diff --git a/src/components/Homepage/HomePage.js b/src/components/Homepage/HomePage.js new file mode 100644 index 0000000..de3dfbb --- /dev/null +++ b/src/components/Homepage/HomePage.js @@ -0,0 +1,28 @@ +import Header from ".././Header"; +import LeftSection from ".././LeftSection" +import MainSection from ".././MainSection"; +import Uploader from ".././Uploader"; +import SettingsMenu from ".././SettingsMenu"; +import React from "react"; +import PhotoViewer from "../PhotoViewer" + +const HomePage = (props) => ( + +
+
+
+ + + + + + {props.photoID.length === 0 ? undefined : + } + +
+
+ +) + +export default HomePage + diff --git a/src/components/Homepage/index.js b/src/components/Homepage/index.js new file mode 100644 index 0000000..e65f31b --- /dev/null +++ b/src/components/Homepage/index.js @@ -0,0 +1,232 @@ +import HomePage from "./HomePage"; +import {startLoginCheck} from "../../actions/auth"; +import {startSetStorage} from "../../actions/storage" +import {startSetQuickFiles, setQuickFiles} from "../../actions/quickFiles" +import {resetSelectedItem, resetSelected, setLastSelected, setRightSelected} from "../../actions/selectedItem" +import {startSetFiles} from "../../actions/files" +import {startSetFolders} from "../../actions/folders" +import {goneSideBar, loadMoreItems} from "../../actions/main"; +import {setParent,resetParentList, startSetParentList, setParentList} from "../../actions/parent" +import {enableListView, setSearch, setCurrentlySearching, resetCurrentlySearching} from "../../actions/filter" +import {setCurrentRoute, resetCurrentRoute} from "../../actions/routes"; +import {history} from "../../routers/AppRouter"; +import uuid from "uuid"; +import {connect} from "react-redux"; +import React from "react"; + +class HomePageContainer extends React.Component { + + constructor(props) { + super(props); + + this.lastLocationKey = ""; + } + + listStyleCheck = () => { + + if (window.localStorage.getItem("list-style") === "List") { + + this.props.dispatch(resetSelected()) + this.props.dispatch(enableListView()) + } + } + + loginCheck = () => { + + console.log("login check", this.props.isAuthenticated); + + const pathname = history.location.pathname; + + if (this.props.isAuthenticated) { + + if (pathname === "/home") { + this.getFiles(); + this.props.dispatch(setParent("/")) + this.props.dispatch(resetParentList()); + + } else if (pathname.includes("/search")) { + this.props.dispatch(startSetQuickFiles()); + } + + this.props.dispatch(resetCurrentRoute()); + this.props.dispatch(startSetStorage()); + this.props.dispatch(resetCurrentlySearching()); + this.props.dispatch(startSetStorage()); + + } else { + + const currentPath = history.location.pathname; + console.log("hisory", currentPath); + this.props.dispatch(setCurrentRoute(currentPath)) + history.push("/") + } + + } + + historyUpdateCheck = () => { + + console.log("history update check"); + + const pathname = history.location.pathname + + if (pathname === "/home") { + + this.getFiles(); + this.props.dispatch(startSetStorage()); + this.props.dispatch(resetCurrentlySearching()); + this.props.dispatch(setParent("/")) + this.props.dispatch(resetParentList()); + this.props.dispatch(startSetStorage()); + + } else if (pathname.includes("/folder")) { + + const sortBy = this.props.sortBy + const idSplit = history.location.pathname.split("/folder/"); + const id = idSplit[1]; + + this.props.dispatch(setQuickFiles([])); + this.props.dispatch(setLastSelected(0)); + this.props.dispatch(resetSelectedItem()) + this.props.dispatch(resetCurrentlySearching()); + this.props.dispatch(setLastSelected(0)); + this.props.dispatch(resetSelected()); + this.props.dispatch(setParent(id)); + this.props.dispatch(startSetParentList(id)) + this.props.dispatch(startSetFolders(id, sortBy)); + this.props.dispatch(startSetFiles(id, sortBy)); + this.props.dispatch(setRightSelected("")) + this.props.dispatch(startSetStorage()); + this.props.dispatch(loadMoreItems(true)) + + } else { + + const currentPathnameSplit = pathname.split("/search/"); + const value = currentPathnameSplit[1]; + const parent = "/" + + this.props.dispatch(setCurrentlySearching()); + this.props.dispatch(setParent(parent)) + this.props.dispatch(loadMoreItems(true)) + this.props.dispatch(startSetFiles(undefined, undefined, value)); + this.props.dispatch(startSetFolders(undefined, undefined, value)); + this.props.dispatch(setParent) + this.props.dispatch(setParentList(["/"], ["Home"])) + this.props.dispatch(setSearch("")) + this.props.dispatch(startSetStorage()); + + } + + } + + componentDidMount = () => { + + console.log("Homepage mounted"); + window.addEventListener("resize", () => {this.props.dispatch(goneSideBar())}) + + this.listStyleCheck(); + this.loginCheck(); + this.setSessionStorage(); + // this.historyUpdateCheck(); + } + + setSessionStorage = () => { + + window.sessionStorage.setItem("uuid", uuid()); + } + + componentWillUnmount = () => { + + window.removeEventListener("resize", () => {}); + } + + + + historyUpdateCheckRefresh = () => { + + const currentPathname = history.location.pathname + + if (this.lastLocationKey !== history.location.key && currentPathname !== "/home" && currentPathname.includes("/search")) { + + console.log("search update"); + + const currentPathnameSplit = currentPathname.split("/search/"); + const value = currentPathnameSplit[1]; + const parent = "/" + + this.props.dispatch(setParent(parent)) + this.props.dispatch(loadMoreItems(true)) + this.props.dispatch(startSetFiles(undefined, undefined, value)); + this.props.dispatch(startSetFolders(undefined, undefined, value)); + this.props.dispatch(setCurrentlySearching()); + this.props.dispatch(setParentList(["/"], ["Home"])) + this.props.dispatch(setSearch("")) + this.props.dispatch(startSetStorage()); + + + } else if (this.lastLocationKey !== history.location.key && history.location.pathname !== "/home") { + + console.log("folder update"); + + const sortBy = this.props.sortBy + const idSplit = history.location.pathname.split("/folder/"); + const id = idSplit[1]; + + this.props.dispatch(setQuickFiles([])); + this.props.dispatch(setLastSelected(0)); + this.props.dispatch(resetSelectedItem()) + this.props.dispatch(resetCurrentlySearching()); + this.props.dispatch(setLastSelected(0)); + this.props.dispatch(resetSelected()); + this.props.dispatch(setParent(id)); + this.props.dispatch(startSetParentList(id)) + this.props.dispatch(loadMoreItems(true)) + this.props.dispatch(startSetFolders(id, sortBy)); + this.props.dispatch(startSetFiles(id, sortBy)); + this.props.dispatch(setRightSelected("")) + this.props.dispatch(startSetStorage()); + + } else if (this.lastLocationKey !== history.location.key) { + + console.log("home update"); + + + this.getFiles(); + this.props.dispatch(startSetStorage()); + this.props.dispatch(resetCurrentlySearching()); + this.props.dispatch(setParent("/")) + this.props.dispatch(resetParentList()); + this.props.dispatch(startSetStorage()); + } + + this.lastLocationKey = history.location.key + } + + componentDidUpdate = () => { + + this.historyUpdateCheckRefresh(); + } + + getFiles = () => { + + this.props.dispatch(startSetQuickFiles()) + this.props.dispatch(startSetFolders()); + this.props.dispatch(startSetFiles()) + } + + goHome = () => { + history.push("/home") + } + + render() { + + return + } +} + +const mapStoreToProps = (state) => ({ + sortBy: state.filter.sortBy, + isAuthenticated: !!state.auth.id, + photoID: state.photoViewer.id +}) + +export default connect(mapStoreToProps)(HomePageContainer); \ No newline at end of file diff --git a/src/components/LeftSection/LeftSection.js b/src/components/LeftSection/LeftSection.js new file mode 100644 index 0000000..51a1a26 --- /dev/null +++ b/src/components/LeftSection/LeftSection.js @@ -0,0 +1,56 @@ +import StorageWidget from ".././StorageWidget"; +import React from "react"; + +class LeftSection extends React.Component { + + constructor(props) { + super(props); + } + + render() { + + return ( + +
+ +
+ + + +

New

+ +
+
+ +

Folder

+
+ +
+ + +

File Upload

+ + +
+ +
+ +

Folder Upload

+
+
+ +
+ + {this.props.storage.total !== 0 ? + + : undefined} + +
+ + + ) + } +} + +export default LeftSection; diff --git a/src/components/LeftSection/index.js b/src/components/LeftSection/index.js new file mode 100644 index 0000000..aa44b1b --- /dev/null +++ b/src/components/LeftSection/index.js @@ -0,0 +1,95 @@ +import LeftSection from "./LeftSection"; +import {showAddOptions} from "../../actions/addOptions"; +import {startAddFile} from "../../actions/files"; +import {startAddFolder} from "../../actions/folders" +import Swal from "sweetalert2"; +import {connect} from "react-redux"; +import React from "react"; + +class LeftSectionContainer extends React.Component { + + constructor(props) { + super(props); + + this.wrapperRef = React.createRef(); + this.uploadInput = React.createRef(); + } + + createFolder = async(e) => { + + let inputValue = "" + + const { value: folderName} = await Swal.fire({ + title: 'Enter Folder Name', + input: 'text', + inputValue: inputValue, + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return 'Please Enter a Name' + } + } + }) + + if (folderName === undefined || folderName === null) { + + return; + } + + const parent = this.props.parent; + const owner = this.props.auth.id; + const parentList = this.props.parentList; + + this.props.dispatch(startAddFolder(folderName, owner, parent, parentList)); + } + + handleClickOutside = (e) => { + + if (this.wrapperRef && !this.wrapperRef.current.contains(event.target) && this.props.showAddOptions) { + this.addButtonEvent(); + } + } + + componentDidMount = () => { + document.addEventListener('mousedown', this.handleClickOutside); + } + + componentWillUnmount = () => { + document.removeEventListener('mousedown', this.handleClickOutside); + } + + addButtonEvent = () => { + + const currentAddOptions = !this.props.showAddOptions + this.props.dispatch(showAddOptions(currentAddOptions)) + } + + handleUpload = (e) => { + e.preventDefault(); + + this.props.dispatch(startAddFile(this.uploadInput.current, this.props.parent, this.props.parentList)) + this.uploadInput.current.value = "" + } + + render() { + + return + } +} + +const connectPropToState = (state) => ({ + auth: state.auth, + parent: state.parent.parent, + parentList: state.parent.parentList, + storage: state.storage, + showAddOptions: state.addOptions.showAddOptions +}) + +export default connect(connectPropToState)(LeftSectionContainer); \ No newline at end of file diff --git a/src/components/LoginPage/LoginPage.js b/src/components/LoginPage/LoginPage.js new file mode 100755 index 0000000..09733e3 --- /dev/null +++ b/src/components/LoginPage/LoginPage.js @@ -0,0 +1,69 @@ +import SpinnerLogin from ".././SpinnerLogin"; +import React from "react"; + +const LoginPage = (props) => { + + if (window.localStorage.getItem("token")) { + props.loginWithToken() + } + + return ( + +
+ + {(window.localStorage.getItem("token") && !props.loginFailed) ? + +
+ +
+ +
+ +
+
+ + +
+ + : + +
+
+
+

MyDrive

+
+ {props.emailInput = ref}}/> + {props.passwordInput = ref}}/> + {props.state.loginMode ? undefined : + {props.passwordInput = ref}}/> + } + +

{props.state.loginMode ? "Create Account": "Back To Login"}

+ + {props.loginFailed ? + +
+ +

{props.loginFailed}

+
+ + : + + undefined} + +
+
+
+
+ + } + +
+ + + + ) + +} + +export default LoginPage \ No newline at end of file diff --git a/src/components/LoginPage/index.js b/src/components/LoginPage/index.js new file mode 100644 index 0000000..c854d76 --- /dev/null +++ b/src/components/LoginPage/index.js @@ -0,0 +1,115 @@ +import LoginPage from "./LoginPage"; +import {startLogin, startLoginCheck, startCreateAccount} from "../../actions/auth" +import {setLoginFailed} from "../../actions/main"; +import {connect} from "react-redux"; +import React from "react"; + +class LoginPageContainer extends React.Component { + + constructor(props) { + super(props); + + this.state = { + value: undefined, + loginMode: true, + email: "", + password: "", + verifyPassword: "", + } + } + + loginWithToken = () => { + + const token = window.localStorage.getItem("token"); + this.props.dispatch(startLoginCheck(token, this.props.currentRoute)) + } + + login = (e) => { + + e.preventDefault(); + + const email = this.state.email + const password = this.state.password + const verifyPassword = this.state.verifyPassword; + + if (this.state.loginMode) { + + this.props.dispatch(startLogin(email, password, this.props.currentRoute)) + + } else if (password === verifyPassword) { + + this.props.dispatch(startCreateAccount(email, password)); + + } else { + + this.props.dispatch(setLoginFailed("Passwords Do Not Match")); + } + } + + switchLoginMode = () => { + + this.setState(() => { + + return { + ...this.state, + loginMode: !this.state.loginMode + } + }) + } + + emailOnChange = (e) => { + + const value = e.target.value; + + this.setState(() => ({ + ...this.state, + email: value + + })) + + } + + passwordOnChange = (e) => { + + const value = e.target.value; + + this.setState(() => ({ + ...this.state, + password: value + })) + + } + + verifyPasswordOnChange = (e) => { + + const value = e.target.value; + + this.setState(() => ({ + ...this.state, + verifyPassword: value + })) + } + + render() { + + return + + } + +} + +const mapStateToProps = (state) => ({ + id: state.auth.id, + loginFailed: state.main.loginFailed, + currentRoute: state.routes.currentRoute +}) + +export default connect(mapStateToProps)(LoginPageContainer) \ No newline at end of file diff --git a/src/components/MainSection/MainSection.js b/src/components/MainSection/MainSection.js new file mode 100644 index 0000000..3545864 --- /dev/null +++ b/src/components/MainSection/MainSection.js @@ -0,0 +1,76 @@ +import Subbar from ".././Subbar"; +import Filter from ".././Filter"; +import DataForm from ".././Dataform"; +import RightSection from ".././RightSection"; +import Spinner from ".././Spinner"; +import QuickAccess from ".././QuickAccess/index"; +import MoverMenu from ".././MoverMenu"; +import Spacer from ".././Spacer" +import PopupWindow from '.././PopupWindow' +import ShareMenu from ".././ShareMenu"; +import InfiniteScroll from 'react-infinite-scroller'; +import React from "react"; + + +const MainSection = React.forwardRef((props, ref) => { + + return ( +
+ + {props.showPopup ? : undefined} + + + +
+ +
+ + {(props.quickFiles.length !== 0 && props.parent === "/") ? +
+ + +
: + undefined} + + + + +
} + useWindow={false} + getScrollParent={() => props.scrollParentRef} + > + + + + + + + + {props.moverID.length === 0 ? undefined : + + } + +
+ + + +
+ + + + +) + +}) + + +export default MainSection \ No newline at end of file diff --git a/src/components/MainSection/index.js b/src/components/MainSection/index.js new file mode 100644 index 0000000..386219e --- /dev/null +++ b/src/components/MainSection/index.js @@ -0,0 +1,133 @@ + +import {startLoadMoreFiles} from "../../actions/files"; +import {startSetSelectedItem, setLastSelected} from "../../actions/selectedItem"; +import {setPopupFile} from "../../actions/popupFile"; +import mobileCheck from "../../utils/mobileCheck" +import MainSection from "./MainSection"; +import env from "../../enviroment/envFrontEnd"; +import axios from "axios"; +import {connect} from "react-redux"; +import {history} from "../../routers/AppRouter"; +import React from "react"; + +const currentURL = env.url; + +class MainSectionContainer extends React.Component { + + constructor(props) { + super(props); + } + + folderClick = (id) => { + + const currentDate = Date.now(); + const mobile = mobileCheck(); + const selectedID = this.props.selected; + + if ((currentDate - this.props.lastSelected < 1500 && selectedID === id) || mobile) { + + history.push(`/folder/${id}`) + + } else { + this.props.dispatch(startSetSelectedItem(id, false)); + } + + } + + fileClick = (fileID, file, fromQuickItems=false) => { + + const currentDate = Date.now(); + + let selectedFileID = fileID; + + if (fromQuickItems) { + selectedFileID = "quick-" + fileID; + } + + if (currentDate - this.props.lastSelected < 1500 && selectedFileID === this.props.selected) { + + this.props.dispatch(setPopupFile({showPopup: true, ...file})) + + } else { + + this.props.dispatch(startSetSelectedItem(fileID, true, fromQuickItems)) + + } + + } + + downloadFile = (fileID) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + this.props.dispatch(setLastSelected(0)); + + axios.get(currentURL +'/file-service/download/get-token',config) + .then((response) => { + + const tempToken = response.data.tempToken; + const finalUrl = currentURL + `/file-service/download/${fileID}/${tempToken}` + + const link = document.createElement('a'); + document.body.appendChild(link); + link.href = finalUrl; + link.setAttribute('type', 'hidden'); + link.click(); + + }).catch((err) => { + console.log(err) + }) + } + + loadMoreItems = () => { + + console.log("loading more items", this.props.loading); + + let limit = window.localStorage.getItem("list-size") || 50 + limit = parseInt(limit) + + if (this.props.loading) { + + return; + } + + if (this.props.files.length >= limit) { + + const parent = this.props.parent; + const search = this.props.filter.search; + const sortBy = this.props.filter.sortBy; + const lastFileDate = this.props.files[this.props.files.length - 1].uploadDate + const lastFileName = this.props.files[this.props.files.length - 1].filename + + this.props.dispatch(startLoadMoreFiles(parent, sortBy, search, lastFileDate, lastFileName)) + } + } + + render() { + + return + } + +} + +const connectPropToState = (state) => ({ + filter: state.filter, + files: state.files, + allowLoadMoreItems: state.main.loadMoreItems, + loading: state.main.loading, + showPopup: state.popupFile.showPopup, + quickFiles: state.quickFiles, + selected: state.selectedItem.selected, + lastSelected: state.selectedItem.lastSelected, + parent: state.parent.parent, + moverID: state.mover.id, +}) + +export default connect(connectPropToState)(MainSectionContainer); \ No newline at end of file diff --git a/src/components/MoverMenu/MoverMenu.js b/src/components/MoverMenu/MoverMenu.js new file mode 100644 index 0000000..dcaec42 --- /dev/null +++ b/src/components/MoverMenu/MoverMenu.js @@ -0,0 +1,53 @@ +import React from "react"; +import moment from "moment"; + +const MoverMenu = (props) => ( +
+ + + +
+ + 0) ? + "movermenu__back-button" : "movermenu__back-button movermenu__back-button--disabled" + } onClick={props.goBack} src="/images/back.svg"/> +

{props.state.title}

+ 0) ? + "movermenu__home-button" : "movermenu__home-button movermenu__home-button--disabled" + } onClick={props.goHome} src="/images/home.svg"/> + +
+ +
+
+ +
+
+ +
+ {props.state.folders.map((folder) => { + + return
props.folderClick(folder._id, folder.name)} + > + +
+ +
{folder.name}
+
+ +
{moment(folder.createdAt).format("L")}
+ +
+ + })} +
+ +
+ +
+ +
+) + +export default MoverMenu; \ No newline at end of file diff --git a/src/components/MoverMenu/index.js b/src/components/MoverMenu/index.js new file mode 100644 index 0000000..884256b --- /dev/null +++ b/src/components/MoverMenu/index.js @@ -0,0 +1,310 @@ +import React from "react"; +import {connect} from "react-redux"; +import MoverMenu from "./MoverMenu"; +import axios from "axios"; +import env from "../../enviroment/envFrontEnd"; +import {resetMoverID} from "../../actions/mover"; +import {removeFile} from "../../actions/files"; +import {removeFolder} from "../../actions/folders" + +const currentURL = env.url; + +class MoverMenuContainer extends React.Component { + + constructor(props) { + super(props); + + this.state = { + folders: [], + selected: "", + lastClick: 0, + search: "", + title: "Home", + historyList: [] + } + } + + moveItem = () => { + + let id = this.props.ID; + + let parent = "/"; + + if (this.state.selected !== "") { + + parent = this.state.selected; + + } else if (this.state.historyList.length !== 0) { + + parent = this.state.historyList[this.state.historyList.length - 1].id; + } + + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + const data = { + id, + parent + } + + if (this.props.isFile) { + + axios.patch(currentURL+`/file-service/move`, data, config).then((response) => { + + console.log("file moved!"); + + this.props.dispatch(removeFile(id)); + this.props.dispatch(resetMoverID()); + + }) + + } else { + + axios.patch(currentURL+`/folder-service/move`, data, config).then((response) => { + + console.log("folder moved!"); + + this.props.dispatch(removeFolder(id)); + this.props.dispatch(resetMoverID()); + + + }) + + } + + + } + + getButtonName = () => { + + + if (this.state.selected !== "") { + + return "Move To Folder"; + + } else if (this.state.historyList.length === 0) { + + return "Move To Home"; + + } else { + return "Move Here"; + } + } + + search = (e) => { + + e.preventDefault(); + + const searchValue = this.state.search; + + const id = "/" + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + axios.get(currentURL+`/folder-service/list?parent=${id}&search=${searchValue}&sortBy=DEFAULT}`, config).then((response) => { + + let folders = response.data; + + if (!this.props.isFile) folders = folders.filter((folder) => + folder._id !== this.props.ID); + + console.log("folders", folders); + this.setState(() => ({ + ...this.state, + title: "Search", + folders, + selected: "", + historyList:[] + })) + }) + } + + inputChange = (e) => { + + const value = e.target.value; + + this.setState(() => ({ + ...this.state, + search: value + })) + } + + goHome = () => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + const id = "/" + + axios.get(currentURL+`/folder-service/list?parent=${id}`, config).then((response) => { + + let folders = response.data; + + if (!this.props.isFile) folders = folders.filter((folder) => + folder._id !== this.props.ID); + + console.log("folders", folders); + this.setState(() => ({ + ...this.state, + folders, + title: "Home", + search: "", + selected: "", + historyList: [] + })) + + }).catch((err) => { + console.log(err); + }) + } + + goBack = () => { + + let currentID = "/" + let currentName = "Home" + let historyList = []; + + if (this.state.historyList.length > 1) { + + historyList = this.state.historyList; + historyList.pop(); + + currentID = this.state.historyList[this.state.historyList.length - 1].id; + currentName = this.state.historyList[this.state.historyList.length - 1].name; + + } else if (this.state.historyList.length !== 0) { + + historyList = this.state.historyList; + historyList.pop(); + } + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + axios.get(currentURL+`/folder-service/list?parent=${currentID}`, config).then((response) => { + + let folders = response.data; + + if (!this.props.isFile) folders = folders.filter((folder) => + folder._id !== this.props.ID); + + console.log("folders", folders); + this.setState(() => ({ + ...this.state, + folders, + title: currentName, + search: "", + selected: "", + historyList + })) + }) + } + + closeMover = () => { + + this.props.dispatch(resetMoverID()); + } + + folderClick = (id, name) => { + + const date = new Date(); + const currentTime = date.getTime(); + const lastTimeDifference = currentTime - this.state.lastClick; + + if (lastTimeDifference < 1500 && id === this.state.selected) { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + axios.get(currentURL+`/folder-service/list?parent=${id}`, config).then((response) => { + + let folders = response.data; + + if (!this.props.isFile) folders = folders.filter((folder) => + folder._id !== this.props.ID); + + const historyList = this.state.historyList; + historyList.push({id, name}); + console.log("folders", folders); + this.setState(() => ({ + ...this.state, + folders, + title: name, + search: "", + selected: "", + historyList + })) + + }).catch((err) => { + console.log(err); + }) + + } else { + + this.setState(() => ({ + ...this.state, + selected: id, + lastClick: currentTime, + })) + } + } + + componentDidMount = () => { + + console.log("Mover Mounted", this.props.ID, this.props.parent) + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + } + + const id = "/" + + axios.get(currentURL+`/folder-service/list?parent=${id}`, config).then((response) => { + + let folders = response.data; + + if (!this.props.isFile) folders = folders.filter((folder) => + folder._id !== this.props.ID); + + console.log("folders", folders); + + this.setState(() => ({ + ...this.state, + folders + })) + + }).catch((err) => { + console.log(err); + }) + } + + render() { + + return + } + +} + +const connectStateToProps = (state) => ({ + ID: state.mover.id, + parent: state.mover.parent, + isFile: state.mover.isFile +}) + +export default connect(connectStateToProps)(MoverMenuContainer); \ No newline at end of file diff --git a/src/components/PhotoViewer/PhotoViewer.js b/src/components/PhotoViewer/PhotoViewer.js new file mode 100644 index 0000000..c6f7ddd --- /dev/null +++ b/src/components/PhotoViewer/PhotoViewer.js @@ -0,0 +1,21 @@ +import React from "react"; +import Spinner from "../Spinner"; + +const PhotoViewer = (props) => ( +
+ + + + {props.state.image === "" ? +
+ +
+ : + + } + + +
+) + +export default PhotoViewer; \ No newline at end of file diff --git a/src/components/PhotoViewer/index.js b/src/components/PhotoViewer/index.js new file mode 100644 index 0000000..33480d1 --- /dev/null +++ b/src/components/PhotoViewer/index.js @@ -0,0 +1,56 @@ +import PhotoViewer from "./PhotoViewer"; +import {connect} from "react-redux"; +import env from "../../enviroment/envFrontEnd"; +import React from "react"; +import {resetPhotoID} from "../../actions/photoViewer"; +import axios from "axios"; + +const currentURL = env.url; + +class PhotoViewerContainer extends React.Component { + + constructor(props) { + super(props); + + this.state = { + image: "" + } + } + + closePhotoViewer = () => { + + this.props.dispatch(resetPhotoID()) + } + + componentDidMount = () => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")}, + responseType: 'arraybuffer' + }; + + axios.get(currentURL +`/file-service/full-thumbnail/${this.props.photoID}`, config).then((response) => { + + const imgFile = new Blob([response.data]); + const imgUrl = URL.createObjectURL(imgFile); + + this.setState(() => ({ + ...this.state, + image: imgUrl + })) + }) + } + + render() { + + return + } +} + +const connectStateToProp = (state) => ({ + photoID: state.photoViewer.id +}) + +export default connect(connectStateToProp)(PhotoViewerContainer); \ No newline at end of file diff --git a/src/components/PopupWindow/PopupWindow.js b/src/components/PopupWindow/PopupWindow.js new file mode 100644 index 0000000..bc446c4 --- /dev/null +++ b/src/components/PopupWindow/PopupWindow.js @@ -0,0 +1,53 @@ +import Spinner from ".././Spinner"; +import capitalize from "../../utils/capitalize"; +import React from "react"; + +class PopupWindow extends React.Component { + + constructor(props) { + super(props); + + } + + render() { + + + return ( +
+ +

{capitalize(this.props.popupFile.filename)}

+ + {!this.props.popupFile.metadata.isVideo ? + +
+ + {!this.props.popupFile.metadata.hasThumbnail ?

No Preview Available

: undefined} + + {!this.props.popupFile.metadata.hasThumbnail ? + : } + +
+ {!this.props.popupFile.metadata.hasThumbnail ? undefined : } +
+
+ : + + } + + + +
+ ) + } + +} + + +export default PopupWindow + diff --git a/src/components/PopupWindow/index.js b/src/components/PopupWindow/index.js new file mode 100644 index 0000000..78d8e38 --- /dev/null +++ b/src/components/PopupWindow/index.js @@ -0,0 +1,160 @@ +import PopupWindow from "./PopupWindow"; +import {hidePopup} from "../../actions/popupFile" +import axios from "axios"; +import env from "../../enviroment/envFrontEnd"; +import {connect} from "react-redux"; +import React from "react"; +import { setPhotoID } from "../../actions/photoViewer"; + +const currentURL = env.url; + +class PopupWindowContainer extends React.Component { + + constructor(props) { + super(props); + + this.wrapperRef = React.createRef(); + this.video = React.createRef(); + this.imageData = "" + this.tempToken = "" + + this.state = { + image: "/images/cloud-svg.svg", + imageClassname: this.props.popupFile.metadata.hasThumbnail ? "popup-window__image popup-window popup-window--gone" : "popup-window__image", + video: "", + spinnerClassname: "popup-window__spinner__wrapper" + } + } + + getThumbnail = () => { + + const thumbnailID = this.props.popupFile.metadata.thumbnailID + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")}, + responseType: 'arraybuffer' + }; + + axios.get(currentURL +`/file-service/thumbnail/${thumbnailID}`, config).then((results) => { + + const imgFile = new Blob([results.data]); + const imgUrl = URL.createObjectURL(imgFile); + + this.setState(() => ({ + ...this.state, + image: imgUrl, + imageClassname: "popup-window__image popup-window__image--loaded", + spinnerClassname: "popup-window__spinner__wrapper popup-window--gone" + + })) + + }).catch((err) => { + console.log(err) + }) + } + + handleClickOutside = (e) => { + + if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) { + this.props.dispatch(hidePopup()) + } + } + + getVideo = () => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token"), + uuid: window.sessionStorage.getItem("uuid") + }, + + }; + + axios.get(currentURL +'/file-service/download/get-token-video',config) + .then((response) => { + + this.tempToken = response.data.tempToken; + + const uuidID = window.sessionStorage.getItem("uuid"); + + const finalUrl = !this.props.popupFile.metadata.transcoded ? currentURL + `/file-service/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` + : currentURL + `/file-service/stream-video-transcoded/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` + + this.setState(() => ({ + ...this.state, + video: finalUrl + })) + + }).catch((err) => { + console.log(err) + }) + } + + componentWillUnmount = () => { + + document.removeEventListener('mousedown', this.handleClickOutside); + + const headers = {'Authorization': "Bearer " + window.localStorage.getItem("token")} + + if (this.props.popupFile.metadata.isVideo) { + + axios.delete(currentURL +`/file-service/remove/token-video/${this.tempToken}`, { + headers, + }).catch((err) => { + console.log(err); + }) + + this.video.current.pause(); + + this.setState(() => ({ + ...this.state, + video: "" + })) + } + } + + componentDidMount = () => { + + document.addEventListener('mousedown', this.handleClickOutside); + + if (this.props.popupFile.metadata.hasThumbnail) { + + this.getThumbnail() + + } else if (this.props.popupFile.metadata.isVideo) { + + this.getVideo(); + } + } + + hidePopupWindow = () => { + + this.props.dispatch(hidePopup()); + } + + setPhotoViewerWindow = () => { + + console.log("set photoviewer window", this.props.popupFile._id); + this.props.dispatch(setPhotoID(this.props.popupFile._id)) + } + + render() { + + return + } + +} + +const connectPropToState = (state) => ({ + + popupFile: state.popupFile, +}) + + +export default connect(connectPropToState)(PopupWindowContainer) diff --git a/src/components/QuickAccess/QuickAccess.js b/src/components/QuickAccess/QuickAccess.js new file mode 100644 index 0000000..d144c6e --- /dev/null +++ b/src/components/QuickAccess/QuickAccess.js @@ -0,0 +1,18 @@ +import QuickAccessItem from ".././QuickAccessItem"; +import React from "react"; + +const QuickAccess = (props) => ( +
+ + {props.quickFiles + .map((file) => )} + +
+) + + +export default QuickAccess; \ No newline at end of file diff --git a/src/components/QuickAccess/index.js b/src/components/QuickAccess/index.js new file mode 100644 index 0000000..4488015 --- /dev/null +++ b/src/components/QuickAccess/index.js @@ -0,0 +1,21 @@ +import QuickAccess from "./QuickAccess"; +import {connect} from "react-redux"; +import React from "react"; + +class QuickAccessContainer extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return + } + +} + +const connectStateToProp = (state) => ({ + quickFiles: state.quickFiles +}) + +export default connect(connectStateToProp)(QuickAccessContainer) \ No newline at end of file diff --git a/src/components/QuickAccessItem/QuickAccessItem.js b/src/components/QuickAccessItem/QuickAccessItem.js new file mode 100644 index 0000000..19996f5 --- /dev/null +++ b/src/components/QuickAccessItem/QuickAccessItem.js @@ -0,0 +1,33 @@ +import ContextMenu from ".././ContextMenu" +import capitalize from "../../utils/capitalize"; +import moment from "moment"; +import React from "react"; + +const QuickAccessItem = (props) => ( + +
{props.fileClick(props._id, props, true)}} + onContextMenu={(e) => props.getContextMenu(e)}> + +
+ {props.image.src = "/images/file-svg.svg"; props.image.className="quickaccess__item__image"; props.failedToLoad = true}}/> + +
+ +
+

{capitalize(props.filename)}

+

Created {moment(props.uploadDate).calendar()}

+
+ + {("quick-"+props._id === props.rightSelected) ? + + + : + undefined} + + + +
+) + +export default QuickAccessItem + diff --git a/src/components/QuickAccessItem/index.js b/src/components/QuickAccessItem/index.js new file mode 100644 index 0000000..60907c8 --- /dev/null +++ b/src/components/QuickAccessItem/index.js @@ -0,0 +1,121 @@ +import QuickAccessItem from "./QuickAccessItem" +import {setRightSelected, setLastSelected, setSelected} from "../../actions/selectedItem" +import mobileCheck from "../../utils/mobileCheck" +import env from "../../enviroment/envFrontEnd"; +import axios from "axios"; +import {connect} from "react-redux"; +import React from "react"; + +const currentURL = env.url; + +class QuickAccessItemContainer extends React.Component { + + constructor(props) { + super(props); + + this.failedToLoad = false; + + this.state = { + contextMenuPos: {}, + image: "/images/file-svg.svg", + imageClassname: "quickaccess__item__image" + } + } + + componentDidMount = () => { + + const hasThumbnail = this.props.metadata.hasThumbnail; + + if (hasThumbnail && !this.failedToLoad) { + this.getThumbnail(); + } + } + + getThumbnail = async() => { + + const thumbnailID = this.props.metadata.thumbnailID; + const imageClassname = "quickaccess__item__image quickaccess__item__image--no-opacity" + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")}, + responseType: 'arraybuffer' + }; + + await this.setState(() => ({ + ...this.state, + iimage: "/images/file-svg.svg", + imageClassname: "quickaccess__item__image" + })) + + axios.get(currentURL +`/file-service/thumbnail/${thumbnailID}`, config).then((results) => { + + const imgFile = new Blob([results.data]); + const imgUrl = URL.createObjectURL(imgFile); + + this.setState(() => ({ + ...this.state, + image: imgUrl, + imageClassname: imageClassname + })) + + }).catch((err) => { + console.log(err) + }) + } + getContextMenu = (e) => { + + e.preventDefault(); + + const isMobile = mobileCheck() + + const windowX = window.innerWidth; + const windowY = window.innerHeight; + + let styleObj = {right:0, left:0, top: "-3px", bottom: 0} + + const clientY = e.nativeEvent.clientY; + const clientX = e.nativeEvent.clientX; + + if (clientX > windowX / 2) { + + styleObj = {...styleObj, left:"unset", right:0} + + } else { + + styleObj = {...styleObj, left:0, right:"unset"} + } + + if (isMobile) { + + styleObj = {bottom: 0, left: "2px"} + } + + this.setState(() => ({ + ...this.state, + contextMenuPos: styleObj + })) + + this.props.dispatch(setSelected("quick-"+this.props._id)) + this.props.dispatch(setRightSelected("quick-"+this.props._id)) + this.props.dispatch(setLastSelected(0)); + } + + + + render() { + + return + } + +} + +const connectStateToProp = (state) => ({ + rightSelected: state.selectedItem.rightSelected, + selected: state.selectedItem.selected +}) + +export default connect(connectStateToProp)(QuickAccessItemContainer); + diff --git a/src/components/RightSection/RightSection.js b/src/components/RightSection/RightSection.js new file mode 100644 index 0000000..a8aa4e9 --- /dev/null +++ b/src/components/RightSection/RightSection.js @@ -0,0 +1,57 @@ +import RightSectionDetail from ".././RightSectionDetail" +import capitalize from "../../utils/capitalize"; +import bytes from "bytes"; +import moment from "moment"; +import React from "react"; + +const RightSection = (props) => { + + props.resetState(); + + props.prevID = props.selectedItem.id; + + return ( + +
+ + {props.selectedItem.name === "" ? + ( +
+ +

Select a file or folder to view its details

+
+ ) + : + ( +
+
+

{capitalize(props.selectedItem.name)}

+ +
+ +
+ {props.selectedItem.file ? () : ()} + {props.selectedItem.file ? () : undefined} + + + {props.selectedItem.isVideo ? + : + undefined} + + {props.selectedItem.link ? + props.getPublicStatus() + : undefined} + + {props.getTranscodeButton(props)} + +
+
+ )} + +
+ ) + +} + + +export default RightSection; \ No newline at end of file diff --git a/src/components/RightSection/index.js b/src/components/RightSection/index.js new file mode 100644 index 0000000..adfd2e9 --- /dev/null +++ b/src/components/RightSection/index.js @@ -0,0 +1,189 @@ +import RightSection from "./RightSection"; +import RightSectionDetail from ".././RightSectionDetail" +import {editFileMetadata} from "../../actions/files" +import {resetSelectedItem} from "../../actions/selectedItem"; +import env from "../../enviroment/envFrontEnd"; +import axios from "axios"; +import {connect} from "react-redux"; +import React from "react"; + +const currentURL = env.url; + +class RightSectionContainer extends React.Component { + + constructor(props) { + super(props); + + this.state = { + optimizing: false, + optimizing_finished: false, + optimizing_removed: false + } + + this.prevID = "" + } + + getFileExtension = (filename) => { + + const filenameSplit = filename.split("."); + + if (filenameSplit.length > 1) { + + const extension = filenameSplit[filenameSplit.length - 1] + + return extension.toUpperCase(); + + } else { + + return "Unknown" + } + + + } + + getSidebarClassName = (value) => { + + if (value==="gone") { + return "section section--right section--no-animation" + } else if (value) { + return "section section--right" + } else { + return "section section--right section--minimized" + } + } + + removeTranscodeVideo = (props, e) => { + + const headers = {'Authorization': "Bearer " + window.localStorage.getItem("token")} + + const data = {id: props.selectedItem.id} + + axios.delete(currentURL +'/file-service/transcode-video/remove', { + headers, + data + }).then(() => { + + this.props.dispatch(editFileMetadata(props.selectedItem.id, {transcoded: undefined})) + + this.setState(() => ({ + ...this.state, + optimizing: false, + optimizing_finished: false, + optimizing_removed: true + })) + + }).catch((err) => { + console.log(err) + }) + } + + transcodeVideo = (props, e) => { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")}, + file: {_id: props.selectedItem.id} + }; + + const data = {file: {_id: props.selectedItem.id}} + + this.setState(() => ({ + ...this.state, + optimizing: true + })) + + + axios.post(currentURL +'/file-service/transcode-video', data,config) + .then((response) => { + + const data = response.data; + + if (data === "Finished") { + + this.props.dispatch(editFileMetadata(props.selectedItem.id, {isVideo: true, transcoded: true})) + + this.setState(() => ({ + ...this.state, + optimizing: false, + optimizing_finished: true, + optimizing_removed: false + })) + } + + }).catch((err) => { + console.log(err) + }) + } + + getTranscodeButton = (props) => { + + if (!props.selectedItem.isVideo) { + return undefined; + } + + if (this.state.optimizing && !this.state.optimizing_finished) { + return (
+ +
) + + } else if ((props.selectedItem.transcoded || this.state.optimizing_finished) && !this.state.optimizing_removed) { + return (
+ +
) + } else { + return (
+ +
) + } + } + + getPublicStatus = () => { + + if (this.props.selectedItem.linkType === "one") { + + return + + } else { + + return + } + } + + resetState = () => { + + if (this.prevID !== "" && this.prevID !== this.props.selectedItem.id) { + + this.setState(() => ({ + ...this.state, + optimizing: false, + optimizing_finished: false + })) + } + } + + resetSelected = () => { + + this.props.dispatch(resetSelectedItem()); + } + + render() { + + return + } +} + +const connectPropToState = (state) => ({ + selectedItem: state.selectedItem, + showSideBar: state.main.showSideBar, + selected: state.main.selected +}) + +export default connect(connectPropToState)(RightSectionContainer) \ No newline at end of file diff --git a/src/components/RightSectionDetail/index.js b/src/components/RightSectionDetail/index.js new file mode 100644 index 0000000..fb187f8 --- /dev/null +++ b/src/components/RightSectionDetail/index.js @@ -0,0 +1,10 @@ +import React from "react"; + +export default ({title, body, first=true}) => ( + +
+
{title}
+
{body}
+
+ +) \ No newline at end of file diff --git a/src/components/SettingsMenu/SettingsMenu.js b/src/components/SettingsMenu/SettingsMenu.js new file mode 100644 index 0000000..1cf879b --- /dev/null +++ b/src/components/SettingsMenu/SettingsMenu.js @@ -0,0 +1,175 @@ +import StorageWidget from ".././StorageWidget"; +import React from "react"; + +const SettingsMenu = (props) => ( + +
+ + + {/* HEADER */} + +
+ + +
+ +

Settings

+
+ + + + +
+ +
+ + + {/*Storage*/} + +
+ +

Storage

+ +
+ + + +
+ +
+ + + {/*List Style*/} + +
+ +
+

Default List Style

+ +
+ +
+ + + +
+ +
+ + + + {/*List Size*/} +
+ +
+

Default List Size

+ +
+ + +
+ + + +
+ +
+ + + {/*Change password*/} +
+ +

Change Password

+ +
+ + +
+ +
+ + + {/*Logout*/} +
+ +

Logout

+ +
+ + +
+ +
+ + + + {/*Logout-all*/} +
+ +
+

Logout All

+ +
+ +
+ + +
+ +
+ + + + {/*Delete-all*/} +
+ +
+

Delete All

+ +
+ +
+ + +
+ +
+ + +
+ + + + +
+ + +) + +export default SettingsMenu + diff --git a/src/components/SettingsMenu/index.js b/src/components/SettingsMenu/index.js new file mode 100644 index 0000000..4299cbd --- /dev/null +++ b/src/components/SettingsMenu/index.js @@ -0,0 +1,295 @@ +import SettingsMenu from "./SettingsMenu"; +import {setFiles} from "../../actions/files" +import {setQuickFiles} from "../../actions/quickFiles"; +import {setFolders} from "../../actions/folders" +import {resetSelectedItem, setSelected} from "../../actions/selectedItem"; +import {startSetStorage} from "../../actions/storage" +import {hideSettings} from "../../actions/settings"; +import {startLogout, startLogoutAll} from "../../actions/auth"; +import {resetCurrentRoute} from "../../actions/routes" +import env from "../../enviroment/envFrontEnd"; +import Swal from "sweetalert2"; +import axios from "axios"; +import {connect} from "react-redux"; +import React from "react"; + +const currentURL = env.url; + +class SettingsMenuContainer extends React.Component { + + constructor(props) { + super(props); + + this.state = { + style: "List (Default)", + size: "50 (Default)" + } + } + + getProgressWidth = () => { + + const totalSpace = this.props.storage.total + const usedSpace = this.props.storage.total - this.props.storage.available; + + const difference = (usedSpace / totalSpace) * 100; + + return difference + "%" + } + + closeSettings = () => { + + this.props.dispatch(hideSettings()); + } + + setStyle = (e) => { + + const value = e.target.value; + + window.localStorage.setItem("list-style", value) + + this.setState(() => ({ + ...this.state, + style: value + })) + } + + setSize = (e) => { + + const value = e.target.value; + + window.localStorage.setItem("list-size", value) + + this.setState(() => ({ + ...this.state, + size: value + })) + + } + + componentDidMount = () => { + + const prevStyle = window.localStorage.getItem("list-style"); + const prevSize = window.localStorage.getItem("list-size"); + + if (prevSize && prevStyle) { + + this.setState(() => ({ + ...this.state, + size: prevSize, + style: prevStyle + })) + + } else if (prevStyle) { + + this.setState(() => ({ + ...this.state, + style: prevStyle + })) + + } else if (prevSize) { + + this.setState(() => ({ + ...this.state, + size: prevSize + })) + + } + } + + logout = () => { + + this.props.dispatch(resetCurrentRoute()) + this.props.dispatch(setFolders([])); + this.props.dispatch(setFiles([])); + this.props.dispatch(setQuickFiles([])); + this.props.dispatch(hideSettings()) + this.props.dispatch(startLogout()) + } + + logoutAll = () => { + + this.props.dispatch(resetCurrentRoute()) + this.props.dispatch(setFolders([])); + this.props.dispatch(setFiles([])); + this.props.dispatch(setQuickFiles([])); + this.props.dispatch(hideSettings()); + this.props.dispatch(startLogoutAll()); + } + + changePassword = () => { + + Swal.mixin({ + input: 'password', + confirmButtonText: 'Next →', + showCancelButton: true, + }).queue([ + { + title: 'Enter Current Password', + }, + 'Enter New Password', + 'Confirm New Password' + ]).then((result) => { + + if (result.value.length !== 3) {return} + + if (result.value[1] !== result.value[2]) { + + Swal.fire({ + icon: 'error', + title: 'Error', + text: 'New passwords do not match', + }) + + } else if (result.value[0].length === 0) { + + Swal.fire({ + icon: 'error', + title: 'Error', + text: 'Enter a new password', + }) + + } else { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + const data = { + newPassword: result.value[1], + oldPassword: result.value[0] + } + + axios.post(currentURL +`/user-service/change-password/`, data, config).then((results) => { + + const newToken = results.data; + window.localStorage.setItem("token", newToken); + + Swal.fire( + 'Password Changed', + 'All other sessions have been logged out', + 'success' + ) + + + }).catch((err) => { + console.log(err) + Swal.fire({ + icon: 'error', + title: 'Error', + text: 'Error changing password', + }) + }) + + } + + }).catch((e) => { + console.log(e) + }) + } + + infoStyle = () => { + + Swal.fire({ + icon: 'info', + title: 'Default Style', + text: 'Changes the default layout of files/folders when the homepage first loads', + + }) + } + + infoSize = () => { + + Swal.fire({ + icon: 'info', + title: 'Default Size', + text: 'Changes how many files are loaded when the homepage first loads.', + + }) + + } + + infoLogoutAll = () => { + + Swal.fire({ + icon: 'info', + title: 'Logout All', + text: 'Logouts all sessions of this account, also removes all temporary tokens.', + + }) + } + + infoDeleteAll = () => { + + Swal.fire({ + icon: 'info', + title: 'Delete All', + text: 'Deletes all files and folders, this cannot be undone.', + + }) + } + + deleteAll = () => { + + Swal.fire({ + title: 'Delete All?', + text: "You cannot undo this action.", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, delete all' + }).then((result) => { + if (result.value) { + + const headers = {'Authorization': "Bearer " + window.localStorage.getItem("token")} + + axios.delete(currentURL +`/folder-service/remove-all`, { + headers + }).then((results) => { + + Swal.fire( + 'Deleted', + 'All files/folders have been deleted', + 'success' + ) + + this.props.dispatch(setFolders([])); + this.props.dispatch(setFiles([])); + this.props.dispatch(setQuickFiles([])); + this.props.dispatch(resetSelectedItem()); + this.props.dispatch(setSelected("")) + this.props.dispatch(startSetStorage()) + }) + + + } + }) + } + + render() { + + return + } + +} + +const connectStateToProp = (state) => ({ + + showSettings: state.settings.showSettings, +}) + +export default connect(connectStateToProp)(SettingsMenuContainer); + diff --git a/src/components/ShareMenu/ShareMenu.js b/src/components/ShareMenu/ShareMenu.js new file mode 100644 index 0000000..e678fc4 --- /dev/null +++ b/src/components/ShareMenu/ShareMenu.js @@ -0,0 +1,49 @@ +import React from "react"; + +const currentURL = process.env.URL + +const ShareMenu = React.forwardRef((props, ref) => { + + if (props.shareSelected === "") { + + return ( +
+ +
+ ) + + } else { + + return ( +
+ + {props.shareSelected.metadata.link ? + +
+ +
+ +

{currentURL}/download-page/{props.shareSelected._id}/{props.shareSelected.metadata.link}

+
+ + +
+ + : +
+ + +

Or

+ +
+ } + +
+ ) + + } + +}) + +export default ShareMenu \ No newline at end of file diff --git a/src/components/ShareMenu/index.js b/src/components/ShareMenu/index.js new file mode 100644 index 0000000..bd17b9f --- /dev/null +++ b/src/components/ShareMenu/index.js @@ -0,0 +1,204 @@ +import ShareMenu from "./ShareMenu"; +import {editFile} from "../../actions/files" +import {editSelectedItem, setShareSelected, editShareSelected} from "../../actions/selectedItem" +import env from "../../enviroment/envFrontEnd"; +import axios from "axios"; +import Swal from "sweetalert2" +import copy from "copy-text-to-clipboard"; +import {connect} from "react-redux"; +import React from "react"; + +const currentURL = env.url; + +class ShareMenuContainer extends React.Component { + + constructor(props) { + super(props); + + this.wrapperRef = React.createRef(); + + this.showingSwal = false; + + this.state = { + title: "No Link" + } + } + + handleClickOutside = (e) => { + + if (this.wrapperRef && !this.wrapperRef.current.contains(event.target) && !this.showingSwal) { + + this.props.dispatch(setShareSelected("")) + } + } + + hide = () => { + + this.props.dispatch(setShareSelected("")) + } + + componentWillUnmount = () => { + document.removeEventListener('mousedown', this.handleClickOutside); + } + + componentDidMount = () => { + document.addEventListener('mousedown', this.handleClickOutside); + } + + makePublic = () => { + + this.showingSwal = true; + + Swal.fire({ + title: 'Are you sure?', + text: "Making this public, will allow anyone to have access to it", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, make public' + }).then((result) => { + + this.showingSwal = false; + + if (result.value) { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + axios.patch(currentURL +`/file-service/make-public/${this.props.shareSelected._id}`, undefined,config).then((results) => { + + this.props.dispatch(editFile(this.props.shareSelected._id,{"metadata": { + ...this.props.shareSelected.metadata, + link: results.data, + linkType: "public" + }})) + + this.props.dispatch(editSelectedItem({link: results.data, + linkType: "public"})) + + + this.props.dispatch(editShareSelected({"metadata": { + ...this.props.shareSelected.metadata, + link: results.data, + linkType: "public" + }})) + + }).catch((err) => { + console.log(err) + }) + + + } + }) + } + + makeOne = () => { + + this.showingSwal = true; + + Swal.fire({ + title: 'Are you sure?', + text: "One time link, will allow anyone to access this file once", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + confirmButtonText: 'Yes, create link' + }).then((result) => { + + this.showingSwal = false; + + if (result.value) { + + const config = { + headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} + }; + + axios.patch(currentURL +`/file-service/make-one/${this.props.shareSelected._id}`, undefined,config).then((results) => { + + this.props.dispatch(editFile(this.props.shareSelected._id,{"metadata": { + ...this.props.shareSelected.metadata, + link: results.data, + linkType: "one" + }})) + + this.props.dispatch(editSelectedItem({link: results.data, + linkType: "one"})) + + + this.props.dispatch(editShareSelected({"metadata": { + ...this.props.shareSelected.metadata, + link: results.data, + linkType: "one" + }})) + + }).catch((err) => { + console.log(err) + }) + + + } + }) + + } + + removeLink = () => { + + const headers = {'Authorization': "Bearer " + window.localStorage.getItem("token")} + + axios.delete(currentURL +`/file-service/remove-link/${this.props.shareSelected._id}`, { + headers + }).then(() => { + + this.props.dispatch(editFile(this.props.shareSelected._id,{"metadata": { + ...this.props.shareSelected.metadata, + link: undefined, + linkType: undefined + }})) + + this.props.dispatch(editSelectedItem({link: undefined, + linkType: undefined})) + + + this.props.dispatch(editShareSelected({"metadata": { + ...this.props.shareSelected.metadata, + link: undefined, + linkType: undefined + }})) + }).catch((err) => { + console.log(err) + }) + } + + copyLink = () => { + + const link = currentURL + "/download-page/" + this.props.shareSelected._id + "/" + this.props.shareSelected.metadata.link; + + copy(link) + + Swal.fire("Link Copied") + } + + render() { + + return + } +} + +const connectStateToProps = (state) => ({ + + shareSelected: state.selectedItem.shareSelected +}) + +export default connect(connectStateToProps)(ShareMenuContainer); diff --git a/src/components/Spacer/index.js b/src/components/Spacer/index.js new file mode 100644 index 0000000..6a3ef51 --- /dev/null +++ b/src/components/Spacer/index.js @@ -0,0 +1,7 @@ +import React from "react"; + +export default ({title}) => ( +
+

{title}

+
+) \ No newline at end of file diff --git a/src/components/Spinner/index.js b/src/components/Spinner/index.js new file mode 100644 index 0000000..65211c2 --- /dev/null +++ b/src/components/Spinner/index.js @@ -0,0 +1,7 @@ +import React from "react"; + +export default () => ( +
+ +
+) \ No newline at end of file diff --git a/src/components/SpinnerLogin/index.js b/src/components/SpinnerLogin/index.js new file mode 100644 index 0000000..402075d --- /dev/null +++ b/src/components/SpinnerLogin/index.js @@ -0,0 +1,7 @@ +import React from "react"; + +export default () => ( +
+ +
+) \ No newline at end of file diff --git a/src/components/StorageWidget/StorageWidget.js b/src/components/StorageWidget/StorageWidget.js new file mode 100644 index 0000000..62b88bf --- /dev/null +++ b/src/components/StorageWidget/StorageWidget.js @@ -0,0 +1,24 @@ +import bytes from "bytes"; +import React from "react" + +const StorageWidget = (props) => ( +
+ +

Storage

+ +
+ +
+ +
+ +
+ +

{bytes(props.storage.total - props.storage.available)} of {bytes(props.storage.total)}

+
+) + + +export default StorageWidget \ No newline at end of file diff --git a/src/components/StorageWidget/index.js b/src/components/StorageWidget/index.js new file mode 100644 index 0000000..cefcaad --- /dev/null +++ b/src/components/StorageWidget/index.js @@ -0,0 +1,35 @@ +import StorageWidget from "./StorageWidget" +import {connect} from "react-redux"; +import React from "react"; + +class StorageWidgetContainer extends React.Component { + + constructor(props) { + super(props); + } + + + getProgressWidth = (props) => { + + const totalSpace = props.storage.total + const usedSpace = props.storage.total - props.storage.available; + + const difference = (usedSpace / totalSpace) * 100; + + return difference + "%" + } + + render() { + + return + } + +} + +const connectStateToProp = (state) => ({ + storage: state.storage, +}) + +export default connect(connectStateToProp)(StorageWidgetContainer) \ No newline at end of file diff --git a/src/components/Subbar/Subbar.js b/src/components/Subbar/Subbar.js new file mode 100644 index 0000000..7c48ef8 --- /dev/null +++ b/src/components/Subbar/Subbar.js @@ -0,0 +1,85 @@ +import React from "react"; + +export class Subbar extends React.Component { + + constructor(props) { + super(props); + } + + render() { + + return ( +
+ + {!this.props.isMobile ? undefined : + +
+ + + +

New

+ +
+
+ +

Folder

+
+ +
+ + +

File Upload

+ + +
+ +
+ +

Folder Upload

+
+
+
+ + + } + + +
+ + {(this.props.parentList.length !== 1 || this.props.currentlySearching)? + ( +
+ + {this.props.parentNameList.map((parent, index) => { + + const parentID = this.props.parentList[index]; + + return ( +
{this.props.itemClick(parentID, parent, true)}}> +

{parent}

+ + {index !== this.props.parentNameList.length - 1 ? () : undefined} + +
+ ) + })} + +
) + + : undefined} + + + +
+ + + + +
+ ) + } +} + + +export default Subbar \ No newline at end of file diff --git a/src/components/Subbar/index.js b/src/components/Subbar/index.js new file mode 100644 index 0000000..1b6d37f --- /dev/null +++ b/src/components/Subbar/index.js @@ -0,0 +1,158 @@ +import Subbar from "./Subbar"; +import {showSideBar, hideSideBar} from "../../actions/main"; +import {showAddOptions2} from "../../actions/addOptions"; +import {startAddFile} from "../../actions/files"; +import {startAddFolder} from "../../actions/folders" +import {enableListView, disableListView} from "../../actions/filter"; +import {resetSelected} from "../../actions/selectedItem"; +import Swal from "sweetalert2"; +import {connect} from "react-redux"; +import {history} from "../../routers/AppRouter"; +import React from "react"; +import mobileCheck from "../../utils/mobileCheck"; + +class SubbarContainer extends React.Component { + + constructor(props) { + super(props); + + this.uploadInput = React.createRef(); + this.wrapperRef = React.createRef(); + this.isMobile = mobileCheck(); + } + + + createFolder = async(e) => { + + let inputValue = "" + + const { value: folderName} = await Swal.fire({ + title: 'Enter Folder Name', + input: 'text', + inputValue: inputValue, + showCancelButton: true, + inputValidator: (value) => { + if (!value) { + return 'Please Enter a Name' + } + } + }) + + if (folderName === undefined || folderName === null) { + + return; + } + + const parent = this.props.parent; + const owner = this.props.auth.id; + const parentList = this.props.parentList; + + this.props.dispatch(startAddFolder(folderName, owner, parent, parentList)); + } + + handleClickOutside = (e) => { + + if (this.wrapperRef && !this.wrapperRef.current.contains(event.target) && this.props.showAddOptions2) { + this.addButtonEvent(); + } + } + + componentDidMount = () => { + + if (this.isMobile) { + document.addEventListener('mousedown', this.handleClickOutside); + } + } + + componentWillUnmount = () => { + + if (this.isMobile) { + document.removeEventListener('mousedown', this.handleClickOutside); + } + } + + addButtonEvent = () => { + + const currentAddOptions = !this.props.showAddOptions2 + + this.props.dispatch(showAddOptions2(currentAddOptions)) + } + + handleUpload = (e) => { + e.preventDefault(); + + this.props.dispatch(startAddFile(this.uploadInput.current, this.props.parent, this.props.parentList)) + this.uploadInput.current.value = "" + } + + showSideBarEvent = () => { + + let show = this.props.showSideBar; + + if (show === "gone") { + show = false; + } + + if (show) { + this.props.dispatch(hideSideBar()) + } else { + this.props.dispatch(showSideBar()) + } + } + + showListViewEvent = () => { + + let listView = this.props.listView; + + if (listView) { + this.props.dispatch(resetSelected()) + this.props.dispatch(disableListView()) + } else { + this.props.dispatch(resetSelected()) + this.props.dispatch(enableListView()) + } + } + + itemClick = (id) => { + + if (id === "/") { + + history.push("/home") + + } else { + + history.push(`/folder/${id}`) + } + + } + + render() { + + return + } + +} + +const connectPropToState = (state) => ({ + auth: state.auth, + listView: state.filter.listView, + parentList: state.parent.parentList, + parentNameList: state.parent.parentNameList, + parent: state.parent.parent, + sortBy: state.filter.sortBy, + showAddOptions2: state.addOptions.showAddOptions2, + showSideBar: state.main.showSideBar, + currentlySearching: state.filter.currentlySearching +}) + +export default connect(connectPropToState)(SubbarContainer) \ No newline at end of file diff --git a/src/components/SuggestedItem/index.js b/src/components/SuggestedItem/index.js new file mode 100644 index 0000000..27894d0 --- /dev/null +++ b/src/components/SuggestedItem/index.js @@ -0,0 +1,23 @@ +import moment from "moment" +import React from "react"; + +export default ({suggested, folder, itemClick}) => { + + const name = folder ? suggested.name : suggested.filename; + + return( +
itemClick(name)} className="header__input__suggested__item"> + +
+ + +

{name}

+ +
+ +

{folder ? moment(suggested.createdAt).format("L") : moment(suggested.uploadDate).format("L")}

+ +
+ ) + +} \ No newline at end of file diff --git a/src/components/UploadItem/UploadItem.js b/src/components/UploadItem/UploadItem.js new file mode 100644 index 0000000..6ef248e --- /dev/null +++ b/src/components/UploadItem/UploadItem.js @@ -0,0 +1,40 @@ +import { CircularProgressbar } from 'react-circular-progressbar'; +import {connect} from "react-redux"; +import React from "react"; + +const UploadItem = (props) => { + + const completed = props.completed; + const uploadImage = props.getUploadImage(); + const canceled = props.canceled; + + return ( +
+

{props.name}

+ + + + +
+ +
+ + + +
+ +
+ + + +
+ ) +} + +export default connect()(UploadItem); diff --git a/src/components/UploadItem/index.js b/src/components/UploadItem/index.js new file mode 100644 index 0000000..272b7f8 --- /dev/null +++ b/src/components/UploadItem/index.js @@ -0,0 +1,90 @@ +import UploadItem from "./UploadItem" +import {cancelUpload} from "../../actions/uploads"; +import {connect} from "react-redux"; +import React from "react"; + +class UploadItemContainer extends React.Component { + + constructor(props) { + super(props); + + const completed = this.props.completed; + const canceled = this.props.canceled; + + this.uploadFinished = false; + + this.state = { + progressBarStyle: {visibility: (completed || canceled) ? "hidden" : "visible"}, + stopUploadButtonStyle: (completed || canceled) ? {visibility:"visible", opacity: 1} : {visibility: "hidden", opacity: 0} + } + } + + hideProgress = () => { + + this.setState(() => ({ + ...this.state, + progressBarStyle: {visibility: "hidden"}, + stopUploadButtonStyle: {opacity: 1, visibility: "visible"} + + })) + } + + showProgress = () => { + + this.setState(() => ({ + ...this.state, + progressBarStyle: {visibility: "visible"}, + stopUploadButtonStyle: {opacity: 0, visibility: "hidden"} + + })) + } + + cancelUploadEvent = () => { + + if (!this.props.completed) { + this.props.source.cancel("User Canceled Upload") + this.props.dispatch(cancelUpload(this.props.id)) + } + + } + + getUploadImage = () => { + + if (this.props.canceled) { + return "/images/error-red.png" + } else if (this.props.completed) { + return "/images/check-green.png" + } else { + return "/images/close-circle-outline.svg" + } + + } + + componentDidUpdate = () => { + + if (this.props.completed && !this.uploadFinished) { + + this.uploadFinished = true; + + this.setState(() => ({ + ...this.state, + progressBarStyle: {visibility: "hidden"}, + stopUploadButtonStyle: {opacity: 1, visibility: "visible"} + + })) + } + } + + render() { + + return + } +} + +export default connect()(UploadItemContainer) \ No newline at end of file diff --git a/src/components/Uploader/Uploader.js b/src/components/Uploader/Uploader.js new file mode 100644 index 0000000..c073cbf --- /dev/null +++ b/src/components/Uploader/Uploader.js @@ -0,0 +1,24 @@ +import UploadItem from ".././UploadItem"; +import React from "react"; + +const Uploader = React.forwardRef((props, ref) => ( +
+ +
+ +

Uploads

+ +
+ props.minimizeUploader()} src={props.uploaderShow ? "/images/down-arrow-white.png" : "/images/up-arrow-white.png"}/> + props.cancelAllUploadsEvent()} src="/images/close-white-png.png"/> +
+ +
+ +
+ {props.uploads.map((upload) => )} +
+
+)) + +export default Uploader; diff --git a/src/components/Uploader/index.js b/src/components/Uploader/index.js new file mode 100644 index 0000000..e622218 --- /dev/null +++ b/src/components/Uploader/index.js @@ -0,0 +1,61 @@ +import Uploader from "./Uploader"; +import {showUploader, hideUploader} from "../../actions/main"; +import {startCancelAllUploads} from "../../actions/uploads"; +import {connect} from "react-redux"; +import React from "react"; + +class UploaderContainer extends React.Component { + + constructor(props) { + super(props); + + this.uploaderWrapper = React.createRef() + this.prevUploadLength = 0; + } + + minimizeUploader = () => { + + const uploaderShow = this.props.uploaderShow; + const dispatch = this.props.dispatch; + + if (uploaderShow) { + + dispatch(hideUploader()) + + } else { + + dispatch(showUploader()) + } + } + + cancelAllUploadsEvent = () => { + + this.props.dispatch(startCancelAllUploads(this.props.uploads)) + } + + componentDidUpdate = () => { + + if (this.props.uploads.length !== this.prevUploadLength) { + + this.uploaderWrapper.current.scrollTop = 0; + } + + this.prevUploadLength = this.props.uploads.length; + } + + render() { + return + } + +} + +const connectStateToProp = (state) => ({ + uploads: state.uploads, + uploaderShow: state.main.uploaderShow +}) + +export default connect(connectStateToProp)(UploaderContainer); \ No newline at end of file diff --git a/src/controllers/file.js b/src/controllers/file.js new file mode 100644 index 0000000..42ff660 --- /dev/null +++ b/src/controllers/file.js @@ -0,0 +1,575 @@ +const FileService = require("../services/FileService") + +const fileService = new FileService() + +class FileController { + + constructor() { + + } + + + async getThumbnail(req, res) { + + if (!req.user) { + + return; + } + + try { + + const user = req.user; + const id = req.params.id; + + const decryptedThumbnail = await fileService.getThumbnail(user, id); + + res.send(decryptedThumbnail); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send(); + } + + } + + async getFullThumbnail(req, res) { + + if (!req.user) { + return; + } + + try { + + const user = req.user; + const fileID = req.params.id; + + await fileService.getFullThumbnail(user, fileID, res); + + } catch (e) { + const code = e.code || 500; + console.log(e.message, e.exception) + return res.status(code).send(); + } + } + + async uploadFile(req, res) { + + if (!req.user) { + + return + } + + try { + + const user = req.user; + const busboy = req.busboy; + + req.pipe(busboy); + + const file = await fileService.upload(user, busboy, req); + + res.send(file); + + console.log("file uploaded"); + + } catch (e) { + const code = e.code || 500; + console.log(e.message, e.exception) + return res.status(code).send(); + } + } + + async getPublicDownload(req, res) { + + try { + + const ID = req.params.id; + const tempToken = req.params.tempToken; + + await fileService.publicDownload(ID, tempToken, res); + + } catch (e) { + + const code = e.code || 500; + const message = e.message || e; + + console.log(message, e); + res.status(code).send(); + } + } + + async removeLink(req, res) { + + if (!req.user) { + return; + } + + try { + + const id = req.params.id; + const userID = req.user._id; + + await fileService.removeLink(userID, id) + + res.send(); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send(); + } + + } + + async makePublic(req, res) { + + if (!req.user) { + return; + } + + try { + + const fileID = req.params.id; + const user = req.user; + + const token = await fileService.makePublic(user, fileID); + + res.send(token); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send(); + } + } + + async getPublicInfo(req, res) { + + try { + + const id = req.params.id; + const tempToken = req.params.tempToken; + + const file = await fileService.getPublicInfo(id, tempToken); + + res.send(file); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async makeOneTimePublic(req, res) { + + if (!req.user) { + return; + } + + try { + + const id = req.params.id; + const userID = req.user._id; + + const token = await fileService.makeOneTimePublic(userID, id); + + res.send(token); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + + } + + async getFileInfo(req, res) { + + if (!req.user) { + return; + } + + try { + + const fileID = req.params.id; + const userID = req.user._id; + + const file = await fileService.getFileInfo(userID, fileID); + + res.send(file); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async getQuickList(req, res) { + + if (!req.user) { + return; + } + + try { + + const userID = req.user._id; + + const quickList = await fileService.getQuickList(userID); + + res.send(quickList); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async getList(req, res) { + + if (!req.user) { + return + } + + try { + + const query = req.query; + const userID = req.user._id; + + const fileList = await fileService.getList(userID, query); + + res.send(fileList); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async getDownloadToken(req, res) { + + if (!req.user) { + return + } + + try { + + const user = req.user; + + const tempToken = await fileService.getDownloadToken(user); + + res.send({tempToken}); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async getDownloadTokenVideo(req, res) { + + if (!req.user) { + return + } + + try { + + const user = req.user; + const cookie = req.headers.uuid + + const tempToken = await fileService.getDownloadTokenVideo(user, cookie); + + res.send({tempToken}); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async removeTempToken(req, res) { + + if (!req.user) { + return + } + + try { + + const user = req.user + const tempToken = req.params.tempToken; + + await fileService.removeTempToken(user, tempToken); + + res.send(); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async transcodeVideo(req, res) { + + if (!req.user) { + + return; + } + + try { + + console.log("transcode request", req.body.file._id) + + const user = req.user; + const body = req.body; + + await fileService.transcodeVideo(user, body); + + res.send("Finished"); + + } catch (e) { + + const code = e.code || 500; + console.log(e.message, e.exception) + return res.status(code).send(); + } + } + + async removeTranscodeVideo(req, res) { + + if (!req.user) { + + return; + } + + try { + + const fileID = req.body.id; + const userID = req.user._id; + + await fileService.removeTranscodeVideo(userID, fileID); + + res.send(); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async streamTranscodedVideo(req, res) { + + if (!req.auth || !req.user) { + return; + } + + try { + + console.log("stream request transcoded", req.params.id) + + const fileID = req.params.id; + const userID = req.user._id; + const headers = req.headers; + + await fileService.streamTranscodedVideo(userID, fileID, headers, res); + + } catch (e) { + + const code = e.code || 500; + const message = e.message || e; + + console.log(message, e); + res.status(code).send(); + } + } + + async streamVideo(req, res) { + + if (!req.auth || !req.user) { + return; + } + + try { + + const user = req.user; + const fileID = req.params.id; + const headers = req.headers; + + console.log("stream request", req.params.id) + + await fileService.streamVideo(user, fileID, headers, res); + + } catch (e) { + + const code = e.code || 500; + const message = e.message || e; + + console.log(message, e); + res.status(code).send(); + } + + } + + async downloadFile(req, res) { + + if (!req.auth || !req.user) { + return; + } + + try { + + console.log("download request") + + const user = req.user; + const fileID = req.params.id; + + await fileService.downloadFile(user, fileID, res); + + } catch (e) { + + const code = e.code || 500; + const message = e.message || e; + + console.log(message, e); + res.status(code).send(); + } + } + + async getSuggestedList(req, res) { + + if (!req.user) { + return; + } + + try { + + const userID = req.user._id; + let searchQuery = req.query.search || ""; + + const {fileList, folderList} = await fileService.getSuggestedList(userID, searchQuery); + + return res.send({folderList, fileList}) + + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } + + async renameFile(req, res) { + + if (!req.user) { + return; + } + + try { + + const fileID = req.body.id; + const title = req.body.title + const userID = req.user._id; + + await fileService.renameFile(userID, fileID, title) + + res.send(); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + + } + + async moveFile(req, res) { + + console.log("move request"); + + if (!req.user) { + return; + } + + try { + + const fileID = req.body.id; + const userID = req.user._id; + const parentID = req.body.parent; + + console.log(fileID, userID, parentID); + + await fileService.moveFile(userID, fileID, parentID); + + res.send(); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + + } + + async deleteFile(req, res) { + + if (!req.user) { + return; + } + + try { + + const userID = req.user._id; + const fileID = req.body.id; + + await fileService.deleteFile(userID, fileID); + + res.send() + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send() + } + } +} + +module.exports = FileController; diff --git a/src/controllers/folder.js b/src/controllers/folder.js new file mode 100644 index 0000000..8ee3529 --- /dev/null +++ b/src/controllers/folder.js @@ -0,0 +1,211 @@ +const FolderService = require("../services/FolderService"); +const folderService = new FolderService(); + +class FolderController { + + constructor() { + + } + + async uploadFolder(req, res) { + + if (!req.user) { + + return + } + + try { + + const data = req.body; + + const folder = await folderService.uploadFolder(data); + + res.send(folder); + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + } + } + + async deleteFolder(req, res) { + + if (!req.user) { + + return + } + + try { + + const userID = req.user._id; + const folderID = req.body.id; + const parentList = req.body.parentList + + await folderService.deleteFolder(userID, folderID, parentList); + + res.send(); + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + } + } + + async deleteAll(req, res) { + + if (!req.user) { + + return + } + + try { + + const userID = req.user._id; + + await folderService.deleteAll(userID); + + res.send(); + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + } + } + + async getInfo(req, res) { + + if (!req.user) { + return; + } + + try { + + const userID = req.user._id; + const folderID = req.params.id; + + const folder = await folderService.getFolderInfo(userID, folderID); + + res.send(folder); + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + } + } + + async getSubfolderList(req, res) { + + if (!req.user) { + + return + } + + try { + + const userID = req.user._id; + const folderID = req.query.id; + + const {folderIDList, folderNameList} = await folderService.getFolderSublist(userID, folderID); + + res.send({folderIDList, folderNameList}) + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + } + } + + async getFolderList(req, res) { + + if (!req.user) { + + return + } + + try { + + const userID = req.user._id; + const query = req.query; + + const folderList = await folderService.getFolderList(userID, query); + + res.send(folderList); + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + } + } + + async moveFolder(req, res) { + + if (!req.user) { + return; + } + + try { + + const userID = req.user._id; + const folderID = req.body.id; + const parent = req.body.parent; + + await folderService.moveFolder(userID, folderID, parent); + + res.send(); + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + + } + } + + async renameFolder(req, res) { + + if (!req.user) { + return; + } + + try { + + const userID = req.user._id; + const folderID = req.body.id; + const title = req.body.title + + await folderService.renameFolder(userID, folderID, title); + + res.send() + + } catch (e) { + + const code = e.code || 500 + + console.log(e); + res.status(code).send(e); + + } + } +} + +module.exports = FolderController; \ No newline at end of file diff --git a/src/controllers/storage.js b/src/controllers/storage.js new file mode 100644 index 0000000..4205c6c --- /dev/null +++ b/src/controllers/storage.js @@ -0,0 +1,31 @@ +const env = require("../enviroment/env") +const disk = require('diskusage'); + +class StorageController { + + constructor() { + + } + + async getStorageInfo(req, res) { + + if (!req.user) { + + return; + } + + try { + + const info = await disk.check(env.root); + + res.send(info) + + } catch (e) { + + console.log(e); + res.status(500).send(e); + } + } +} + +module.exports = StorageController; \ No newline at end of file diff --git a/src/controllers/user.js b/src/controllers/user.js new file mode 100644 index 0000000..6dd9202 --- /dev/null +++ b/src/controllers/user.js @@ -0,0 +1,148 @@ +const env = require("../enviroment/env"); +const UserService = require("../services/UserService") +const UserProvider = new UserService(); + +class UserController { + + constructor() { + + } + + async getUser(req, res) { + + try { + + const user = req.user; + user.tokens = undefined; + user.tempTokens = undefined; + user.password = undefined; + user.privateKey = undefined; + user.publicKey = undefined; + + res.send(user) + + } catch (e) { + + res.status(500).send(e); + } + } + + async login(req, res) { + + try { + + const body = req.body; + + const {user, token} = await UserProvider.login(body); + + user.tokens = undefined; + user.tempTokens = undefined; + user.password = undefined; + user.privateKey = undefined; + user.publicKey = undefined; + + res.status(200).send({user, token}); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send(e); + } + } + + async logout(req, res) { + + if (!req.user) { + + return; + } + + try { + + const user = req.user; + const token = req.encryptedToken; + + await UserProvider.logout(user, token); + + res.send(); + + } catch (e) { + + res.status(500).send(e); + } + } + + async logoutAll(req, res) { + + try { + + const user = req.user; + + await UserProvider.logoutAll(user); + + res.send(); + + } catch (e) { + + res.status(500).send(e); + } + } + + async createUser(req, res) { + + if (env.createAcctBlocked) { + + return await res.status(401).send() + } + + try { + + const {user, token} = await UserProvider.create(req.body); + + user.tokens = undefined; + user.tempTokens = undefined; + user.password = undefined; + user.privateKey = undefined; + user.publicKey = undefined; + + res.status(201).send({user, token}) + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send(e); + } + } + + async changePassword(req, res) { + + if (!req.user) { + + return; + } + + try { + + const user = req.user; + const oldPassword = req.body.oldPassword; + const newPassword = req.body.newPassword; + + const newToken = await UserProvider.changePassword(user, oldPassword, newPassword); + + res.send(newToken); + + } catch (e) { + + const code = e.code || 500; + + console.log(e); + res.status(code).send(e); + } + } +} + +module.exports = UserController; \ No newline at end of file diff --git a/src/db/mongoose.js b/src/db/mongoose.js new file mode 100644 index 0000000..9e5fb05 --- /dev/null +++ b/src/db/mongoose.js @@ -0,0 +1,10 @@ +const mongoose = require("mongoose"); +const env = require("../enviroment/env"); + +mongoose.connect(env.mongoURL, { + useNewUrlParser: true, + useCreateIndex: true, + useUnifiedTopology: true, +}) + +module.exports = mongoose; \ No newline at end of file diff --git a/src/db/mongooseServerUtils.js b/src/db/mongooseServerUtils.js new file mode 100644 index 0000000..4a4adcf --- /dev/null +++ b/src/db/mongooseServerUtils.js @@ -0,0 +1,12 @@ +const mongoose = require("mongoose"); +const env = require("../enviroment/env"); + +mongoose.connect(env.mongoURL, { + useNewUrlParser: true, + useCreateIndex: true, + useUnifiedTopology: true, + socketTimeoutMS: 30000000, + keepAlive: true, +}) + +module.exports = mongoose; \ No newline at end of file diff --git a/src/db/utils/fileUtils/index.js b/src/db/utils/fileUtils/index.js new file mode 100644 index 0000000..7cb92d5 --- /dev/null +++ b/src/db/utils/fileUtils/index.js @@ -0,0 +1,166 @@ +const mongoose = require("../../mongoose") +const conn = mongoose.connection; +const ObjectID = require('mongodb').ObjectID + +const DbUtil = function() { + + this.getPublicFile = async(fileID) => { + + let file = await conn.db.collection("fs.files") + .findOne({"_id": ObjectID(fileID)}); + + return file; + } + + this.removeOneTimePublicLink = async(fileID) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID)}, { + "$unset": {"metadata.linkType": "", "metadata.link": ""}}); + + return file; + + } + + this.removeLink = async(fileID, userID) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$unset": {"metadata.linkType": "", "metadata.link": ""}}) + + return file; + } + + this.makePublic = async(fileID, userID, token) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + return file; + } + + this.getPublicInfo = async(fileID, tempToken) => { + + const file = await conn.db.collection("fs.files") + .findOne({"_id": ObjectID(fileID), "metadata.link": tempToken}) + + return file; + } + + this.makeOneTimePublic = async(fileID, userID, token) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "one", "metadata.link": token}}) + + return file; + } + + this.getFileInfo = async(fileID, userID) => { + + const file = await conn.db.collection("fs.files") + .findOne({"metadata.owner": userID, "_id": ObjectID(fileID)}); + + return file; + } + + this.getQuickList = async(userID) => { + + const fileList = await conn.db.collection("fs.files") + .find({"metadata.owner": userID}) + .sort({uploadDate: -1}) + .limit(10) + .toArray(); + + return fileList; + } + + this.getList = async(queryObj, sortBy, limit) => { + + const fileList = await conn.db.collection("fs.files") + .find(queryObj) + .sort(sortBy) + .limit(limit) + .toArray(); + + + return fileList; + } + + this.removeTempToken = async(user, tempToken) => { + + user.tempTokens = user.tempTokens.filter((filterToken) => { + + return filterToken.token !== tempToken; + }); + + return user; + } + + this.removeTranscodeVideo = async(fileID, userID) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), "metadata.owner": userID}, + {"$unset": {"metadata.transcoded": "", "metadata.transcodedIV": "", + "metadata.transcoded_size": "", "metadata.transcodedID": ""}}) + + return file; + } + + this.getFileSearchList = async(userID, searchQuery) => { + + const fileList = await conn.db.collection("fs.files") + .find({"metadata.owner": userID, "filename": searchQuery}) + .limit(10) + .toArray(); + + return fileList; + } + + this.renameFile = async(fileID, userID, title) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, {"$set": {"filename": title}}) + + return file; + } + + this.moveFile = async(fileID, userID, parent, parentList) => { + + const file = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, {"$set": {"metadata.parent": parent, "metadata.parentList": parentList}}) + + return file; + } + + this.getFileListByParent = async(userID, parentListString) => { + + const fileList = await conn.db.collection("fs.files") + .find({"metadata.owner": userID, + "metadata.parentList": {$regex : `.*${parentListString}.*`}}).toArray(); + + return fileList; + } + + this.getFileListByOwner = async(userID) => { + + const fileList = await conn.db.collection("fs.files") + .find({"metadata.owner": userID}).toArray(); + + return fileList; + } + + this.removeChunksByID = async(fileID) => { + + await conn.db.collection("fs.chunks").deleteMany({files_id: fileID}); + } + +} + +module.exports = DbUtil; \ No newline at end of file diff --git a/src/db/utils/folderUtils/index.js b/src/db/utils/folderUtils/index.js new file mode 100644 index 0000000..53e6883 --- /dev/null +++ b/src/db/utils/folderUtils/index.js @@ -0,0 +1,61 @@ +const Folder = require("../../../models/folder"); +const ObjectID = require('mongodb').ObjectID + +const DbUtil = function() { + + this.getFolderSearchList = async(userID, searchQuery) => { + + const folderList = await Folder.find({"owner": userID, "name": searchQuery}).limit(10); + + return folderList; + } + + this.getFolderInfo = async(folderID, userID) => { + + const folder = await Folder.findOne({"owner": userID, "_id": ObjectID(folderID)}); + + return folder; + } + + this.getFolderListByParent = async(userID, parent, sortBy) => { + + const folderList = await Folder.find({"owner": userID, "parent": parent}) + .sort(sortBy); + + return folderList; + } + + this.getFolderListBySearch = async(userID, searchQuery, sortBy) => { + + const folderList = await Folder.find({"name": searchQuery,"owner": userID}) + .sort(sortBy); + + return folderList; + } + + this.moveFolder = async(folderID, userID, parent, parentList) => { + + const folder = await Folder.findOneAndUpdate({"_id": ObjectID(folderID), + "owner": userID}, {"$set": {"parent": parent, "parentList": parentList}}); + + return folder; + } + + this.renameFolder = async(folderID, userID, title) => { + + const folder = await Folder.findOneAndUpdate({"_id": ObjectID(folderID), + "owner": userID}, {"$set": {"name": title}}); + + return folder; + } + + this.findAllFoldersByParent = async(parentID, userID) => { + + const folderList = await Folder.find({"parentList": parentID, "owner": userID}); + + return folderList; + + } +} + +module.exports = DbUtil; \ No newline at end of file diff --git a/src/enviroment/env.js b/src/enviroment/env.js new file mode 100644 index 0000000..df55f36 --- /dev/null +++ b/src/enviroment/env.js @@ -0,0 +1,9 @@ +module.exports = { + key: process.env.KEY, + newKey: process.env.NEW_KEY, + password: process.env.PASSWORD, + createAcctBlocked: process.env.BLOCK_CREATE_ACCOUNT, + root: process.env.ROOT, + url: process.env.URL, + mongoURL: process.env.MONGODB_URL +} \ No newline at end of file diff --git a/src/enviroment/envFrontEnd.js b/src/enviroment/envFrontEnd.js new file mode 100644 index 0000000..d1b45c0 --- /dev/null +++ b/src/enviroment/envFrontEnd.js @@ -0,0 +1,6 @@ +const env = { + port: process.env.PORT, + url: process.env.URL +} + +export default env; \ No newline at end of file diff --git a/src/express-routers/file.js b/src/express-routers/file.js new file mode 100644 index 0000000..f9f9860 --- /dev/null +++ b/src/express-routers/file.js @@ -0,0 +1,56 @@ +const express = require("express"); +const router = new express.Router; +const auth = require("../middleware/auth"); +const tempAuth = require("../middleware/tempAuth") +const tempAuthVideo = require("../middleware/tempAuthVideo") +const FileController = require("../controllers/file"); + +const fileController = new FileController() + +router.post("/file-service/upload", auth, fileController.uploadFile); + +router.post("/file-service/transcode-video", auth, fileController.transcodeVideo); + +router.get("/file-service/thumbnail/:id", auth, fileController.getThumbnail); + +router.get("/file-service/full-thumbnail/:id", auth, fileController.getFullThumbnail); + +router.get("/file-service/public/download/:id/:tempToken", fileController.getPublicDownload); + +router.get("/file-service/public/info/:id/:tempToken", fileController.getPublicInfo); + +router.get("/file-service/info/:id", auth, fileController.getFileInfo); + +router.get("/file-service/quick-list", auth, fileController.getQuickList); + +router.get("/file-service/list", auth, fileController.getList); + +router.get("/file-service/download/get-token", auth, fileController.getDownloadToken); + +router.get("/file-service/download/get-token-video", auth, fileController.getDownloadTokenVideo); + +router.get("/file-service/stream-video-transcoded/:id/:tempToken/:uuid", tempAuthVideo, fileController.streamTranscodedVideo); + +router.get("/file-service/stream-video/:id/:tempToken/:uuid", tempAuthVideo, fileController.streamVideo); + +router.get("/file-service/download/:id/:tempToken", tempAuth, fileController.downloadFile); + +router.get("/file-service/suggested-list", auth, fileController.getSuggestedList); + +router.patch("/file-service/make-public/:id", auth, fileController.makePublic); + +router.patch("/file-service/make-one/:id", auth, fileController.makeOneTimePublic); + +router.patch("/file-service/rename", auth, fileController.renameFile); + +router.patch("/file-service/move", auth, fileController.moveFile); + +router.delete("/file-service/remove-link/:id", auth, fileController.removeLink); + +router.delete("/file-service/remove/token-video/:tempToken", auth, fileController.removeTempToken); + +router.delete("/file-service/transcode-video/remove", auth, fileController.removeTranscodeVideo); + +router.delete("/file-service/remove", auth, fileController.deleteFile); + +module.exports = router; \ No newline at end of file diff --git a/src/express-routers/folder.js b/src/express-routers/folder.js new file mode 100644 index 0000000..117be38 --- /dev/null +++ b/src/express-routers/folder.js @@ -0,0 +1,23 @@ +const express = require("express"); +const router = new express.Router; +const auth = require("../middleware/auth"); +const FolderController = require("../controllers/folder"); +const folderController = new FolderController(); + +router.post("/folder-service/upload", auth, folderController.uploadFolder); + +router.delete("/folder-service/remove", auth, folderController.deleteFolder); + +router.delete("/folder-service/remove-all", auth, folderController.deleteAll); + +router.get("/folder-service/info/:id", auth, folderController.getInfo); + +router.get("/folder-service/subfolder-list", auth, folderController.getSubfolderList); + +router.get("/folder-service/list", auth, folderController.getFolderList); + +router.patch("/folder-service/rename", auth, folderController.renameFolder); + +router.patch("/folder-service/move", auth, folderController.moveFolder); + +module.exports = router; \ No newline at end of file diff --git a/src/express-routers/storage.js b/src/express-routers/storage.js new file mode 100644 index 0000000..5fa436b --- /dev/null +++ b/src/express-routers/storage.js @@ -0,0 +1,9 @@ +const express = require("express"); +const router = new express.Router; +const auth = require("../middleware/auth"); +const StorageController = require("../controllers/storage"); +const storageController = new StorageController(); + +router.get("/storage-service/info", auth, storageController.getStorageInfo); + +module.exports = router; \ No newline at end of file diff --git a/src/express-routers/user.js b/src/express-routers/user.js new file mode 100644 index 0000000..089a4ff --- /dev/null +++ b/src/express-routers/user.js @@ -0,0 +1,19 @@ +const express = require("express"); +const router = new express.Router; +const auth = require("../middleware/auth"); +const UserController = require("../controllers/user"); +const userController = new UserController(); + +router.get("/user-service/user", auth, userController.getUser); + +router.post("/user-service/login", userController.login); + +router.post("/user-service/logout", auth, userController.logout); + +router.post("/user-service/logout-all", auth, userController.logoutAll); + +router.post("/user-service/create", userController.createUser); + +router.post("/user-service/change-password", auth, userController.changePassword); + +module.exports = router; \ No newline at end of file diff --git a/src/middleware/auth.js b/src/middleware/auth.js new file mode 100644 index 0000000..35085f2 --- /dev/null +++ b/src/middleware/auth.js @@ -0,0 +1,49 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/user"); +const env = require("../enviroment/env"); + +const auth = async(req, res, next) => { + + try { + + const token = req.header("Authorization").replace("Bearer ", ""); + + const decoded = await jwt.verify(token, env.password); + + const iv = decoded.iv; + + const user = await User.findOne({_id: decoded._id}); + const encrpytionKey = user.getEncryptionKey(); + + const encryptedToken = user.encryptToken(token, encrpytionKey, iv); + + let tokenFound = false; + for (let i = 0; i < user.tokens.length; i++) { + + const currentToken = user.tokens[i].token; + + if (currentToken === encryptedToken) { + tokenFound = true; + break; + } + } + + if (!user || !tokenFound) { + + throw new Error("User not found") + + } else { + + req.token = token; + req.encryptedToken = encryptedToken + req.user = user; + next(); + } + + } catch (e) { + console.log(e); + res.status(401).send({error: "Error Authenticating"}) + } +} + +module.exports = auth; \ No newline at end of file diff --git a/src/middleware/tempAuth.js b/src/middleware/tempAuth.js new file mode 100644 index 0000000..06dec37 --- /dev/null +++ b/src/middleware/tempAuth.js @@ -0,0 +1,57 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/user"); +const env = require("../enviroment/env"); + +const tempAuth = async(req, res, next) => { + + try { + + const token = req.params.tempToken; + + const decoded = await jwt.verify(token, env.password); + + const iv = decoded.iv; + + const user = await User.findOne({_id: decoded._id}) + const encrpytionKey = user.getEncryptionKey(); + + const encryptedToken = user.encryptToken(token, encrpytionKey, iv); + + let tokenFound = false; + for (let i = 0; i < user.tempTokens.length; i++) { + + const currentToken = user.tempTokens[i].token; + + if (currentToken === encryptedToken) { + tokenFound = true; + break; + } + } + + if (!user || !tokenFound) { + + throw new Error("User Not Found") + + } else { + + user.tempTokens = user.tempTokens.filter((filterToken) => { + + return filterToken.token !== encryptedToken + }) + + await user.save(); + + req.user = user; + req.auth = true; + req.encryptedTempToken = encryptedToken; + + next(); + } + + } catch (e) { + console.log(e); + res.status(401).send(); + } +} + +module.exports = tempAuth; \ No newline at end of file diff --git a/src/middleware/tempAuthVideo.js b/src/middleware/tempAuthVideo.js new file mode 100644 index 0000000..3f62d06 --- /dev/null +++ b/src/middleware/tempAuthVideo.js @@ -0,0 +1,56 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/user"); +const env = require("../enviroment/env"); + +const tempAuthVideo = async(req, res, next) => { + + try { + + const token = req.params.tempToken; + + const decoded = await jwt.verify(token, env.password); + + const iv = decoded.iv; + + if (req.params.uuid !== decoded.cookie) { + + throw new Error("Cookie mismatch") + } + + const user = await User.findOne({_id: decoded._id}) + const encrpytionKey = user.getEncryptionKey(); + + const encryptedToken = user.encryptToken(token, encrpytionKey, iv); + + let tokenFound = false; + for (let i = 0; i < user.tempTokens.length; i++) { + + const currentToken = user.tempTokens[i].token; + + if (currentToken === encryptedToken) { + tokenFound = true; + break; + } + } + + if (!user || !tokenFound) { + + throw new Error("User not found"); + + } else { + + await user.save(); + + req.user = user; + req.auth = true; + req.encryptedTempToken = encryptedToken; + next(); + } + + } catch (e) { + console.log(e); + res.status(401).send(); + } +} + +module.exports = tempAuthVideo; \ No newline at end of file diff --git a/src/models/folder.js b/src/models/folder.js new file mode 100644 index 0000000..290fe0c --- /dev/null +++ b/src/models/folder.js @@ -0,0 +1,32 @@ +const mongoose = require("mongoose"); + +const folderSchema = new mongoose.Schema({ + + name: { + type: String, + required: true, + }, + + parent: { + type: String, + required: true, + }, + + owner: { + type: String, + required: true + }, + + parentList: { + type: Array, + required:true + } + + +}, { + timestamps: true +}) + +const Folder = mongoose.model("Folder", folderSchema); + +module.exports = Folder; \ No newline at end of file diff --git a/src/models/thumbnail.js b/src/models/thumbnail.js new file mode 100644 index 0000000..fe873c9 --- /dev/null +++ b/src/models/thumbnail.js @@ -0,0 +1,25 @@ +const mongoose = require("mongoose"); + +const thumbnailSchema = new mongoose.Schema({ + + name: { + type: String, + required: true, + }, + owner: { + type: String, + required: true + }, + + data: { + type: Buffer, + required: true + } + +}, { + timestamps: true +}) + +const Thumbnail = mongoose.model("Thumbnail", thumbnailSchema); + +module.exports = Thumbnail; \ No newline at end of file diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 0000000..4c90626 --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,275 @@ +const mongoose = require("mongoose"); +const validator = require("validator"); +const crypto = require("crypto"); +const bcrypt = require("bcrypt"); +const jwt = require("jsonwebtoken"); +const env = require("../enviroment/env"); + +const userSchema = mongoose.Schema({ + + name: + { + type:String, + trim: true + }, + email: { + type: String, + required: true, + trim: true, + unique: true, + lowercase: true, + validate(value) { + if (!validator.isEmail(value)) { + throw new Error("Email is invalid"); + } + } + }, + password: { + type: String, + trim: true, + required: true, + validate(value) { + if (value.length < 6) { + throw new Error("Password Length Not Sufficent"); + } + } + }, + tokens:[{ + token: { + type: String, + required: true + } + }], + tempTokens: [{ + token: { + type: String, + required: true + } + }], + privateKey: { + type: String, + }, + publicKey: { + type: String, + } + +}, { + timestamps: true +}) + +// userSchema.virtual("files", { +// ref: "fs.files", +// localField: "_id" +// }) + +userSchema.pre("save", async function(next) { + + const user = this; + + if (user.isModified("password")) { + user.password = await bcrypt.hash(user.password, 8); + } + + next(); +}) + +userSchema.statics.findByCreds = async(email, password) => { + + const user = await User.findOne({email}); + + if (!user) { + + throw new Error("User not found") + } + + const isMatch = await bcrypt.compare(password, user.password); + + if (!isMatch) { + console.log("incorrect password") + throw new Error("Incorrect password"); + } + + return user; +} + +userSchema.methods.toJSON = function() { + + const user = this; + + const userObject = user.toObject(); + + delete userObject.password; + delete userObject.tokens; + delete userObject.tempTokens; + + return userObject; +} + +userSchema.methods.generateAuthToken = async function() { + + const iv = crypto.randomBytes(16); + + const user = this; + let token = jwt.sign({_id:user._id.toString(), iv}, env.password); + const publicKey = user.publicKey; + + const encryptionKey = user.getEncryptionKey(); + + const encryptedToken = user.encryptToken(token, encryptionKey, iv); + + user.tokens = user.tokens.concat({token: encryptedToken}); + + await user.save(); + return token; +} + +userSchema.methods.encryptToken = function(token, key, iv) { + + iv = Buffer.from(iv, "hex") + + const TOKEN_CIPHER_KEY = crypto.createHash('sha256').update(key).digest(); + const cipher = crypto.createCipheriv('aes-256-cbc', TOKEN_CIPHER_KEY, iv); + let encryptedText = cipher.update(token); + encryptedText = Buffer.concat([encryptedText, cipher.final()]).toString("hex"); + + return encryptedText; +} + +userSchema.methods.decryptToken = function(encryptedToken, key, iv) { + + encryptedToken = Buffer.from(encryptedToken, "hex"); + iv = Buffer.from(iv, "hex") + + const TOKEN_CIPHER_KEY = crypto.createHash('sha256').update(key).digest(); + const decipher = crypto.createDecipheriv('aes-256-cbc', TOKEN_CIPHER_KEY, iv) + + let tokenDecrypted = decipher.update(encryptedToken); + tokenDecrypted = Buffer.concat([tokenDecrypted, decipher.final()]).toString(); + + return tokenDecrypted; +} + +userSchema.methods.generateEncryptionKeys = async function() { + + const user = this; + const userPassword = user.password; + const masterPassword = env.key; + + const randomKey = crypto.randomBytes(32); + + const iv = crypto.randomBytes(16); + const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); + const cipher = crypto.createCipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let encryptedText = cipher.update(randomKey); + encryptedText = Buffer.concat([encryptedText, cipher.final()]); + + const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest(); + const masterCipher = crypto.createCipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv); + let masterEncryptedText = masterCipher.update(encryptedText); + masterEncryptedText = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex"); + + user.privateKey = masterEncryptedText; + user.publicKey = iv.toString("hex"); + + await user.save(); +} + +userSchema.methods.getEncryptionKey = function() { + + const user = this; + const userPassword = user.password; + const masterEncryptedText = user.privateKey; + const masterPassword = env.key; + const iv = Buffer.from(user.publicKey, "hex"); + + const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); + const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest(); + + const unhexMasterText = Buffer.from(masterEncryptedText, "hex"); + const masterDecipher = crypto.createDecipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv) + let masterDecrypted = masterDecipher.update(unhexMasterText); + masterDecrypted = Buffer.concat([masterDecrypted, masterDecipher.final()]) + + let decipher = crypto.createDecipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let decrypted = decipher.update(masterDecrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + + return decrypted; +} + +userSchema.methods.changeEncryptionKey = async function(randomKey) { + + const user = this; + const userPassword = user.password; + const masterPassword = env.key; + + const iv = crypto.randomBytes(16); + const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); + const cipher = crypto.createCipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let encryptedText = cipher.update(randomKey); + encryptedText = Buffer.concat([encryptedText, cipher.final()]); + + const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest(); + const masterCipher = crypto.createCipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv); + let masterEncryptedText = masterCipher.update(encryptedText); + masterEncryptedText = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex"); + + user.privateKey = masterEncryptedText; + user.publicKey = iv.toString("hex"); + + await user.save(); +} + +userSchema.methods.generateTempAuthToken = async function() { + + + // const user = this; + // let token = jwt.sign({_id:user._id.toString()}, env.password); + // const publicKey = user.publicKey; + + // const encryptionKey = user.getEncryptionKey(); + + // const encryptedToken = user.encryptToken(token, encryptionKey, publicKey); + + // user.tokens = user.tokens.concat({token: encryptedToken}); + + // await user.save(); + // return token; + + const iv = crypto.randomBytes(16); + + const user = this; + const token = jwt.sign({_id:user._id.toString(), iv}, env.password, {expiresIn:2}); + const publicKey = user.publicKey; + + const encryptionKey = user.getEncryptionKey(); + const encryptedToken = user.encryptToken(token, encryptionKey, iv); + + user.tempTokens = user.tempTokens.concat({token: encryptedToken}); + + await user.save(); + return token; +} + +userSchema.methods.generateTempAuthTokenVideo = async function(cookie) { + + const iv = crypto.randomBytes(16); + + const user = this; + const token = jwt.sign({_id:user._id.toString(), cookie, iv}, env.password, {expiresIn:"5h"}); + //const publicKey = user.publicKey; + + const encryptionKey = user.getEncryptionKey(); + const encryptedToken = user.encryptToken(token, encryptionKey, iv); + + //user.tempTokens = user.tempTokens.concat({token}); + user.tempTokens = user.tempTokens.concat({token: encryptedToken}); + + await user.save(); + return token; +} + + +const User = mongoose.model("User", userSchema); + +module.exports = User; \ No newline at end of file diff --git a/src/reducers/addOptions.js b/src/reducers/addOptions.js new file mode 100644 index 0000000..49eef86 --- /dev/null +++ b/src/reducers/addOptions.js @@ -0,0 +1,26 @@ +const defaultState = { + showAddOptions: false, + showAddOption2: false +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SHOW_ADD_OPTIONS": + + return { + ...state, + showAddOptions: action.show + } + + case "SHOW_ADD_OPTIONS2": + + return { + ...state, + showAddOptions2: action.show + } + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/auth.js b/src/reducers/auth.js new file mode 100755 index 0000000..f5fa86d --- /dev/null +++ b/src/reducers/auth.js @@ -0,0 +1,13 @@ +export default (state = {}, action) => { + switch (action.type) { + + case "LOGIN": + return { + id: action.id + } + case "LOGOUT": + return {} + default: + return state + } +} \ No newline at end of file diff --git a/src/reducers/files.js b/src/reducers/files.js new file mode 100644 index 0000000..6faeb14 --- /dev/null +++ b/src/reducers/files.js @@ -0,0 +1,60 @@ +export default (state = [], action) => { + + switch (action.type) { + + case "SET_FILES": + return action.files + + case "ADD_FILE": + + return [ + action.file, + ...state + ] + + case "LOAD_MORE_FILES": + + return [ + ...state, + ...action.files + ] + + case "EDIT_FILE": + + return state.map((file) => { + + if (file._id === action.id) { + + return {...file, ...action.file} + + } else { + + return file + } + }) + + case "EDIT_FILE_METADATA": + + return state.map((file) => { + + if (file._id === action.id) { + + return {...file, metadata: {...file.metadata, ...action.metadata}} + + } else { + + return file + } + }) + + case "REMOVE_FILE": + + return state.filter((file) => { + + return file._id !== action.id + }) + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/filter.js b/src/reducers/filter.js new file mode 100644 index 0000000..a7fe7b8 --- /dev/null +++ b/src/reducers/filter.js @@ -0,0 +1,81 @@ +const defaultState = { + sortBy: "date_desc", + limit: 50, + search: "", + listView: false, + currentlySearching: false, +} + +export default (state = defaultState, action) => { + + switch(action.type) { + + case "ENABLE_LIST_VIEW": { + + return { + ...state, + listView: true + } + } + + case "DISABLE_LIST_VIEW": { + + return { + ...state, + listView: false + } + } + + case "SET_SORT_BY": { + + return { + ...state, + sortBy: action.sortBy + } + + } + + case "RESET_Filters": { + return defaultState + } + + case "SET_LIMIT": + + return { + ...state, + limit: action.limit + } + + case "SET_SEARCH": + + return { + ...state, + search: action.search + } + + case "RESET_SEARCH": + + return { + ...state, + search: "" + } + + case "SET_CURRENTLY_SEARCHING": + + return { + ...state, + currentlySearching: true + } + + case "RESET_CURRENTLY_SEARCHING": + + return { + ...state, + currentlySearching: false + } + + default: + return state; + } + +} \ No newline at end of file diff --git a/src/reducers/folders.js b/src/reducers/folders.js new file mode 100644 index 0000000..e3da570 --- /dev/null +++ b/src/reducers/folders.js @@ -0,0 +1,39 @@ +export default (state = [], action) => { + + switch(action.type) { + + case "ADD_FOLDER": + + return [ + action.folder, + ...state + ] + + case "EDIT_FOLDER": + + return state.map((folder) => { + + if (folder._id === action.id) { + + return {...folder, ...action.folder} + + } else { + + return folder + } + }) + + case "SET_FOLDERS": + + return action.folders; + + case "REMOVE_FOLDER": + + return state.filter((folder) => { + return action.id !== folder._id; + }) + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/main.js b/src/reducers/main.js new file mode 100644 index 0000000..f8eec6b --- /dev/null +++ b/src/reducers/main.js @@ -0,0 +1,85 @@ +import moment from "moment" + +const defaultState = { + showSideBar: true, + uploaderShow: true, + resetItems: moment.now(), + loadMoreItems: true, + loading: false, + loginFailed: false, +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SET_LOGIN_FAILED": + + return { + ...state, + loginFailed: action.message + } + + case "SET_LOADING": + + return { + ...state, + loading: action.load + } + + + case "LOAD_MORE_ITEMS": + + return { + + ...state, + loadMoreItems: action.load + } + + case "RESET_ITEMS": + + return { + ...state, + resetItems: moment.now() + } + + case "SHOW_UPLOADER": { + + return { + ...state, + uploaderShow: true + } + } + + case "HIDE_UPLOADER": { + + return { + ...state, + uploaderShow: false + } + } + + case "SHOW_SIDEBAR": + + return { + ...state, + showSideBar: true + } + case "HIDE_SIDEBAR": + + return { + ...state, + showSideBar: false + } + + case "GONE_SIDEBAR": + + return { + ...state, + showSideBar: "gone" + } + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/mover.js b/src/reducers/mover.js new file mode 100644 index 0000000..839010a --- /dev/null +++ b/src/reducers/mover.js @@ -0,0 +1,33 @@ +const defaultState = { + id: "", + parent: "/", + isFile: true, +} + +export default (state = defaultState, action) => { + + switch(action.type) { + + case "SET_MOVER_ID": + + return { + ...state, + id: action.id, + parent: action.parent, + isFile: action.isFile + } + + case "RESET_MOVER_ID": + + return { + ...state, + id: "", + parent: "/", + isFile: action.isFile + } + + default: { + return state; + } + } +} \ No newline at end of file diff --git a/src/reducers/parent.js b/src/reducers/parent.js new file mode 100644 index 0000000..cf39cfd --- /dev/null +++ b/src/reducers/parent.js @@ -0,0 +1,69 @@ +const defaultState = { + parentList: ["/"], + parentNameList: ["Home"], + parent: "/" +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SET_PARENT": + + return { + ...state, + parent: action.parent, + } + case "ADD_PARENT_LIST": + + return { + ...state, + parentList: [...state.parentList, action.parent], + parentNameList: [...state.parentNameList, action.name] + } + + case "ADJUST_PARENT_LIST": + + return { + ...state, + parentList: action.parentList, + parentNameList: action.parentNameList + } + + case "SET_PARENT_LIST": + + return { + ...state, + parentList: action.parentList, + parentNameList: action.parentNameList + } + + case "REMOVE_PARENT_LIST": + + return { + ...state, + parentList: parentList.filter((parent) => { + return parent._id !== action.parent + }) + } + + case "RESET_PARENT_LIST": + + return { + ...state, + parentList: ["/"], + parentNameList: ["Home"] + } + + case "ADD_PARENT_NAME_LIST": + + return { + ...state, + parentNameList: [...state.parentNameList, action.name] + } + + default: + return state; + } + +} \ No newline at end of file diff --git a/src/reducers/photoViewer.js b/src/reducers/photoViewer.js new file mode 100644 index 0000000..9455890 --- /dev/null +++ b/src/reducers/photoViewer.js @@ -0,0 +1,26 @@ +const defaultState = { + id: "" +} + +export default (state = defaultState, action) => { + + switch(action.type) { + + case "SET_PHOTO_ID": + + return { + ...state, + id: action.id + } + + case "RESET_PHOTO_ID": + + return { + ...state, + id: "" + } + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/popupFile.js b/src/reducers/popupFile.js new file mode 100644 index 0000000..a13507d --- /dev/null +++ b/src/reducers/popupFile.js @@ -0,0 +1,31 @@ +const defaultState = { + showPopup: false +} + +export default (state = defaultState, action) => { + + switch(action.type) { + + case "SET_POPUP_FILE": + + return action.file + + case "SHOW_POPUP": + + return { + ...state, + showPopup: true + } + + case "HIDE_POPUP": + + return { + ...state, + showPopup: false + } + + default: + + return state; + } +} \ No newline at end of file diff --git a/src/reducers/quickFiles.js b/src/reducers/quickFiles.js new file mode 100644 index 0000000..fe867b0 --- /dev/null +++ b/src/reducers/quickFiles.js @@ -0,0 +1,40 @@ +const defaultState = []; + + +export default (state = defaultState, action) => { + + switch(action.type) { + + case "SET_QUICK_FILES": + + return action.files + + case "ADD_QUICK_FILE": + + return [action.file, ...state] + + case "EDIT_FILE": + + return state.map((file) => { + + if (file._id === action.id) { + + return {...file, ...action.file} + + } else { + + return file + } + }) + + case "REMOVE_FILE": + + return state.filter((file) => { + + return file._id !== action.id + }) + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/routes.js b/src/reducers/routes.js new file mode 100644 index 0000000..d3eadf7 --- /dev/null +++ b/src/reducers/routes.js @@ -0,0 +1,26 @@ +const defaultState = { + currentRoute: "/home" +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SET_CURRENT_ROUTE": + + return { + ...state, + currentRoute: action.route + } + + case "RESET_CURRENT_ROUTE": + + return { + ...state, + currentRoute: "/home" + } + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/selectedItem.js b/src/reducers/selectedItem.js new file mode 100644 index 0000000..60573f7 --- /dev/null +++ b/src/reducers/selectedItem.js @@ -0,0 +1,83 @@ +const defaultState = { + name: "", + id: "", + selected: "", + rightSelected: "", + shareSelected: "", + selectedItem: "", + lastSelected: 0, + +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SET_SELECTED": + + return { + ...state, + selected: action.selected + } + + case "SET_SELECTED_ITEM": + + return { + ...state, + ...action.selectedItem + } + + case "EDIT_SELECTED_ITEM": + + return { + ...state, + ...action.item + } + + case "RESET_SELECTED_ITEM": + + return defaultState + + case "EDIT_SHARE_SELECTED": + + return { + ...state, + shareSelected: { + ...state.shareSelected, + ...action.selected + } + } + + case "SET_SHARE_SELECTED": + + return { + ...state, + shareSelected: action.selected + } + + case "SET_RIGHT_SELECTED": + + return { + ...state, + rightSelected: action.selected + } + + case "SET_LAST_SELECTED": + + return { + ...state, + lastSelected: action.lastSelected + } + + case "RESET_SELECTED": + + return { + ...state, + selected: "" + } + + + default: + return state; + } +} \ No newline at end of file diff --git a/src/reducers/settings.js b/src/reducers/settings.js new file mode 100644 index 0000000..6760b0c --- /dev/null +++ b/src/reducers/settings.js @@ -0,0 +1,32 @@ +const defaultState = { + + showSettings: false +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SHOW_SETTINGS": + + return { + ...state, + showSettings: true + } + + + case "HIDE_SETTINGS": + + return { + ...state, + showSettings: false + } + + + default: + + return state; + + } + +} \ No newline at end of file diff --git a/src/reducers/storage.js b/src/reducers/storage.js new file mode 100644 index 0000000..9e1d69d --- /dev/null +++ b/src/reducers/storage.js @@ -0,0 +1,25 @@ +const defaultState = { + free: 0, + total: 0, + available: 0 +} + +export default (state = defaultState, action) => { + + switch (action.type) { + + case "SET_STORAGE": + + return { + ...action.info + } + + case "RESET_STORAGE": + + return defaultState + + default: + + return state; + } +} \ No newline at end of file diff --git a/src/reducers/uploads.js b/src/reducers/uploads.js new file mode 100644 index 0000000..7be37b2 --- /dev/null +++ b/src/reducers/uploads.js @@ -0,0 +1,49 @@ +const defaultState = [] + +export default (state = defaultState, action) => { + + switch(action.type) { + + case "ADD_UPLOAD": + + return [action.upload, ...state] + + case "EDIT_UPLOAD": + + return state.map((upload) => { + + //console.log("edit upload", upload.id, action.id) + + if (upload.id === action.id) { + + //console.log("edit match", action.progress) + upload.progress = action.progress + upload.completed = action.completed + } + + return upload + + }) + + case "CANCEL_UPLOAD": + + return state.map((upload) => { + + if (upload.id === action.id) { + + upload.canceled = true; + } + + return upload + + }) + + case "RESET_UPLOADS": + + return [] + + default: + + return state + } +} \ No newline at end of file diff --git a/src/routers/AppRouter.js b/src/routers/AppRouter.js new file mode 100755 index 0000000..7b218bd --- /dev/null +++ b/src/routers/AppRouter.js @@ -0,0 +1,29 @@ +import React from "react"; +import {Router, Route, Switch} from "react-router-dom"; +import LoginPage from "../components/LoginPage" +import createHistory from "history/createBrowserHistory" +import HomePage from "../components/Homepage" +import PrivateRoute from "./PrivateRoute"; +import PublicRoute from "./PublicRoute"; +import DownloadPage from "../components/DownloadPage"; +import uuid from "uuid" + +export const history = createHistory() + +const AppRouter = () => ( + + + + + + + + + + + + + +) + +export default AppRouter; diff --git a/src/routers/PrivateRoute.js b/src/routers/PrivateRoute.js new file mode 100644 index 0000000..6fc490f --- /dev/null +++ b/src/routers/PrivateRoute.js @@ -0,0 +1,18 @@ +import React from "react"; +import {connect} from "react-redux"; +import {Route, Redirect} from "react-router-dom"; + +export const PrivateRoute = ({isAuthenticated, component: Component, ...rest}) => ( + +
+ ( + isAuthenticated ? () : () + )}/> +
+) + +const mapStateToProps = (state) => ({ + isAuthenticated: !!state.auth.id +}) + +export default connect(mapStateToProps)(PrivateRoute) \ No newline at end of file diff --git a/src/routers/PublicRoute.js b/src/routers/PublicRoute.js new file mode 100644 index 0000000..bac70dc --- /dev/null +++ b/src/routers/PublicRoute.js @@ -0,0 +1,15 @@ +import {connect} from "react-redux"; +import React from "react"; +import {Route, Redirect} from "react-router-dom" + +export const PublicRoute = ({isAuthenticated, component: Component}, ...rest) => ( + ( + isAuthenticated ? () : () + )}/> +) + +const connectStateToProps = (state) => ({ + isAuthenticated: !!state.auth.id +}) + +export default connect(connectStateToProps)(PublicRoute) \ No newline at end of file diff --git a/src/services/FileService/index.js b/src/services/FileService/index.js new file mode 100644 index 0000000..305ea6d --- /dev/null +++ b/src/services/FileService/index.js @@ -0,0 +1,896 @@ +const imageChecker = require("../../utils/imageChecker"); +const crypto= require("crypto"); +const videoChecker = require("../../utils/videoChecker"); +const mongoose = require("../../db/mongoose") +const conn = mongoose.connection; +const createThumbnail = require("./utils/createThumbnail"); +const Thumbnail = require("../../models/thumbnail"); +const ObjectID = require('mongodb').ObjectID +const NotAuthorizedError = require("../../utils/NotAuthorizedError"); +const NotFoundError = require("../../utils/NotFoundError"); +const env = require("../../enviroment/env"); +const jwt = require("jsonwebtoken"); +const removeChunks = require("./utils/removeChunks"); + + +const Folder = require("../../models/folder"); +const sortBySwitch = require("../../utils/sortBySwitch") +const createQuery = require("../../utils/createQuery"); +const ffmpeg = require("fluent-ffmpeg"); +const temp = require("temp").track(); +const progress = require("progress-stream"); +const fs = require("fs") +const DbUtilFile = require("../../db/utils/fileUtils"); +const DbUtilFolder = require("../../db/utils/folderUtils"); + +const dbUtilsFile = new DbUtilFile(); +const dbUtilsFolder = new DbUtilFolder(); + +const FileService = function() { + + this.upload = (user, busboy, req) => { + + return new Promise((resolve, reject) => { + + const password = user.getEncryptionKey(); + + let bucketStream; + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const cipher = crypto.createCipheriv('aes256', CIPHER_KEY, initVect); + + cipher.on("error", async(e) => { + await removeChunks(bucketStream); + reject({ + message: "File service upload cipher error", + exception: e, + code: 500 + }) + }) + + const formData = new Map(); + + busboy.on("error", async(e) => { + await removeChunks(bucketStream); + reject({ + message: "File service upload busboy error", + exception: e, + code: 500 + }) + }) + + busboy.on("file", async(fieldname, file, filename) => { + + const parent = formData.get("parent") || "/" + const parentList = formData.get("parentList") || "/"; + const size = formData.get("size") || "" + let hasThumbnail = false; + let thumbnailID = "" + const isVideo = videoChecker(filename) + + const metadata = { + owner: user._id, + parent, + parentList, + hasThumbnail, + thumbnailID, + isVideo, + size, + IV: initVect + } + + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + + bucketStream = bucket.openUploadStream(filename, {metadata}) + + bucketStream.on("error", async(e) => { + await removeChunks(bucketStream); + reject({ + message: "Cannot upload file to database", + exception: e, + code: 500 + }) + }) + + req.on("aborted", async() => { + + console.log("Upload Request Cancelling..."); + + await removeChunks(bucketStream); + }) + + file.pipe(cipher).pipe(bucketStream); + + bucketStream.on("finish", async(file) => { + + const imageCheck = imageChecker(filename); + + if (file.length < 15728640 && imageCheck) { + + let updatedFile = await createThumbnail(file, filename, user); + + resolve(updatedFile); + + } else { + + resolve(file); + } + + }) + + }).on("field", (field, val) => { + + formData.set(field, val) + + }) + + }) + } + + this.getThumbnail = async(user, id) => { + + const password = user.getEncryptionKey(); + + const thumbnail = await Thumbnail.findById(id); + + if (user._id.toString() !== thumbnail.owner) { + + throw new NotAuthorizedError("Thumbnail Unauthorized Error"); + } + + const iv = thumbnail.data.slice(0, 16); + + const chunk = thumbnail.data.slice(16); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv); + + const decryptedThumbnail = Buffer.concat([decipher.update(chunk), decipher.final()]); + + //console.log("decry", decryptedThumbnail); + + return decryptedThumbnail; + } + + this.getFullThumbnail = async(user, fileID, res) => { + + return new Promise((resolve, reject) => { + + const userID = user._id; + + dbUtilsFile.getFileInfo(fileID, userID).then( async(file) => { + + if (!file) { + reject({ + code: 401, + message: "File For Full Thumbnail Not Found", + exception: undefined + }) + } + + const bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255, + }) + const password = user.getEncryptionKey(); + const IV = file.metadata.IV.buffer + + const readStream = bucket.openDownloadStream(ObjectID(fileID)) + + readStream.on("error", (e) => { + reject({ + code: 500, + message: "File service Full Thumbnail stream error", + exception: e + }) + }) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + decipher.on("error", (e) => { + reject({ + code: 500, + message: "File service Full Thumbnail decipher error", + exception: e + }) + }) + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size); + + readStream.pipe(decipher).pipe(res).on("finish", async() => { + console.log("Sent Full Thumbnail"); + resolve(); + }); + }); + }) + } + + this.publicDownload = (ID, tempToken, res) => { + + return new Promise((resolve, reject) => { + + dbUtilsFile.getPublicFile(ID).then((file) => { + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + reject({ + code: 401, + message: "File not public/Not found", + exception: undefined + }) + } else { + + const bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255, + }) + + const password = env.key; + const IV = file.metadata.IV.buffer + + const readStream = bucket.openDownloadStream(ObjectID(ID)) + + readStream.on("error", (e) => { + reject({ + code: 500, + message: "File service public download decipher error", + exception: e + }) + }) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + decipher.on("error", (e) => { + reject({ + code: 500, + message: "File service public download decipher error", + exception: e + }) + }) + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size); + + readStream.pipe(decipher).pipe(res).on("finish", async() => { + console.log("removing public link"); + await this.removePublicOneTimeLink(file); + resolve(); + }); + + } + + }) + }) + + } + + this.removePublicOneTimeLink = async(currentFile) => { + + const ID = currentFile._id; + + if (currentFile.metadata.linkType === "one") { + + await dbUtilsFile.removeOneTimePublicLink(ID); + } + } + + this.removeLink = async(userID, fileID) => { + + const file = await dbUtilsFile.removeLink(fileID, userID); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Remove Link File Not Found Error") + } + + this.makePublic = async(user, fileID) => { + + const userID = user._id; + const token = await jwt.sign({_id: userID.toString()}, env.password); + + const file = await dbUtilsFile.makePublic(fileID, userID, token, "public"); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Make Public File Not Found Error"); + + return token; + } + + this.getPublicInfo = async(fileID, tempToken) => { + + const file = await dbUtilsFile.getPublicInfo(fileID, tempToken); + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + + throw new NotFoundError("Public Info Not Found"); + + } else { + + return file; + } + } + + this.makeOneTimePublic = async(userID, fileID) => { + + const token = await jwt.sign({_id: userID.toString()}, env.password); + + const file = await dbUtilsFile.makeOneTimePublic(fileID, userID, token); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Make One Time Public Not Found Error"); + + return token; + } + + this.getFileInfo = async(userID, fileID) => { + + let currentFile = await dbUtilsFile.getFileInfo(fileID, userID) + + if (!currentFile) throw new NotFoundError("Get File Info Not Found Error"); + + const parentID = currentFile.metadata.parent + + let parentName = ""; + + if (parentID === "/") { + + parentName = "Home" + + } else { + + const parentFolder = await Folder.findOne({"owner": userID, "_id": parentID}); + + if (parentFolder) { + + parentName = parentFolder.name; + + } else { + + parentName = "Unknown" + } + + } + + return {...currentFile, parentName} + + } + + this.getQuickList = async(userID) => { + + const quickList = await dbUtilsFile.getQuickList(userID); + + if (!quickList) throw new NotFoundError("Quick List Not Found Error"); + + return quickList; + } + + this.getList = async(userID, query) => { + + let searchQuery = query.search || ""; + const parent = query.parent || "/"; + let limit = query.limit || 50; + let sortBy = query.sortby || "DEFAULT" + const startAt = query.startAt || undefined + const startAtDate = query.startAtDate || "0" + const startAtName = query.startAtName || "" + sortBy = sortBySwitch(sortBy) + limit = parseInt(limit) + + const queryObj = createQuery(userID, parent, query.sortby,startAt, startAtDate, searchQuery, startAtName) + + const fileList = await dbUtilsFile.getList(queryObj, sortBy, limit); + + if (!fileList) throw new NotFoundError("File List Not Found"); + + return fileList; + + } + + this.getDownloadToken = async(user) => { + + const tempToken = await user.generateTempAuthToken(); + + if (!tempToken) throw new NotAuthorizedError("Get Download Token Not Authorized Error"); + + return tempToken; + } + + this.getDownloadTokenVideo = async(user, cookie) => { + + if (!cookie) throw new NotAuthorizedError("Get Download Token Video Cookie Not Authorized Error"); + + const tempToken = await user.generateTempAuthTokenVideo(cookie); + + if (!tempToken) throw new NotAuthorizedError("Get Download Token Video Not Authorized Error"); + + return tempToken; + } + + this.removeTempToken = async(user, tempToken) => { + + const key = user.getEncryptionKey(); + + const decoded = await jwt.verify(tempToken, env.password); + + const publicKey = decoded.iv; + + const encryptedToken = user.encryptToken(tempToken, key, publicKey); + + const removedTokenUser = await dbUtilsFile.removeTempToken(user, encryptedToken); + + if (!removedTokenUser) throw new NotFoundError("Remove Temp Token User Not Found Errors"); + + await removedTokenUser.save(); + } + + this.transcodeVideo = (user, body) => { + + return new Promise((resolve, reject) => { + + const id = body.file._id; + const userID = user._id; + + conn.db.collection("fs.files") + .findOne({"_id": ObjectID(id), "metadata.owner": userID}).then((currentFile) => { + + if (!currentFile) { + reject({ + message: "Transcode Video Not Found Error", + exception: undefined, + code: 401 + }) + } else { + + const password = user.getEncryptionKey(); //env.key; + + const IV = currentFile.metadata.IV.buffer + + const bucket = new mongoose.mongo.GridFSBucket(conn.db); + + const readStream = bucket.openDownloadStream(ObjectID(id)); + + readStream.on("error", (e) => { + reject({ + message: "File service transcode video stream error", + exception: e, + code: 500 + }) + }) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + decipher.on("error", (e) => { + reject({ + message: "File service transcode video decipher error", + exception: e, + code: 500 + }) + }) + + const tempStream = temp.createWriteStream() + + tempStream.on("error", (e) => { + reject({ + message: "File service transcode video temp error", + exception: e, + code: 500 + }) + }) + + + readStream.pipe(decipher).pipe(tempStream).on("finish", () => { + + const writeBucket = new mongoose.mongo.GridFSBucket(conn.db, { + bucketName: "videos", + }); + + const gfsWrite = writeBucket.openUploadStream(currentFile.filename, { + _id: id, + }) + + gfsWrite.on("error", (e) => { + reject({ + message: "File service transcode video write stream error", + exception: e, + code: 500 + }) + }) + + const initVect = crypto.randomBytes(16); + + const cipher = crypto.createCipheriv('aes256', CIPHER_KEY, initVect); + + cipher.on("error", (e) => { + reject({ + message: "File service transcode video cipher write error", + exception: e, + code: 500 + }) + }) + + const str = progress({time: 100}) + + str.on("progress", () => { + + }); + + str.on("finish", () => { + + }) + + const tempStream2 = temp.createWriteStream() + + tempStream2.on("error", (e) => { + reject({ + message: "File service transcode video temp write error", + exception: e, + code: 500 + }) + }) + + const ffmpegCommand = ffmpeg() + .input(tempStream.path).outputOptions(['-movflags frag_keyframe+empty_moov', '-movflags +faststart']) + .outputFormat('mp4').on("error", (err, stdout, stderr) => { + console.log('An error occurred: ' + err.message, err, stderr); + reject({ + message: "Video Transcode Failed Error", + exception: err.message, + code: 500 + }) + }) + .videoCodec('libx264') + .audioBitrate('128k') + .videoBitrate(8000, true) + .pipe(tempStream2).on("finish", () => { + + fs.createReadStream(tempStream2.path).pipe(cipher).pipe(gfsWrite).on("finish", async(file) => { + + console.log("transcode finished"); + + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(id)}, {"$set": {"metadata.transcoded": true, "metadata.transcodedIV": initVect, "metadata.transcoded_size": tempStream2.bytesWritten, "metadata.transcodedID": file._id}}) + + resolve(); + + }).on("error", (e) => { + reject({ + message: "File service transcode read stream error", + exception: e, + code: 500 + }) + }) + }) + + ffmpegCommand.on("error", (e) => { + reject({ + message: "File service FFMPEG error", + exception: e, + code: 500 + }) + }) + + }) + + } + + }) + }) + + } + + this.removeTranscodeVideo = async(userID, fileID) => { + + const parentFile = await dbUtilsFile.getFileInfo(fileID, userID); + const transcodedVideoID = parentFile.metadata.transcodedID; + + const bucket = new mongoose.mongo.GridFSBucket(conn.db, { + bucketName: "videos" + }); + + try { + await bucket.delete(ObjectID(transcodedVideoID)); + } catch (e) { + console.log("Could Not Find Transcoded Video"); + } + + const file = await dbUtilsFile.removeTranscodeVideo(fileID, userID); + + if (!file) throw new NotFoundError("Remove Transcode Video Error"); + } + + this.streamTranscodedVideo = (userID, fileID, headers, res) => { + + return new Promise((resolve, reject) => { + + dbUtilsFile.getFileInfo(fileID, userID).then((file) => { + + if (!file) { + reject({ + code: 401, + message: "Stream Transcoded File Not Found", + exception: undefined + }) + } else { + + const fileSize = file.metadata.transcoded_size; + const transcodedVideoID = file.metadata.transcodedID; + + const password = user.getEncryptionKey(); //env.key; + + const range = headers.range + const parts = range.replace(/bytes=/, "").split("-") + const start = parseInt(parts[0], 10) + const end = parts[1] + ? parseInt(parts[1], 10) + : fileSize-1 + const chunksize = (end-start)+1 + const IV = file.metadata.transcodedIV.buffer + + let head = { + 'Content-Range': 'bytes ' + start + '-' + end + '/' + fileSize, + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize, + 'Content-Type': 'video/mp4' + } + + const bucket = new mongoose.mongo.GridFSBucket(conn.db, { + bucketName: "videos", + chunkSizeBytes: chunksize + }); + const readStream = bucket.openDownloadStream(ObjectID(transcodedVideoID), { + start: start, + end: end + }); + + readStream.on("error", (e) => { + reject({ + code: 500, + message: "File service transcode video stream error", + exception: e + }) + }) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + decipher.setAutoPadding(false); + + decipher.on("error", (e) => { + reject({ + code: 500, + message: "File service transcode video decipher error", + exception: e + }) + }); + + res.writeHead(206, head); + + readStream.pipe(decipher).pipe(res).on("finish", () => { + + resolve(); + }); + } + + }) + + }) + } + + this.streamVideo = (user, fileID, headers, res) => { + + return new Promise((resolve, reject) => { + + const userID = user._id; + + dbUtilsFile.getFileInfo(fileID, userID).then((currentFile) => { + + if (!currentFile) { + reject({ + code: 401, + message: "Video Steam Not Found Error", + exception: undefined + }) + } else { + + const password = user.getEncryptionKey(); + const fileSize = currentFile.metadata.size; + + const range = headers.range + const parts = range.replace(/bytes=/, "").split("-") + let start = parseInt(parts[0], 10) + let end = parts[1] + ? parseInt(parts[1], 10) + : fileSize-1 + const chunksize = (end-start)+1 + const IV = currentFile.metadata.IV.buffer + + let head = { + 'Content-Range': 'bytes ' + start + '-' + end + '/' + fileSize, + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize, + 'Content-Type': 'video/mp4'} + + const bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 + }); + const readStream = bucket.openDownloadStream(ObjectID(fileID), { + start: start, + end: end.length + }); + + readStream.on("error", (e) => { + reject({ + code: 500, + message: "File service stream video stream error", + exception: e + }) + }) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + decipher.on("error", (e) => { + reject({ + code: 500, + message: "File service stream video decipher error", + exception: e + }) + }) + + res.writeHead(206, head); + + readStream.pipe(decipher).pipe(res).on("finish", () => { + resolve(); + }); + } + }) + }) + } + + this.downloadFile = (user, fileID, res) => { + + return new Promise((resolve, reject) => { + + const userID = user._id; + + dbUtilsFile.getFileInfo(fileID, userID).then((file) => { + + if (!file) { + reject({ + code: 401, + message: "Download File Not Found Error", + exception: undefined + }) + } else { + + const password = user.getEncryptionKey(); + + const bucket = new mongoose.mongo.GridFSBucket(conn.db); + + const IV = file.metadata.IV.buffer + + const readStream = bucket.openDownloadStream(ObjectID(fileID)) + + readStream.on("error", (e) => { + reject({ + code: 500, + message: "File service download decipher error", + exception: e + }) + }) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + decipher.on("error", (e) => { + reject({ + code: 500, + message: "File service download decipher error", + exception: e + }) + }) + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size); + + readStream.pipe(decipher).pipe(res).on("finish", () => { + resolve(); + }); + + } + + }) + + }) + } + + this.getSuggestedList = async(userID, searchQuery) => { + + searchQuery = new RegExp(searchQuery, 'i') + + const fileList = await dbUtilsFile.getFileSearchList(userID, searchQuery); + const folderList = await dbUtilsFolder.getFolderSearchList(userID, searchQuery); + + if (!fileList || !folderList) throw new NotFoundError("Suggested List Not Found Error"); + + return { + fileList, + folderList + } + } + + this.renameFile = async(userID, fileID, title) => { + + const file = await dbUtilsFile.renameFile(fileID, userID, title); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Rename File Not Found Error"); + + return file; + } + + this.moveFile = async(userID, fileID, parentID) => { + + let parentList = ["/"]; + + if (parentID.length !== 1) { + + const parentFile = await dbUtilsFolder.getFolderInfo(parentID, userID); + if (!parentFile) throw new NotFoundError("Rename Parent File Not Found Error") + const parentList = parentFile.parentList; + parentList.push(parentID); + } + + const file = await dbUtilsFile.moveFile(fileID, userID, parentID, parentList.toString()); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Rename File Not Found Error"); + + return file; + } + + this.deleteFile = async(userID, fileID) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255, + }); + + const videoBucket = new mongoose.mongo.GridFSBucket(conn.db, { + bucketName: "videos" + }); + + const file = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("Delete File Not Found Error"); + + if (file.metadata.thumbnailID) { + + await Thumbnail.deleteOne({_id: file.metadata.thumbnailID}); + } + + if (file.metadata.isVideo && file.metadata.transcoded) { + try { + await bucket.delete(ObjectID(file.metadata.transcodedID)); + } catch (e) { + console.log("Could Not Find Transcoded Video"); + } + } + + await bucket.delete(ObjectID(fileID)); + + } +} + +module.exports = FileService; + diff --git a/src/services/FileService/utils/createThumbnail.js b/src/services/FileService/utils/createThumbnail.js new file mode 100644 index 0000000..8a43a3f --- /dev/null +++ b/src/services/FileService/utils/createThumbnail.js @@ -0,0 +1,84 @@ +const mongoose = require("../../../db/mongoose") +const conn = mongoose.connection; +const crypto= require("crypto"); +const env = require("../../../enviroment/env"); +const Thumbnail = require("../../../models/thumbnail"); +const ObjectID = require('mongodb').ObjectID +const sharp = require("sharp"); +const concat = require("concat-stream") + +const createThumbnail = async(file, filename, user) => { + + return new Promise((resolve) => { + + const password = user.getEncryptionKey(); + + let CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }) + + const readStream = bucket.openDownloadStream(ObjectID(file._id)) + + readStream.on("error", (e) => { + console.log("File service upload thumbnail error", e); + resolve(file); + }) + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, file.metadata.IV); + + decipher.on("error", (e) => { + console.log("File service upload thumbnail decipher error", e); + resolve(file) + }) + + try { + + const concatStream = concat(async(bufferData) => { + + const thumbnailIV = crypto.randomBytes(16); + + //CIPHER_KEY = crypto.createHash('sha256').update(process.env.newPassword).digest() + + const thumbnailCipher = crypto.createCipheriv("aes256", CIPHER_KEY, thumbnailIV); + + bufferData = Buffer.concat([thumbnailIV, thumbnailCipher.update(bufferData), thumbnailCipher.final()]); //thumbnailCipher.update(bufferData) + + const thumbnailModel = new Thumbnail({name: filename, owner: user._id, data: bufferData}); + + await thumbnailModel.save(); + + let updatedFile = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": file._id}, {"$set": {"metadata.hasThumbnail": true, "metadata.thumbnailID": thumbnailModel._id}}) + + updatedFile = updatedFile.value; + + updatedFile = {...updatedFile, metadata: {...updatedFile.metadata, hasThumbnail: true, thumbnailID: thumbnailModel._id}} + + resolve(updatedFile); + + }).on("error", (e) => { + console.log("File service upload concat stream error", e); + resolve(file); + }) + + const imageResize = sharp().resize(300).on("error", (e) => { + + console.log("resize error", e); + resolve(file); + }) + + readStream.pipe(decipher).pipe(imageResize).pipe(concatStream); + + + } catch (e) { + + console.log(e); + resolve(file); + } + + }) +} + +module.exports = createThumbnail; \ No newline at end of file diff --git a/src/services/FileService/utils/removeChunks.js b/src/services/FileService/utils/removeChunks.js new file mode 100644 index 0000000..52bc691 --- /dev/null +++ b/src/services/FileService/utils/removeChunks.js @@ -0,0 +1,26 @@ +const DbUtilsFile = require("../../../db/utils/fileUtils"); +const dbUtilsFile = new DbUtilsFile(); + +const removeChunks = async(bucketStream) => { + + try { + + const uploadID = bucketStream.id; + + if (!uploadID || uploadID.length === 0) { + + console.log("Invalid uploadID for remove chunks"); + return; + } + + await dbUtilsFile.removeChunksByID(uploadID); + console.log("Upload Request Cancelled, Chunks Removed"); + + } catch(e) { + + console.log("Could not remove chunks for canceled upload", uploadID, e); + + } +} + +module.exports = removeChunks; \ No newline at end of file diff --git a/src/services/FolderService/index.js b/src/services/FolderService/index.js new file mode 100644 index 0000000..8353d14 --- /dev/null +++ b/src/services/FolderService/index.js @@ -0,0 +1,287 @@ +const Folder = require("../../models/folder"); +const InternalServerError = require("../../utils/InternalServerError"); +const NotFoundError = require("../../utils/NotFoundError"); +const UtilsFile = require("../../db/utils/fileUtils"); +const ObjectID = require('mongodb').ObjectID +const UtilsFolder = require("../../db/utils/folderUtils"); +const mongoose = require("../../db/mongoose") +const conn = mongoose.connection; +const Thumbnail = require("../../models/thumbnail"); +const sortBySwitch = require("../../utils/sortBySwitchFolder") + +const utilsFile = new UtilsFile(); +const utilsFolder = new UtilsFolder(); + +const FolderService = function() { + + this.uploadFolder = async(data) => { + + const folder = new Folder(data); + + await folder.save(); + + if (!folder) throw new InternalServerError("Upload Folder Error"); + + return folder; + } + + this.deleteFolder = async(userID, folderID, parentList) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + const videoBucket = new mongoose.mongo.GridFSBucket(conn.db, { + bucketName: "videos" + }); + + const parentListString = parentList.toString() + + await Folder.deleteMany({"owner": userID, "parentList": { $all: parentList}}) + await Folder.deleteMany({"owner": userID, "_id": folderID}); + + const fileList = await utilsFile.getFileListByParent(userID, parentListString); + + if (!fileList) throw new NotFoundError("Delete File List Not Found"); + + for (let i = 0; i < fileList.length; i++) { + + const currentFile = fileList[i]; + + try { + + if (currentFile.metadata.thumbnailID) { + + await Thumbnail.deleteOne({_id: currentFile.metadata.thumbnailID}); + } + + if (currentFile.metadata.isVideo && currentFile.metadata.transcoded) { + + try { + await videoBucket.delete(ObjectID(currentFile.metadata.transcodedID)) + } catch (e) {console.log("Could Not Find Transcoded Video")} + + } + + await bucket.delete(ObjectID(currentFile._id)); + + } catch (e) { + + console.log("Could not delete file", currentFile.filename, currentFile._id); + } + + + } + } + + this.deleteAll = async(userID) => { + + console.log("remove all request") + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + const videoBucket = new mongoose.mongo.GridFSBucket(conn.db, { + bucketName: "videos" + }); + + await Folder.deleteMany({"owner": userID}); + + const fileList = await utilsFile.getFileListByOwner(userID); + + if (!fileList) throw new NotFoundError("Delete All File List Not Found Error"); + + for (let i = 0; i < fileList.length; i++) { + const currentFile = fileList[i]; + + try { + + if (currentFile.metadata.thumbnailID) { + + await Thumbnail.deleteOne({_id: currentFile.metadata.thumbnailID}) + } + + if (currentFile.metadata.isVideo && currentFile.metadata.transcoded) { + + try { + await videoBucket.delete(ObjectID(currentFile.metadata.transcodedID)) + } catch (e) { + console.log("Cannot Find Transcoded Video"); + } + + } + + await bucket.delete(ObjectID(currentFile._id)); + + } catch (e) { + + console.log("Could Not Remove File", currentFile.filename, currentFile._id); + } + } + + } + + this.getFolderInfo = async(userID, folderID) => { + + let currentFolder = await utilsFolder.getFolderInfo(folderID, userID); + + if (!currentFolder) throw new NotFoundError("Folder Info Not Found Error"); + + const parentID = currentFolder.parent; + + let parentName = ""; + + if (parentID === "/") { + + parentName = "Home" + + } else { + + const parentFolder = await utilsFolder.getFolderInfo(parentID, userID); + + if (parentFolder) { + + parentName = parentFolder.name; + + } else { + + parentName = "Unknown" + } + + } + + const folderName = currentFolder.name + + currentFolder = {...currentFolder._doc, parentName, folderName} + // Must Use ._doc here, or the destucturing/spreading + // Will add a bunch of unneeded variables to the object. + + return currentFolder; + } + + this.getFolderSublist = async(userID, folderID) => { + + const folder = await utilsFolder.getFolderInfo(folderID, userID); + + if (!folder) throw new NotFoundError("Folder Sublist Not Found Error"); + + const subfolderList = folder.parentList; + + let folderIDList = []; + let folderNameList = []; + + for (let i = 0; i < subfolderList.length; i++) { + + const currentSubFolderID = subfolderList[i]; + + if (currentSubFolderID === "/") { + + folderIDList.push("/"); + folderNameList.push("Home") + + } else { + + const currentFolder = await utilsFolder.getFolderInfo(currentSubFolderID, userID); + + folderIDList.push(currentFolder._id); + folderNameList.push(currentFolder.name) + } + } + + folderIDList.push(folderID); + folderNameList.push(folder.name) + + return { + folderIDList, + folderNameList + } + + } + + this.getFolderList = async(userID, query) => { + + let searchQuery = query.search || ""; + const parent = query.parent || "/"; + let sortBy = query.sortby || "DEFAULT" + sortBy = sortBySwitch(sortBy) + + if (searchQuery.length === 0) { + + const folderList = await utilsFolder.getFolderListByParent(userID, parent, sortBy); + + if (!folderList) throw new NotFoundError("Folder List Not Found Error"); + + return folderList; + + } else { + + searchQuery = new RegExp(searchQuery, 'i') + const folderList = await utilsFolder.getFolderListBySearch(userID, searchQuery, sortBy); + + if (!folderList) throw new NotFoundError("Folder List Not Found Error"); + + return folderList; + } + } + + this.renameFolder = async(userID, folderID, title) => { + + const folder = await utilsFolder.renameFolder(folderID, userID, title); + + if (!folder) throw new NotFoundError("Rename Folder Not Found"); + } + + this.moveFolder = async(userID, folderID, parentID) => { + + let parentList = ["/"]; + + if (parentID.length !== 1) { + + const parentFile = await utilsFolder.getFolderInfo(parentID, userID); + parentList = parentFile.parentList; + parentList.push(parentID); + } + + const folder = await utilsFolder.moveFolder(folderID, userID, parentID, parentList); + + if (!folder) throw new NotFoundError("Move Folder Not Found") + + const folderChilden = await utilsFolder.findAllFoldersByParent(folderID.toString(), userID); + + folderChilden.map( async(currentFolderChild) => { + + let currentFolderChildParentList = currentFolderChild.parentList; + + const indexOfFolderID = currentFolderChildParentList.indexOf(folderID.toString()); + + currentFolderChildParentList = currentFolderChildParentList.splice(indexOfFolderID); + + currentFolderChildParentList = [...parentList, ...currentFolderChildParentList]; + + currentFolderChild.parentList = currentFolderChildParentList; + + await currentFolderChild.save() + }) + + const fileChildren = await utilsFile.getFileListByParent(userID, folderID.toString()); + + fileChildren.map( async(currentFileChild) => { + + let currentFileChildParentList = currentFileChild.metadata.parentList; + + currentFileChildParentList = currentFileChildParentList.split(","); + + const indexOfFolderID = currentFileChildParentList.indexOf(folderID.toString()); + + currentFileChildParentList = currentFileChildParentList.splice(indexOfFolderID); + + currentFileChildParentList = [...parentList, ...currentFileChildParentList]; + + await utilsFile.moveFile(currentFileChild._id, userID, currentFileChild.metadata.parent, currentFileChildParentList.toString()) + + }) + } +} + +module.exports = FolderService; \ No newline at end of file diff --git a/src/services/UserService/index.js b/src/services/UserService/index.js new file mode 100644 index 0000000..b18ed44 --- /dev/null +++ b/src/services/UserService/index.js @@ -0,0 +1,79 @@ +const User = require("../../models/user"); +const bcrypt = require("bcrypt"); +const crypto = require("crypto"); +const NotFoundError = require("../../utils/NotFoundError"); +const InternalServerError = require("../../utils/InternalServerError"); +const NotAuthorizedError = require("../../utils/NotAuthorizedError"); + +const UserService = function() { + + this.login = async(userData) => { + + const email = userData.email; + const password = userData.password; + + const user = await User.findByCreds(email, password); + + const token = await user.generateAuthToken(); + + if (!user || !token) throw new NotFoundError("Login User Not Found Error"); + + return {user, token} + } + + this.logout = async(user, userToken) => { + + user.tokens = user.tokens.filter((token) => { + return token.token !== userToken; + }) + + await user.save(); + } + + this.logoutAll = async(user) => { + + user.tokens = [] + user.tempTokens = []; + + await user.save(); + } + + this.create = async(userData) => { + + console.log("Create"); + + const user = new User(userData); + await user.save(); + + await user.generateEncryptionKeys(); + + const token = await user.generateAuthToken(); + + if (!user || !token) throw new InternalServerError("Could Not Create New User Error"); + + return {user, token} + } + + this.changePassword = async(user, oldPassword, newPassword) => { + + const isMatch = await bcrypt.compare(oldPassword, user.password); + + if (!isMatch) throw new NotAuthorizedError("Change Passwords Do Not Match Error"); + + const encryptionKey = user.getEncryptionKey(); + + user.password = newPassword; + + user.tokens = []; + user.tempTokens = []; + + await user.save(); + await user.changeEncryptionKey(encryptionKey); + + const newToken = await user.generateAuthToken(); + + return newToken; + } +} + +module.exports = UserService; \ No newline at end of file diff --git a/src/store/configureStore.js b/src/store/configureStore.js new file mode 100755 index 0000000..d72dd41 --- /dev/null +++ b/src/store/configureStore.js @@ -0,0 +1,49 @@ +import {createStore, combineReducers, applyMiddleware} from "redux" +import thunk from "redux-thunk"; +import authReducer from "../reducers/auth"; +import mainReducer from "../reducers/main"; +import fileReducer from "../reducers/files" +import folderReducer from "../reducers/folders" +import filterReducer from "../reducers/filter"; +import selectedItemReducer from "../reducers/selectedItem"; +import uploadsReducer from "../reducers/uploads"; +import storageReducer from "../reducers/storage"; +import quickFilesReducer from "../reducers/quickFiles"; +import popupFilesReducer from "../reducers/popupFile"; +import settingsReducer from "../reducers/settings"; +import parentReducer from "../reducers/parent"; +import addOptionsReducer from "../reducers/addOptions"; +import photoViewerReducer from "../reducers/photoViewer"; +import routesReducer from "../reducers/routes"; +import moverReducer from "../reducers/mover"; + +//const composeEnchancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + +export default () => { + + const store = createStore( + combineReducers({ + auth: authReducer, + main:mainReducer, + files: fileReducer, + folders: folderReducer, + filter: filterReducer, + selectedItem: selectedItemReducer, + uploads: uploadsReducer, + storage: storageReducer, + quickFiles: quickFilesReducer, + popupFile: popupFilesReducer, + settings: settingsReducer, + parent: parentReducer, + addOptions: addOptionsReducer, + photoViewer: photoViewerReducer, + routes: routesReducer, + mover: moverReducer + }), + + applyMiddleware(thunk) + //composeEnchancers(applyMiddleware(thunk)) + ) + + return store; +} \ No newline at end of file diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss new file mode 100644 index 0000000..3707c06 --- /dev/null +++ b/src/styles/base/_base.scss @@ -0,0 +1,23 @@ +* { + box-sizing: border-box; +} + +html { + font-size: 62.5% +} + +body { + color: $dark-grey; + font-family: Helvetica, Arial, sans-serif; + font-size: $m-size; + background-color: white; + line-height: 1.6; +} + +button { + cursor: pointer; +} + +button:disabled { + cursor: default; +} diff --git a/src/styles/base/_settings.scss b/src/styles/base/_settings.scss new file mode 100644 index 0000000..8ed2469 --- /dev/null +++ b/src/styles/base/_settings.scss @@ -0,0 +1,17 @@ +// Sizes + +$s-size: 1.2rem; +$m-size: 1.6rem; +$l-size: 3.2rem; +$xl-size: 4.8rem; +$dark-grey: #333333; +$blue: #1c88bf; +$dark-blue: #364051; +$off-white: #f7f7f7; +$grey: #666; +$desktop-breakpoint: 45rem; +$teal: #00bfa5; +$small-border: 2px; + +// font sizees +$font-size-large: 1.8rem; diff --git a/src/styles/components/_ContentMenu.scss b/src/styles/components/_ContentMenu.scss new file mode 100644 index 0000000..d2ce286 --- /dev/null +++ b/src/styles/components/_ContentMenu.scss @@ -0,0 +1,70 @@ +.context-menu { + background: white; + position: absolute; + width: 230px; + + z-index: 1; + box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302), 0 1px 3px 1px rgba(60,64,67,0.149); + border: 1px solid #dadce0; + border-radius: 10px; + overflow: hidden; + height: fit-content; + + @media (max-width: $desktop-breakpoint) { + + position: fixed; + width: 99%; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } +} + +.context__item { + display: flex; + height: 41px; + + @media (max-width: $desktop-breakpoint) { + + height: 55px; + } +} + +.context__item:hover { + background: lightgrey; + + + @media (max-width: $desktop-breakpoint) { + + background: white; + } + +} + +.context__item:active { + + background: lightgrey; +} + +.context__item__image { + width: 30px; + /* height: 43px; */ + opacity: 0.7; + margin-left: 7px; + + @media (max-width: $desktop-breakpoint) { + + width: 40px; + } +} + +.context__item__title { + color: black; + margin-left: 7px; + font-weight: 300; + font-size: 13px; + + @media (max-width: $desktop-breakpoint) { + + font-size: 15px; + } +} \ No newline at end of file diff --git a/src/styles/components/_Dataform.scss b/src/styles/components/_Dataform.scss new file mode 100644 index 0000000..dd3254c --- /dev/null +++ b/src/styles/components/_Dataform.scss @@ -0,0 +1,16 @@ +.dataform { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: start; +} + +.dataform__item { + flex-basis: 100px; + flex-grow: 1; +} + +.dataform__no-files { + text-align: center; + font-weight: 300; +} \ No newline at end of file diff --git a/src/styles/components/_DownloadPage.scss b/src/styles/components/_DownloadPage.scss new file mode 100644 index 0000000..ab90849 --- /dev/null +++ b/src/styles/components/_DownloadPage.scss @@ -0,0 +1,49 @@ +.downloadpage { + width: 100vw; + height: 100vh; +} + +.downloadpage__box { + + display: flex; + flex-direction: column; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + justify-content: center; + width: 345px; + align-items: center; + height: 289px; + padding: 3.2rem 1.6rem 1.2rem 1.6rem; + border: 0.5px; + border-style: groove; + /* position: relative; */ + border-radius: 7px; +} + +.downloadpage__box__button { + color: red; +} + +.downloadpage__box__title { + color: black; + font-size: 19px; + margin-top: -18px; + font-weight: 300; + max-width: 94%; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} + +.downloadpage__box__subtitle { + color: black; + font-weight: 200; + font-size: 12px; + max-width: 94%; + overflow: hidden; + text-overflow: ellipsis; +} \ No newline at end of file diff --git a/src/styles/components/_File.scss b/src/styles/components/_File.scss new file mode 100644 index 0000000..b71aa27 --- /dev/null +++ b/src/styles/components/_File.scss @@ -0,0 +1,234 @@ +.file__item { + flex-grow: 1; + display: flex; + flex-direction: column; + border: 1px solid #dadce0; + align-items: center; + justify-content: initial; + border-radius: 10px; + flex-basis: 23%; + max-width: 23%; + margin: 5px; + cursor: pointer; + height: 171px; + + // overflow: hidden; + position: relative; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + + @media (max-width: $desktop-breakpoint) { + flex-basis: 28%; + max-width: 30.2%; + position: unset + } +} + +.file__item--selected { + background:#e8f0fe; +} + +.file__title { + color: #373737; + margin-top: 25px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + width: 90%; + max-width: 23vw; + text-align: center; + font-weight: 300; + // text-transform: capitalize; +} + +.file__title--gone { + + display: none; +} + +.file__title--selected { + color: #1967d2; +} + +.file__image__wrapper { + + width: 100px; + height: 100px; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; +} + +.file__image { + width: 100px; + opacity: 0.7; +} + +.file__item__wrapper { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: initial; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + width: 100%; + height: 100%; +} + +.file__item__listview { + + flex-grow: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + border-radius: 10px; + flex-basis: 98%; + min-width: 98%; + max-width: 98%; + max-width: 99%; + width: 100%; + margin: 5px; + cursor: pointer; + height: 50px; + position: relative; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + + @media (max-width: $desktop-breakpoint) { + position: unset + } + +} + +.file__item__listview--smaller-max-width { + max-width: 98%; +} + +.file__image__listview { + width: 31px; + opacity: 0.7; + min-width: 31px; +} + +.file__image__listview--mover { + + @media (max-width: $desktop-breakpoint) { + width: 38px; + min-width: 38px; + } +} + +.file__image__listview--no-opacity { + width: 31px; + opacity: 1; + /* min-width: 30px; */ + /* max-width: 30px; */ + height: 33px; + /* max-height: 30px; */ + border-radius: 5px; +} + +.file__image--no-opacity { + width: 87%; + max-height: 100%; + object-fit: cover; + opacity: 1; + max-width: 100%; + margin-top: 8px; + padding: -1px; + border-radius: 11px; + margin-left: 6.5%; +} + +.file__title__listview { + color: #373737; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 18%; + width: 18%; + min-width: 18%; + text-align: center; + font-weight: 300; + margin-right: 15px; + text-align: left; +} + +.file__title__listview--no-margin { + margin-right: none +} + +.file__title__listview--no-max { + max-width: none; + width: unset; + padding-left: 7px; +} + +.file__title__listview--mover { + + @media (max-width: $desktop-breakpoint) { + font-size: 18px; + } +} + +.file__item__listview__title__wrapper { + display: flex; + flex-direction: row; + margin-left: 15px; + max-width: 30%; + min-width: 30%; + width: 30%; + align-items: center; +} + +.file__item__listview__title__wrapper--folder { + display: flex; + flex-direction: row; + margin-left: 15px; + max-width: 50%; + min-width: 30%; + width: 50%; + align-items: center; +} + +.file__item__listview__title__wrapper--mover { + + max-width: 63%; + width: 63%; +} + +.file__item__tag { + + width: 49px; + height: 25px; + background: lightblue; + position: absolute; + top: 60px; + left: 9px; + border-radius: 5px; +} + +.file__item__tag__title { + font-size: 10px; +} diff --git a/src/styles/components/_Filter.scss b/src/styles/components/_Filter.scss new file mode 100644 index 0000000..0939195 --- /dev/null +++ b/src/styles/components/_Filter.scss @@ -0,0 +1,55 @@ +.filter { + display: flex; + justify-content: space-between; + align-items: center; + height: 52px; +} + +.filter__folder-wrapper { + background: white; +} + +.filter__folder-title { + color: #5f6368; + margin-left: 22px; + font-weight: 500; + font-size: 13px; +} +.filter__folder-title-wrapper { + margin-left: 15px; +} + +.filters__wrapper { + margin-right: 6%; + display: flex; + flex-direction: row; +} + +.filter__select-wrapper { + margin-right: 7px; + display: flex; + flex-direction: row; +} + +.filter__select-image { + margin-top: 5px; + width: 18px; + opacity: 0.7; +} + +.filter__select { + background: none; + border: none; + -moz-appearance: none; + -webkit-appearance: none; + color: #5f6368; + font-size: 13px; + outline: none; + font-weight: 500; + padding-left: 3px; + padding-right: 4px; +} + +.filter__select:hover { + background: lightgrey +} \ No newline at end of file diff --git a/src/styles/components/_Folder.scss b/src/styles/components/_Folder.scss new file mode 100644 index 0000000..dc58011 --- /dev/null +++ b/src/styles/components/_Folder.scss @@ -0,0 +1,91 @@ +.folder__image { + width: 30px; + height: 30px; + margin-left: 10px; + opacity: 0.7; + + @media (max-width: $desktop-breakpoint) { + // flex-basis: 29%; + max-width: 31%; + } + +} + +.folder__title { + color: #373737; + margin-right: 2px; + margin-top: 25px; + margin-left: 4px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + width: 77%; + max-width: 50vw; + font-weight: 300; + text-transform: capitalize; +} + +.folder__title--selected { + color: #1967d2; +} + +.folder__item { + + flex-grow: 1; + display: flex; + flex-direction: row; + /* border: 1px solid #dadce0; */ + align-items: center; + justify-content: initial; + /* border-radius: 10px; */ + /* flex-basis: 23%; */ + /* max-width: 23%; */ + height: 100%; + width: 100%; + /* margin: 5px; */ + /* height: 50px; */ + cursor: pointer; + /* position: relative; */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + +} + +.folder__item__wrapper { + + flex-grow: 1; + display: flex; + flex-direction: row; + border: 1px solid #dadce0; + align-items: center; + justify-content: initial; + border-radius: 10px; + flex-basis: 23%; + max-width: 23%; + margin: 5px; + height: 50px; + cursor: pointer; + position: relative; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + + @media (max-width: $desktop-breakpoint) { + flex-basis: 46%; + max-width: 47%; + position: unset; + } +} + +.folder__item--selected { + background:#e8f0fe; +} \ No newline at end of file diff --git a/src/styles/components/_Header.scss b/src/styles/components/_Header.scss new file mode 100644 index 0000000..7576da9 --- /dev/null +++ b/src/styles/components/_Header.scss @@ -0,0 +1,206 @@ +.header { + text-align: center; + // height: 11vh; + display: flex; + flex-direction: row; + border-bottom: 1px solid #dadce0; + justify-content: space-between; + /* display: none; */ + position: absolute; + /* top: 0; */ + width: 100%; +} + +.header__title { + color: white; + text-decoration: none; + h1 { + margin: 0; + } +} + +.header__image { + width: 100px; + height: 30px; + margin: 20px 0 15px 20px; + cursor: pointer; + user-select: none; + + @media (max-width: $desktop-breakpoint) { + width: 73px; + height: 24px; + margin: 25px 0 15px 14px; + display: none; + } + +} + +.header__form { + width: 55%; + margin-left: -179px; + position: relative; + + @media (max-width: $desktop-breakpoint) { + width: 67%; + margin-top: 5px; + margin-left: 8px; + position: unset; + } +} + + +.header__content { + display: flex; + justify-content: space-between; + align-items: center; + padding: $s-size 0; +} + +.header__setting { + width: 30px; + height: 30px; + /* position: fixed; */ + /* right: 15px; */ + /* top: 0px; */ + margin: 20px 20px 20px 0; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + margin: 25px 20px 20px 10px; + } + + +} + +.header__input { + border: 1px solid #f1f3f4; + height: 50px; + font-size: 1.8rem; + font-weight: 300; + padding: 1.2rem; + margin: 10px 0 10px 0; + /* margin-left: 130px; */ + width: 100%; + border-radius: 3px; + background: #f1f3f4; + + @media (max-width: $desktop-breakpoint) { + + width: 121%; + } +} +.header__input:focus { + + background: white; + outline: none; + border-style: groove; +} + +.header__input__suggested { + width: 100%; + max-height: 43vh; + overflow: scroll; + background: white; + /* color: black; */ + margin-top: -10px; + position: absolute; + z-index: 3; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); + border-radius: 0 0 6px 6px; + border: groove 1px #f1f3f4; + + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0px; + background: transparent; /* Chrome/Safari/Webkit */ + } + + @media (max-width: $desktop-breakpoint) { + + width: 100vw; + // margin-left: -23vw; + left: 0; + max-height: 50vh; + overflow: scroll; + background: white; + margin-top: 1px; + position: absolute; + z-index: 3; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); + border-radius: 0 0 6px 6px; + border: groove 1px #f1f3f4; + } + +} + + +.header__input__suggested--gone { + display: none; +} + +.header__input__suggested__item { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.header__input__suggested__item:hover { + + background: lightgrey; + border-radius: 6px; +} + +.header__input__suggested__item__title__wrapper { + + height: inherit; + color: red; + /* width: 400px; */ + display: flex; + flex-direction: row; +} + +.header__input__suggested__item__title { + color: #373737;; + font-weight: 300; + font-size: 18px; + padding-left: 9px; + font-size: small; + text-overflow: ellipsis; + max-width: 41vw; + overflow: hidden; + white-space: nowrap; + + @media (max-width: $desktop-breakpoint) { + color: #373737; + font-weight: 300; + font-size: 18px; + padding-left: 9px; + font-size: medium; + text-overflow: ellipsis; + max-width: 41vw; + overflow: hidden; + white-space: nowrap; + } + +} + +.header__input__suggested__item__subtitle { + color: #373737;; + font-weight: 100; + font-size: small; + margin-right: 10px; +} + +.header__input__suggested__item__image { + width: 25px; + margin-left: 10px; + opacity: 0.7; + + @media (max-width: $desktop-breakpoint) { + width: 42px; + margin-left: 10px; + opacity: 0.7; + } + +} \ No newline at end of file diff --git a/src/styles/components/_HomePage.scss b/src/styles/components/_HomePage.scss new file mode 100644 index 0000000..549d692 --- /dev/null +++ b/src/styles/components/_HomePage.scss @@ -0,0 +1,47 @@ +.section { + float: left; + height: 100%; + width: 100%; + overflow-x: hidden; + overflow-y: scroll; + + + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0px; + background: transparent; /* Chrome/Safari/Webkit */ + } + +} +.main-container { + display: flex; + flex-direction: column; + width: 80vw; + width: fill-available; + // overflow-y: scroll; + overflow-x: hidden; + + @media (max-width: $desktop-breakpoint) { + width: 100vw; + } + +} + +.sub-container { + width: inherit; + // height: 83vh; + height: -webkit-fill-available; + display: inherit; + overflow: hidden; +} + +.main-page { + background: white; + height: 100vh; + width: 100vw; + display: inline-flex; + padding-top: 71px; +} + + diff --git a/src/styles/components/_LeftSection.scss b/src/styles/components/_LeftSection.scss new file mode 100644 index 0000000..d431f41 --- /dev/null +++ b/src/styles/components/_LeftSection.scss @@ -0,0 +1,203 @@ +.section--left { + margin: 0 3px 0 0; + width: 20vw; + min-width: 20%; + display: flex; + flex-direction: column; + justify-content: space-between; + // background: green; + @media (max-width: $desktop-breakpoint) { + display: none + } +} + +.add-button { + display: inline-flex; + flex-direction: row; + margin-top: 13px; + margin-left: 12px; + height: 50px; + width: 132px; + align-items: center; + box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302), 0 1px 3px 1px rgba(60,64,67,0.149); + border: 1px solid transparent; + border-radius: 24px; + overflow: hidden; + cursor: pointer; + user-select: none; + + @media (max-width: $desktop-breakpoint) { + height: 40px; + width: 104px; + margin-left: 6px; + margin-top: 6px; + min-height: 40px; + } +} + +.add-button__img { + width: 30px; + height: 30px; + margin-left: 13px; + margin-bottom: 3px; + + @media (max-width: $desktop-breakpoint) { + height: unset; + width: 21%; + margin-bottom: 1px; + } +} + +.add-button__title { + font-weight: 500; + color: #373737; + padding-left: 2px; + -moz-appearance: none; + -webkit-appearance: none; + background: none; + border: none; + font-size: 20px; + margin-left: 10px; + width: 50%; + + @media (max-width: $desktop-breakpoint) { + padding-left: 0px; + font-size: 17px; + margin-left: 8px; + } +} + +.add-button__title:focus { + outline: none; +} + +.add-button__option-wrapper { + position: absolute; + background: white; + border: none; + height: 200px; + width: 300px; + max-height: 0px; + overflow: hidden; + margin-left: -3px; + z-index: 1; + transition: margin 0.3s, max-height 0.3s; + cursor: pointer; +} + +.add-button__option-wrapper2 { + position: absolute; + background: white; + border: none; + height: 200px; + width: 300px; + max-height: 0px; + overflow: hidden; + margin-left: 6px; + z-index: 1; + transition: margin 0.3s, max-height 0.3s; + cursor: pointer; +} + + +.add-button__option-wrapper--show { + + max-height: 185px; + border: 1px solid #dadce0; + border-radius: 10px; + // padding: 0 140px 132px 36px; + margin-top: 52px; + box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302),0 1px 3px 1px rgba(60,64,67,0.149); +} + +.add_button__option__add-folder-wrapper { + border-bottom: 1px solid #dadce0; + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; +} +.add_button__option__add-folder-wrapper:hover { + background: lightgrey +} + +.add_button__option__add-folder-wrapper--no-border { + border: none; +} + +.add_button__option__add-folder-image { + width: 14%; + height: 10%; + margin-left: 14px; +} + +.add_button__option__add-folder-title { + color: #373737; + padding-top: 4px; + padding-left: 10px; +} +.add_button__option__add-file-input { + opacity: 0.0; + + /* IE 8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + + /* IE 5-7 */ + filter: alpha(opacity=0); + + /* Netscape or and older firefox browsers */ + -moz-opacity: 0.0; + + /* older Safari browsers */ + -khtml-opacity: 0.0; + + cursor: pointer; + + position: absolute; + top: 63px; + left: 0; + bottom: 0; + background: red; + height: 32%; +} + +.storage__wrapper { + width: inherit; + height: 86px; +} + +.storage__title { + color: black; + font-size: 13px; + font-weight: 400; + margin-bottom: 3px; + margin-left: 16px; +} + +.storage__subtitle { + font-size: 11px; + margin-left: 16px; + color: black; + font-weight: 300; + white-space: nowrap; + width: 85%; + overflow: hidden; + text-overflow: ellipsis; + margin-top: 4px; +} + +.storage__progress__wrapper { + + width: 75%; + height: 6px; + margin-left: 16px; + background: #e0e0e0; +} + +.storage__progress { + + // width: 50%; + // height: inherit; + // /* color: red; */ + // background: #3f85f0; +} \ No newline at end of file diff --git a/src/styles/components/_LoginPage.scss b/src/styles/components/_LoginPage.scss new file mode 100644 index 0000000..587a47e --- /dev/null +++ b/src/styles/components/_LoginPage.scss @@ -0,0 +1,8 @@ +.login { + position: absolute; + margin: auto; + top: 0; + right: 0; + bottom: 0; + left: 0; +} \ No newline at end of file diff --git a/src/styles/components/_MoverMenu.scss b/src/styles/components/_MoverMenu.scss new file mode 100644 index 0000000..97110e3 --- /dev/null +++ b/src/styles/components/_MoverMenu.scss @@ -0,0 +1,158 @@ +.movermenu { + width: 400px; + height: 500px; + background: white; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + -webkit-box-shadow: 0 11px 8px 0 rgba(0,0,0,0.2); + box-shadow: 0 11px 8px 0 rgba(0,0,0,0.2); + border: 2px solid #dadce0; + + @media (max-width: $desktop-breakpoint) { + width: 100%; + height: 100%; + } +} + +.movermenu__close-button { + width: 32px; + opacity: 0.8; + cursor: pointer; +} + +.movermenu__form { + + @media (max-width: $desktop-breakpoint) { + width: 100%; + display: flex; + justify-content: center + } +} + +.movermenu__nav_wrapper { + width: 100%; + height: 32px; + /* background: green; */ + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.movermenu__main_wrapper { + width: 100%; + height: 341px; + overflow: scroll; + + @media (max-width: $desktop-breakpoint) { + width: 100%; + height: calc(100% - 155px); + } +} + +.movermenu__search_wrapper { + width: 100%; + height: 48px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.movermenu__input { + border: 1px solid #cacccd; + border-radius: 2px; + height: 32px; + font-size: 1.8rem; + font-weight: 300; + padding: 1.2rem; + margin: 0 0 1.2rem 0; + width: 381px; + margin-top: 11px; + background: #f1f3f4; + + @media (max-width: $desktop-breakpoint) { + height: 41px; + font-size: 1.9rem; + width: 98%; + } +} + + +.movermenu__input:focus { + + background: white; + outline: none; + border-style: groove; +} + +.movermenu__home-button { + width: 32px; + margin-right: 10px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + width: 35px; + } +} + +.movermenu__home-button--disabled { + + cursor: default; + opacity: 0.4; +} + +.movermenu__back-button { + width: 32px; + margin-left: 3px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + width: 35px; + } + +} + +.movermenu__back-button--disabled { + opacity: 0.4; + cursor: default; +} + +.movermenu__title { + color: black; + font-size: 20px; + font-weight: 200; + margin-left: -6px; + max-width: 55%; + overflow: hidden; + text-overflow: ellipsis; +} + +.movermenu__button { + margin-right: 10px; + color: white; + background: #3c85ed; + font-size: 12px; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: 2px; + padding: 9px; +} + +.movermenu__button_wrapper { + width: 100%; + /* background: blue; */ + height: 36px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + + +} \ No newline at end of file diff --git a/src/styles/components/_Photoviewer.scss b/src/styles/components/_Photoviewer.scss new file mode 100644 index 0000000..a8f1837 --- /dev/null +++ b/src/styles/components/_Photoviewer.scss @@ -0,0 +1,41 @@ +.photoviewer { + background: rgba(0, 0, 0, 0.95); + width: 100%; + position: fixed; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + z-index: 5; +} + +.photoviewer__close-button { + width: 22px; + margin-left: 10px; + margin-top: 10px; + opacity: 0.6; + cursor: pointer; +} + +.photoviewer__image { + + width: 95%; + height: 90%; + margin-left: 2.5%; + // background: blue; + object-fit: contain; +} + +.photoviewer__loader { + width: 98%; + height: 88%; + display: flex; + align-items: center; + + @media (max-width: $desktop-breakpoint) { + width: 90%; + } +} \ No newline at end of file diff --git a/src/styles/components/_Popup-window.scss b/src/styles/components/_Popup-window.scss new file mode 100644 index 0000000..ebd79c5 --- /dev/null +++ b/src/styles/components/_Popup-window.scss @@ -0,0 +1,127 @@ +.popup-window { + width: 61%; + height: 70%; + background: white; + position: fixed; + margin: 0 auto; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + z-index: 1; + -webkit-box-shadow: 0 11px 8px 0 rgba(0,0,0,0.2); + box-shadow: 0 11px 8px 0 rgba(0,0,0,0.2); + border: 2px solid #dadce0; + border-radius: 6px; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + + @media (max-width: $desktop-breakpoint) { + width: 100%; + height: 100%; + } + +} + +.popup-window--gone { + + display: none; +} + +.popup-window__title { + color: black; + font-weight: 300; + margin-top: 10px; + /* width: 100px; */ + max-width: 72%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.popup-window__subtitle { + color: black; + font-weight: 300; + font-size: 11px; + margin-top: -11px; +} + +.popup-window__image__wrapper__subwrapper { + background: red; +} + +.popup-window__image { + width: 120px; + opacity: 0.8; +} + +.popup-window__image--loaded { + + width: 100%; + max-height: 100%; + object-fit: cover; + opacity: 1; + max-width: 100%; + border-radius: 6px; + cursor: pointer; + +} + +.popup-window__button { + width: unset; + margin-bottom: 12px; +} + +.popup-window__image__wrapper { + // width: inherit; + flex-direction: column-reverse; + align-items: center; + display: flex; + margin-bottom: 50px; + height: 50%; + width: 80%; + max-width: 80%; + overflow: hidden; + position: relative; + justify-content: center; + max-height: 50%; + + @media (max-width: $desktop-breakpoint) { + flex-direction: column-reverse; + align-items: center; + display: flex; + margin-bottom: 50px; + height: 65%; + width: 99%; + max-width: 99%; + overflow: hidden; + position: relative; + justify-content: center; + max-height: 50%; + } + +} + +.popup-window__spinner__wrapper { + margin-left: -25px; +} + +.popup-window__close-button { + width: 29px; + position: absolute; + left: 8px; + top: 10px; + opacity: 0.7; + cursor: pointer; +} + +.popup-window__video { + max-height: 70%; + max-width: 95%; + border-radius: 3px; + + +} \ No newline at end of file diff --git a/src/styles/components/_QuickAccess.scss b/src/styles/components/_QuickAccess.scss new file mode 100644 index 0000000..0925d44 --- /dev/null +++ b/src/styles/components/_QuickAccess.scss @@ -0,0 +1,96 @@ +.quickaccess { + width: inherit; + height: 170px; + // background: red; + display: flex; + /* flex-direction: row; */ + flex-flow: row wrap; + align-items: center; + /* overflow-y: hidden; */ + overflow: hidden; + +} + +.quickaccess__item { + min-width: 186px; + min-height: 160px; + height: 160px; + width: 186px; + margin: 5px; + // background: blue; + display: flex; + flex-direction: column; + align-items: center; + border: 1px solid #dadce0; + border-radius: 10px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + position: relative; + + @media (max-width: $desktop-breakpoint) { + + position: unset; + } + +} + +.quickaccess__item__image__wrapper { + width: 100px; + height: 100px; +} + +.quickaccess__item__image { + width: 100px; + height: 100px; + opacity: 0.7; +} + +.quickaccess__item__image--no-opacity { + opacity: 1; + border-radius: 10px; + margin-top: 5px; + max-height: 99px; + max-width: 99px; + object-fit: cover; + +} + +.quickaccess__item__title { + // color: black; + font-size: 13px; + font-weight: 350; + margin-top: 8px; + text-overflow: ellipsis; + white-space: nowrap; + /* max-width: 100px; */ + width: 90%; + /* height: 54px; */ + /* overflow: hidden; */ + overflow-x: hidden; + overflow-y: visible; + margin-left: 5px; +} + +.quickaccess__item__subtitle { + + // color: black; + margin-top: -12px; + font-weight: 100; + font-size: 11px; + overflow-x: hidden; + overflow-y: visible; + text-overflow: ellipsis; + white-space: nowrap; + width: 90%; + margin-left: 5px; +} + +.quickaccess__item__title__wrapper { + width: 100%; +} \ No newline at end of file diff --git a/src/styles/components/_RightSection.scss b/src/styles/components/_RightSection.scss new file mode 100644 index 0000000..d44e1cf --- /dev/null +++ b/src/styles/components/_RightSection.scss @@ -0,0 +1,141 @@ +.section--right { + margin: 0 0 0 3px; + width: 37vw; + transition: width 0.5s; + overflow: hidden; + display: flex; + flex-direction: column; + // height: 83vh; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0px; + background: transparent; /* Chrome/Safari/Webkit */ + } + + @media (max-width: $desktop-breakpoint) { + display: none + } +} + +.section--minimized { + width: 0%; + margin: 0; +} + +.section--no-animation { + + transition: none; +} + +.section__title-wrapper { + width: 25vw; + height: 30%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.section__title-text { + + color: black; + word-wrap: break-word; + overflow: scroll; + line-height: 24px; + font-weight: 300; + margin-left: 10px; + text-overflow: ellipsis; + max-height: 100%; + padding: 6px; + width: 77%; + + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0px; + background: transparent; /* Chrome/Safari/Webkit */ + } + +} + +.section__image { + + width: 17vw; + margin-left: 36px; + opacity: 0.3; +} + +.section__title { + color: black; + font-weight: 300; + line-height: 24px; + min-width: 25vw; + max-width: 25vw; +} + +.section-detail-wrapper { + width: inherit; + height: inherit; +} + +.section__none--wrapper { + width: inherit; + display: flex; + flex-direction: column; + justify-content: center; +} + +.section__title-text--gone { + visibility: hidden; +} + +.section__title-image { + + width: 30px; + margin-right: 10px; + cursor: pointer; +} + +.section__title-image--gone { + visibility: hidden; +} + +.section-detail__block { + width: 34vw; + height: 70%; + /* background: green; */ + overflow: scroll; + margin-top: 10px; +} + +.section-detail__wrapper { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.section-detail__title { + color: black; + margin-left: 15px; + font-weight: 300; +} + +.section-detail__title--margin { + margin-top: -15px; +} + +.section-detail__body { + color: black; + font-weight: 500; + margin-right: 35%; + word-wrap: break-word; + overflow: hidden; + width: 43%; + line-height: 22px; + margin-left: 10px; +} + +.section-detail__body--margin { + margin-top: -15px; +} + diff --git a/src/styles/components/_SettingsMenu.scss b/src/styles/components/_SettingsMenu.scss new file mode 100644 index 0000000..69a1c27 --- /dev/null +++ b/src/styles/components/_SettingsMenu.scss @@ -0,0 +1,255 @@ +.settingsmenu { + width: 918px; + height: 487px; + background: white; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + -webkit-box-shadow: 0 11px 8px 0 rgba(0,0,0,0.2); + box-shadow: 0 11px 8px 0 rgba(0,0,0,0.2); + border: 2px solid #dadce0; + z-index: 1; + border-radius: 6px; + + @media (max-width: $desktop-breakpoint) { + width: 100%; + height: 100%; + } +} + +.settingsmenu--gone { + + display: none; +} + +.settingsmenu__header { + height: 45px; + background: #fff; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + +} + + +.settingsmenu__header__image { + height: 27px; + opacity: 0.8; + margin-top: 17px; +} + +.settingsmenu__header__title { + color: black; + font-weight: 300; + margin-left: 6px; + font-size: 13px; + margin-top: 21px +} + +.settingsmenu__header__button { + + margin-right: 10px; + height: 28px; + margin-top: -6px; + cursor: pointer; +} + +.settingsmenu__header__box { + + height: inherit; + width: 30%; + display: flex; + flex-direction: row; + margin-left: 10px; + margin-top: -18px; +} + +.settingsmenu_body { + + max-height: 441px; + overflow-y: scroll; + overflow-x: hidden; + + @media (max-width: $desktop-breakpoint) { + max-height: 93vh; + } +} + +.settingsmenu__option { + display: flex; + flex-direction: row; + justify-content: space-around; + border-bottom: 1px solid #eee; + height: 77px; + max-height: 77px; + /* align-items: center; */ + position: relative; + /* align-content: center; */ + align-items: center; + + @media (max-width: $desktop-breakpoint) { + justify-content: space-between; + } +} + +.settingsmenu__option--no-border { + + border: unset +} + +.settingsmenu__option__title { + + width: 250px; + margin-left: 8%; + font-weight: 500; + /* position: absolute; */ + + @media (max-width: $desktop-breakpoint) { + margin-left: 10px; + } +} + +.settingsmenu__option__title--offset { + margin-left: 59.5px; + + @media (max-width: $desktop-breakpoint) { + margin-left: 10px; + } +} + +.settingsmenu__suboption { + width: 230px; + height: 76px; + display: flex; + align-items: center; + + @media (max-width: $desktop-breakpoint) { + + justify-content: center; + flex-direction: column; + margin-right: 10px; + width: 135px; + align-items: flex-end; + } +} + +.settingsmenu__suboption--offset { + margin-left: -42px; + + @media (max-width: $desktop-breakpoint) { + + margin-left: unset; + } +} + +.settingsmenu__select { + + background: none; + border: none; + -moz-appearance: none; + -webkit-appearance: none; + color: #5f6368; + font-size: 15px; + outline: none; + font-weight: 500; + padding-left: 3px; + padding-right: 4px; +} + +.settingsmenu__select:hover { + background: lightgrey +} + +.settingsmenu__button { + + color: white; + background: #3c85ed; + font-size: 12px; + /* padding: 1.2rem; */ + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: 2px; + padding: 9px; + + &:hover { + background: darken(#3c85ed, 10); + color: darken(white, 10) + } +} + +.settingsmenu__button--red { + background: red; + margin-left: 3.5px; + + &:hover { + background: darken(red, 10); + color: darken(white, 10) + } + +} + +.settingsmenu__button--offset-logout { + margin-left: -3px; +} + +.settingsmenu__button--offset-logout-all { + margin-left: 4px; +} + +.settingsmenu__option__title__wrapper { + display: flex; + justify-content: center; + /* width: 100%; */ + margin-left: 47px; + + @media (max-width: $desktop-breakpoint) { + + margin-left: unset; + } +} + +.settingsmenu__option__info { + width: 18px; + position: absolute; + margin-left: -21px; + opacity: 0.7; + top: 12px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + margin-left: -41px; + } +} + +.settingsmenu__option__info__style { + width: 18px; + position: absolute; + margin-left: 28px; + opacity: 0.7; + top: 12px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + margin-left: 10px; + } +} + +.settingsmenu__option__info__size { + width: 18px; + position: absolute; + margin-left: 25px; + opacity: 0.7; + top: 12px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + margin-left: 2px; + } +} \ No newline at end of file diff --git a/src/styles/components/_Sharemenu.scss b/src/styles/components/_Sharemenu.scss new file mode 100644 index 0000000..499998b --- /dev/null +++ b/src/styles/components/_Sharemenu.scss @@ -0,0 +1,134 @@ +.sharemenu { + + width: 425px; + height: 230px; + position: fixed; + background: white; + left: calc(70% - 425px); + box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302), 0 1px 3px 1px rgba(60,64,67,0.149); + border: 1px solid transparent; + border-radius: 6px; + z-index: 1; + left: 0; + right: 0; + margin: 0 auto; + display: flex; + flex-direction: column; + text-align: center; + justify-content: center; + align-items: center; + + @media (max-width: $desktop-breakpoint) { + width: 100%; + height: 100%; + top: 0; + } +} + +.sharemenu--gone { + display: none; +} + +.sharemenu--block { + width: 100% +} + +.sharemenu__title { + color: black; + max-width: 88%; + overflow: hidden; + text-overflow: ellipsis; + /* margin-left: 5px; */ + margin-right: 5px; + font-size: 13.5px; + font-weight: 200; + white-space: nowrap; +} + +.sharemenu__subtitle { + color: black; +} + +.sharemenu__button__public { + + color: white; + background: #3c85ed; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: 2px; + width: 29%; + height: 37px; + padding: 3px; + + &:hover { + background: darken(#3c85ed, 10); + color: darken(white, 10) + } + + @media (max-width: $desktop-breakpoint) { + width: 36%; + height: 47px; + } + +} + +.sharemenu__close-button { + width: 25px; + position: absolute; + top: 1px; + left: 1px; + opacity: 0.8; + + @media (max-width: $desktop-breakpoint) { + width: 42px; + } +} + +.sharemenu__button__one { + color: white; + background: #3c85ed; + font-size: $font-size-large; + padding: $s-size; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: $small-border; + + &:hover { + background: darken(#3c85ed, 10); + color: darken(white, 10) + } +} + +.sharemenu__button__done { + color: white; + background: #3c85ed; + font-size: $font-size-large; + padding: $s-size; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: $small-border; + + &:hover { + background: darken(#3c85ed, 10); + color: darken(white, 10) + } +} + +.sharemenu__link__wrapper { + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; +} + +.sharemenu__image { + width: 27px; + margin-left: 10px; + margin-right: 5px; +} \ No newline at end of file diff --git a/src/styles/components/_Spacer.scss b/src/styles/components/_Spacer.scss new file mode 100644 index 0000000..7b32b87 --- /dev/null +++ b/src/styles/components/_Spacer.scss @@ -0,0 +1,12 @@ +.spacer { + display: flex; + height: 37px; +} + +.spacer__title { + color: #5f6368; + margin-left: 22px; + font-weight: 500; + font-size: 13px; + margin-left: 38px; +} \ No newline at end of file diff --git a/src/styles/components/_Spinner.scss b/src/styles/components/_Spinner.scss new file mode 100644 index 0000000..c522891 --- /dev/null +++ b/src/styles/components/_Spinner.scss @@ -0,0 +1,21 @@ +.spinner { + border: 3px solid #f3f3f3; + border-top: 3px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + margin-left: 50%; + text-align: center; + animation: spin 2s linear infinite; + + + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + + .spinner--small-margin { + margin-left: 4px; + } \ No newline at end of file diff --git a/src/styles/components/_Subbar.scss b/src/styles/components/_Subbar.scss new file mode 100644 index 0000000..9e00ba1 --- /dev/null +++ b/src/styles/components/_Subbar.scss @@ -0,0 +1,98 @@ +.info-button { + width: 30px; + position: absolute; + right: 15px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + display: none; + } +} + +.info-button:hover { + -webkit-filter: drop-shadow(1px 1px 3px #222222); +} + +.drive-dropdown { + + width: 100px; + padding-left: 15px; +} + +.grid-button { + width: 30px; + position: absolute; + right: 60px; + cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + right: 9px; + } +} + +.subbar { + // background: purple; + width: 80vw; + display: flex; + flex-direction: row; + align-items: center; + height: 7vh; + min-height: 50px; + user-select: none; + // overflow-x: scroll; + +} + +.path__block { + height: inherit; + /* min-height: 7vh; */ + /* width: 50px; */ + display: flex; + flex-direction: row; + align-items: center; + cursor: pointer; +} + +.path__wrapper { + // background: blue; + width: 50vw; + margin-left: 12px; + display: flex; + flex-direction: row; + overflow-x: scroll; + overflow-y: hidden; + height: 52px; + /* max-height: inherit; */ + min-height: inherit; + /* min-height: 7vh; */ + // margin-top: 4px; + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + width: 0px; + //background: transparent; /* Chrome/Safari/Webkit */ + } + + @media (max-width: $desktop-breakpoint) { + margin-top: 4px; + } + +} + +.path__title { + color: black; + font-size: 17px; + line-height: 0; + font-weight: 300; + white-space: nowrap; + /* height: fit-content; */ + padding: 0px 0 2px 0; + margin-top: 19px; +} + +.path__image { + height: fit-content; + /* width: 30px; */ + // margin-top: 7px; +} \ No newline at end of file diff --git a/src/styles/components/_Swal.scss b/src/styles/components/_Swal.scss new file mode 100644 index 0000000..ad606cb --- /dev/null +++ b/src/styles/components/_Swal.scss @@ -0,0 +1,10 @@ + +.swal2-popup { + + font-size: 10px !important; + + @media (max-width: $desktop-breakpoint) { + + font-size: 13px !important; + } +} \ No newline at end of file diff --git a/src/styles/components/_Uploader.scss b/src/styles/components/_Uploader.scss new file mode 100644 index 0000000..f166de7 --- /dev/null +++ b/src/styles/components/_Uploader.scss @@ -0,0 +1,145 @@ +.uploader__wrapper { + width: 330px; + /* max-height: 255px; */ + /* overflow: scroll; */ + background: white; + display: flex; + flex-direction: column; + position: absolute; + right: 25px; + bottom: 25px; + border-radius: 5px; + overflow: hidden; + -webkit-box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); + + @media (max-width: $desktop-breakpoint) { + position:absolute; + left:0;right:0;bottom:0; + margin:0 auto; + } + +} + +.uploader__wrapper--gone { + display: none; +} + +.uploader__header__wrapper { + width: inherit; + height: 51px; + display: flex; + flex-direction: row; + background: #323232; + justify-content: space-between; + align-items: center; +} + +.uploader__header__title { + max-width: 50%; + text-overflow: ellipsis; + overflow: hidden; + font-weight: 300; + margin-left: 15px; + color: white; +} + +.uploader__header__button_wrapper { + display: flex; + flex-direction: row; + margin-right: 15px; + height: inherit; + align-items: center; + +} + +.uploader__header__button { + + margin-left: 33px; + /* height: 34px; */ + width: 27px; + opacity: 0.7; + padding: 4px; + cursor: pointer; +} + +.upload__item__wrapper { + width: inherit; + height: 50px; + /* background: green; */ + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #dadce0; + position: relative; + +} + +.CircularProgressbar { + + height: 30px; + width: 30px; +} + +.upload__item__block { + width: inherit; + max-height: 255px; + overflow-x: hidden; + overflow-y: scroll; + display: flex; + // flex-direction: column-reverse; + flex-direction: column; + transition: height 0.5s; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0px; + background: transparent; /* Chrome/Safari/Webkit */ + } +} + +.upload__item__block--hide { + + height: 0px; +} + +.upload__item__item { + margin-right: 15px; + max-width: 72%; + text-overflow: ellipsis; + overflow: hidden; + font-weight: 300; + margin-left: 15px; + color: black; + white-space: nowrap; +} + +.upload__item__progress { + cursor: pointer; +} + +.upload__item__image { + width: 30px; + height: 30px; + position: absolute; + /* top: 1px; */ + /* margin-right: 15px; */ + opacity: 0; + transition: opacity 0.5s; + right: 15px; +} + +.upload__item__item__wrapper { + cursor: pointer; + margin-right: 15px; + margin-left: 15px; + z-index: 3; + width: 30px; + max-width: 30px; + // background: purple; +} + +.upload__item__item__wrapper--hidden { + visibility: hidden; +} \ No newline at end of file diff --git a/src/styles/components/_box-layout.scss b/src/styles/components/_box-layout.scss new file mode 100644 index 0000000..a446637 --- /dev/null +++ b/src/styles/components/_box-layout.scss @@ -0,0 +1,87 @@ +.box-layout { + background: white; + height: 100vh; + width: 100vw; + background-size: cover; + display: flex; + justify-content: center; + align-items: center; +} + +.box-layout__box { + background: fade-out(white, .15); + border-radius: 3px; + text-align: center; + width: 35rem; + padding: 3.2rem 1.6rem 1.2rem 1.6rem; + border: 0.5px; + border-style: groove; + display: flex; + flex-direction: column; + + @media (max-width: $desktop-breakpoint) { + border: none; + width: -webkit-fill-available; + } +} + +.box-layout__title { + margin: 0 0 3rem 0; + line-height: 1; + font-family: Arial, Helvetica, sans-serif; + font-weight: 500; + color: $grey; +} + +.box-layout__input { + border: 1px solid #cacccd; + border-radius: $small-border; + height: 43px; + font-size: $font-size-large; + font-weight: 300; + padding: $s-size; + margin: 0 0 $s-size 0; + width: -webkit-fill-available; +} + +.box-layout--button { + width: -webkit-fill-available; +} + +.box-layout__icon { + position: absolute; + width: 130px; + left: 25px; + top: 25px; + + @media (max-width: $desktop-breakpoint) { + left: 0; + top: 0; + padding: 15px; + } +} + +.box-layout__text { + color: $grey; +} + +.login__image__wrapper { + width: inherit; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: 20px; +} + +.login__image { + + width: 25px; + height: 25px; + margin-right: 4px; +} + +.login__title { + + color: black; +} \ No newline at end of file diff --git a/src/styles/components/_button.scss b/src/styles/components/_button.scss new file mode 100644 index 0000000..f0a567d --- /dev/null +++ b/src/styles/components/_button.scss @@ -0,0 +1,50 @@ +.button { + color: white; + background: #3c85ed; + font-size: $font-size-large; + padding: $s-size; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: $small-border; + + &:hover { + background: darken(#3c85ed, 10); + color: darken(white, 10) + } +} + +.button--small { + color: white; + background: #3c85ed; + font-size: 11px; + padding: 12px; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: 2px; + width: 35%; + white-space: nowrap; + height: 100%; + margin-left: 10%; + + &:hover { + background: darken(#3c85ed, 10); + color: darken(white, 10) + } +} + +.button--small--disabled { + + opacity: 0.5; +} + +.button--link { + background: none; +} + +.button--secondary { + background: #888888 +} \ No newline at end of file diff --git a/src/styles/components/_content-container.scss b/src/styles/components/_content-container.scss new file mode 100644 index 0000000..2f0166e --- /dev/null +++ b/src/styles/components/_content-container.scss @@ -0,0 +1,6 @@ +.content-container { + margin: 0 auto; + max-width: 80rem; + padding: 0 $m-size; + background: black; +} \ No newline at end of file diff --git a/src/styles/components/_loader.scss b/src/styles/components/_loader.scss new file mode 100644 index 0000000..f7ec77b --- /dev/null +++ b/src/styles/components/_loader.scss @@ -0,0 +1,3 @@ +.loader { + +} \ No newline at end of file diff --git a/src/styles/styles.scss b/src/styles/styles.scss new file mode 100644 index 0000000..d8c9198 --- /dev/null +++ b/src/styles/styles.scss @@ -0,0 +1,28 @@ +@import "./base/settings"; +@import "./base/base"; +@import "./components/content-container"; +@import "./components/LoginPage"; +@import "./components/box-layout"; +@import "./components/button"; +@import "./components/Header"; +@import "./components/HomePage"; +@import "./components/Dataform"; +@import "./components/Folder"; +@import "./components/File"; +@import "./components/Filter"; +@import "./components/LeftSection"; +@import "./components/Subbar"; +@import "./components/RightSection"; +@import "./components/Uploader"; +@import "./components/loader"; +@import "./components/Spinner"; +@import "./components/QuickAccess"; +@import "./components/Spacer"; +@import "./components/Popup-window"; +@import "./components/ContentMenu"; +@import "./components/Sharemenu"; +@import "./components/DownloadPage"; +@import "./components/SettingsMenu"; +@import "./components/Swal"; +@import "./components/Photoviewer"; +@import "./components/MoverMenu"; diff --git a/src/utils/InternalServerError.js b/src/utils/InternalServerError.js new file mode 100644 index 0000000..5556ea5 --- /dev/null +++ b/src/utils/InternalServerError.js @@ -0,0 +1,10 @@ +class InternalServerError extends Error { + + constructor(args) { + super(args); + + this.code = 500; + } +} + +module.exports = InternalServerError; \ No newline at end of file diff --git a/src/utils/NotAuthorizedError.js b/src/utils/NotAuthorizedError.js new file mode 100644 index 0000000..d28fd6e --- /dev/null +++ b/src/utils/NotAuthorizedError.js @@ -0,0 +1,10 @@ +class NotAuthorizedError extends Error { + + constructor(args) { + super(args); + + this.code = 401; + } +} + +module.exports = NotAuthorizedError; \ No newline at end of file diff --git a/src/utils/NotFoundError.js b/src/utils/NotFoundError.js new file mode 100644 index 0000000..128205f --- /dev/null +++ b/src/utils/NotFoundError.js @@ -0,0 +1,10 @@ +class NotFoundError extends Error { + + constructor(args) { + super(args); + + this.code = 404; + } +} + +module.exports = NotFoundError; \ No newline at end of file diff --git a/src/utils/capitalize.js b/src/utils/capitalize.js new file mode 100644 index 0000000..26ea026 --- /dev/null +++ b/src/utils/capitalize.js @@ -0,0 +1,12 @@ +export default (name) => { + + if (name.length <= 1) { + + return name.toUpperCase(); + + } else { + + return name.substring(0,1).toUpperCase() + name.substring(1); + } + +} \ No newline at end of file diff --git a/src/utils/createError.js b/src/utils/createError.js new file mode 100644 index 0000000..6e9efc1 --- /dev/null +++ b/src/utils/createError.js @@ -0,0 +1,12 @@ +const createError = (message, code, exception) => { + + let error = new Error(message); + error.message = message + error.code = code + error.exception = exception + + throw error; + +} + +module.exports = createError; \ No newline at end of file diff --git a/src/utils/createQuery.js b/src/utils/createQuery.js new file mode 100644 index 0000000..c4f6751 --- /dev/null +++ b/src/utils/createQuery.js @@ -0,0 +1,41 @@ +const createQuery = (owner, parent,sortBy, startAt, startAtDate,searchQuery, startAtName) => { + + let query = {"metadata.owner": owner} + + if (searchQuery !== "") { + + searchQuery = new RegExp(searchQuery, 'i') + + query = {...query, filename: searchQuery} + + } else { + + query = {...query, "metadata.parent": parent} + } + + if (startAt) { + + if (sortBy === "date_desc" || sortBy === "DEFAULT") { + + query = {...query, "uploadDate": {$lt: new Date(startAtDate)}} + + } else if (sortBy === "date_asc") { + + query = {...query, "uploadDate": {$gt: new Date(startAtDate)}} + + } else if (sortBy === "alp_desc") { + + query = {...query, "filename": {$lt: startAtName}} + + } else { + + query = {...query, "filename": {$gt: startAtName}} + } + } + + + return query; + +} + +module.exports = createQuery \ No newline at end of file diff --git a/src/utils/imageChecker.js b/src/utils/imageChecker.js new file mode 100644 index 0000000..2df835e --- /dev/null +++ b/src/utils/imageChecker.js @@ -0,0 +1,31 @@ +const videoExtList = [ + "jpeg", + "jpg", + "png", + "gif", + "svg", + "tiff", + "bmp" +] + +const videoChecker = (filename) => { + + if (filename.length < 1 || !filename.includes(".")) { + + return false; + } + + const extSplit = filename.split("."); + + if (extSplit < 1) { + + return false; + } + + const ext = extSplit[extSplit.length - 1]; + + return videoExtList.includes(ext.toLowerCase()); + +} + +module.exports = videoChecker; \ No newline at end of file diff --git a/src/utils/mobileCheck.js b/src/utils/mobileCheck.js new file mode 100644 index 0000000..67876d7 --- /dev/null +++ b/src/utils/mobileCheck.js @@ -0,0 +1,6 @@ +const mobilecheck = () => { + var check = false; + (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); + return check; + }; +export default mobilecheck; diff --git a/src/utils/sortBySwitch.js b/src/utils/sortBySwitch.js new file mode 100644 index 0000000..d0c43c3 --- /dev/null +++ b/src/utils/sortBySwitch.js @@ -0,0 +1,15 @@ +const sortBySwitch = (sortBy) => { + switch(sortBy) { + + case "alp_asc": + return {filename: 1} + case "alp_desc": + return {filename: -1} + case "date_asc": + return {uploadDate: 1} + default: + return {uploadDate: -1} + } +} + +module.exports = sortBySwitch; \ No newline at end of file diff --git a/src/utils/sortBySwitchFolder.js b/src/utils/sortBySwitchFolder.js new file mode 100644 index 0000000..314397e --- /dev/null +++ b/src/utils/sortBySwitchFolder.js @@ -0,0 +1,15 @@ +const sortBySwitch = (sortBy) => { + switch(sortBy) { + + case "alp_asc": + return {name: 1} + case "alp_desc": + return {name: -1} + case "date_asc": + return {createdAt: 1} + default: + return {createdAt: -1} + } +} + +module.exports = sortBySwitch; \ No newline at end of file diff --git a/src/utils/videoChecker.js b/src/utils/videoChecker.js new file mode 100644 index 0000000..e3c31f6 --- /dev/null +++ b/src/utils/videoChecker.js @@ -0,0 +1,57 @@ +const videoExtList = [ + "3g2", + "3gp", + "aaf", + "asf", + "avchd", + "avi", + "drc", + "flv", + "m2v", + "m4p", + "m4v", + "mkv", + "mng", + "mov", + "mp2", + "mp4", + "mpe", + "mpeg", + "mpg", + "mpv", + "mxf", + "nsv", + "ogg", + "ogv", + "qt", + "rm", + "rmvb", + "roq", + "svi", + "vob", + "webm", + "wmv", + "yuv" +] + +const videoChecker = (filename) => { + + if (filename.length < 1 || !filename.includes(".")) { + + return false; + } + + const extSplit = filename.split("."); + + if (extSplit < 1) { + + return false; + } + + const ext = extSplit[extSplit.length - 1]; + + return videoExtList.includes(ext.toLowerCase()); + +} + +module.exports = videoChecker; \ No newline at end of file diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000..8759201 Binary files /dev/null and b/tests/.DS_Store differ diff --git a/tests/db/utils/fileUtils/index.test.js b/tests/db/utils/fileUtils/index.test.js new file mode 100644 index 0000000..ae684f8 --- /dev/null +++ b/tests/db/utils/fileUtils/index.test.js @@ -0,0 +1,1125 @@ +const User = require("../../../../src/models/user"); +const mongoose = require("../../../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../../../fixtures/createUser"); +const path = require("path"); +const createFile = require("../../../fixtures/createFile"); +const UtilsFile = require("../../../../src/db/utils/fileUtils"); +const ObjectID = require('mongodb').ObjectID +const jwt = require("jsonwebtoken"); +const utilsFile = new UtilsFile(); + +let user; +let file; + +process.env.KEY = "1234"; + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + conn.once("open", async() => { + + //user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const filename = "bunny.mp4"; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: "/", + parentList: "/" + } + + file = await createFile(filename, filepath, metadata, user); + + done(); + }) + } else { + // user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const filename = "bunny.mp4"; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: "/", + parentList: "/" + } + + file = await createFile(filename, filepath, metadata, user); + + done(); + } +}) + +afterEach( async(done) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db); + + await User.deleteMany({}); + + const allFiles = await conn.db.collection("fs.files").find({}).toArray(); + + for (let i = 0; i < allFiles.length; i++) { + + const currentFileID = allFiles[i]._id; + await bucket.delete(ObjectID(currentFileID)); + } + + done(); + +}) + + +test("When giving fileID, should return public file", async() => { + + const fileID = file._id; + const tempToken = file.metadata.link; + + const recievedFile = await utilsFile.getPublicInfo(fileID, tempToken); + + expect(recievedFile._id).toEqual(fileID); + +}) + +test("When giving wrong fileID, should not return public file", async() => { + + const wrongFileID = "123456789012"; + + const recievedFile = await utilsFile.getFileInfo(wrongFileID); + + expect(recievedFile).toBe(null); +}) + +test("When giving the wrong tempToken, should not return public file", async() => { + + const fileID = file._id; + const wrongTempToken = "1234" + + const recievedFile = await utilsFile.getPublicInfo(fileID, wrongTempToken); + + expect(recievedFile).toBe(null); +}) + +test("When giving fileID and userID, should remove public file link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + + + await utilsFile.removeLink(fileID, userID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + + expect(updatedFile.metadata.linkType).toBe(undefined); +}) + +test("When giving the wrong fileID, should not remove public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + const wrongFileID = "123456789012"; + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + + + await utilsFile.removeLink(wrongFileID, userID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + + expect(updatedFile.metadata.linkType).toEqual("public") +}) + +test("When giving the wrong userID, should not remove public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + const wrongUserID = "123456789012"; + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + + + await utilsFile.removeLink(fileID, wrongUserID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + + expect(updatedFile.metadata.linkType).toEqual("public") +}) + +test("When giving fileID, should remove one time public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "one", "metadata.link": token}}) + + + + await utilsFile.removeOneTimePublicLink(fileID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + + expect(updatedFile.metadata.linkType).toBe(undefined); +}) + +test("When giving the wrong fileID, should not remove one time public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + const wrongFileID = "123456789012"; + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "one", "metadata.link": token}}) + + await utilsFile.removeOneTimePublicLink(wrongFileID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + + expect(updatedFile.metadata.linkType).toBe("one"); +}) + +test("When giving fileID, userID, and token, should make file public", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + + + await utilsFile.makePublic(fileID, userID, token); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.linkType).toBe("public") +}) + +test("When giving wrong fileID, should not make file public", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + const wrongFileID = "123456789012"; + + + await utilsFile.makePublic(wrongFileID, userID, token); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.linkType).toBe(undefined) +}) + +test("When giving wrong userID, should not make file public", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const wrongUserID = "123456789012"; + + + await utilsFile.makePublic(fileID, wrongUserID, token); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.linkType).toBe(undefined) +}) + +test("When giving fileID, userID, and token, should make one time public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + + + await utilsFile.makeOneTimePublic(fileID, userID, token); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.linkType).toBe("one") +}) + +test("When giving wrong fileID, should not make one time public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const userID = user._id; + const wrongFileID = "123456789012"; + + + await utilsFile.makeOneTimePublic(wrongFileID, userID, token); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.linkType).toBe(undefined) +}) + +test("When giving wrong userID, should not make one time public link", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD) + const fileID = file._id; + const wrongUserID = "123456789012"; + + + await utilsFile.makeOneTimePublic(fileID, wrongUserID, token); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.linkType).toBe(undefined) +}) + +test("When giving fileID and userID, should return file", async() => { + + const fileID = file._id; + const userID = user._id; + + const recievedFile = await utilsFile.getFileInfo(fileID, userID); + + + expect(recievedFile._id).toEqual(fileID); +}) + +test("When giving wrong fileID, should not return file", async() => { + + const userID = user._id; + const wrongFileID = "123456789012"; + + const recievedFile = await utilsFile.getFileInfo(wrongFileID, userID); + + expect(recievedFile).toEqual(null); +}) + +test("When giving wrong userID, should not return file", async() => { + + const fileID = file._id; + const wrongUserID = "123456789012"; + + const recievedFile = await utilsFile.getFileInfo(fileID, wrongUserID); + + expect(recievedFile).toEqual(null); +}) + +test("When giving userID, should return quicklist", async() => { + + const userID = user._id; + + const recievedList = await utilsFile.getQuickList(userID); + + expect(recievedList.length).toBe(1); +}) + +test("When giving wrong userID, should not return quicklist", async() => { + + const wrongUserID = "123456789012"; + + const recievedList = await utilsFile.getQuickList(wrongUserID); + + expect(recievedList.length).toBe(0); +}) + +test("When giving default query object, should return a filtered file list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + const fileFour = await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + const fileFive = await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + } + + const defaultSortBy = {uploadDate: -1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(3); + expect(recievedList[0]._id).toEqual(fileThree._id); + expect(recievedList[1]._id).toEqual(fileTwo._id); + expect(recievedList[2]._id).toEqual(file._id); + expect(recievedList.includes(fileFour)).toBe(false); + expect(recievedList.includes(fileFive)).toBe(false); +}) + +test("When giving owner, should return filtered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + const fileFive = await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": "1234", + "metadata.parent": "/", + } + + const defaultSortBy = {uploadDate: -1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileFive._id); +}) + +test("When giving parent, should return a filtered file list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + const fileFour = await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "1234", + } + + const defaultSortBy = {uploadDate: -1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileFour._id); +}) + +test("When giving limit, should return filitered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + } + + const defaultSortBy = {uploadDate: -1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 1); + + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileThree._id); +}) + +test("When giving ascending upload date, should return filtered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + } + + const defaultSortBy = {uploadDate: 1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(3); + expect(recievedList[0]._id).toEqual(file._id); + expect(recievedList[1]._id).toEqual(fileTwo._id); + expect(recievedList[2]._id).toEqual(fileThree._id); +}) + +test("When giving decending filename, should return filtered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + } + + + const defaultSortBy = {filename: -1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(3); + expect(recievedList[0]._id).toEqual(fileThree._id); + expect(recievedList[1]._id).toEqual(file._id); + expect(recievedList[2]._id).toEqual(fileTwo._id); +}) + +test("When giving ascending filename, should return filtered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + } + + + const defaultSortBy = {filename: 1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(3); + expect(recievedList[0]._id).toEqual(fileTwo._id); + expect(recievedList[1]._id).toEqual(file._id); + expect(recievedList[2]._id).toEqual(fileThree._id); +}) + +test("When giving start at with default values, should return filtered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + "uploadDate": {$lt: new Date(fileThree.uploadDate)} + } + + const defaultSortBy = {uploadDate: -1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(2); + expect(recievedList[0]._id).toEqual(fileTwo._id); + expect(recievedList[1]._id).toEqual(file._id); +}) + +test("When giving start at with ascending data, should return filtered list", async() => { + + const userID = user._id; + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + const fileThree = await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + + const queryObj = { + "metadata.owner": userID, + "metadata.parent": "/", + "uploadDate": {$gt: new Date(fileTwo.uploadDate)} + } + + const defaultSortBy = {uploadDate: 1} + + + + const recievedList = await utilsFile.getList(queryObj, defaultSortBy, 50); + + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileThree._id); +}) + +test("When giving user and temp token, should remove temp token", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD, {expiresIn: 2}); + user.tempTokens = user.tempTokens.concat({token}) + await user.save(); + + + const recievedUser = await utilsFile.removeTempToken(user, token); + await recievedUser.save(); + const updatedUser = await User.findById(user._id); + + + expect(updatedUser.tempTokens.length).toBe(0); +}) + +test("When giving the wrong temp token, should not remove temp token", async() => { + + const token = jwt.sign({_id: user._id}, process.env.PASSWORD, {expiresIn: 2}); + user.tempTokens = user.tempTokens.concat({token}) + await user.save(); + + + const recievedUser = await utilsFile.removeTempToken(user, "1234"); + await recievedUser.save(); + const updatedUser = await User.findById(user._id); + + + expect(updatedUser.tempTokens.length).toBe(1); + expect(updatedUser.tempTokens[0].token._id).toBe(token._id) +}) + +test("When giving fileID and userID, should remove transcoded video", async() => { + + const fileID = file._id; + const userID = user._id; + await await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), "metadata.owner": userID}, + {"$set": {"metadata.transcoded": true}}) + + await utilsFile.removeTranscodeVideo(fileID, userID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.transcoded).toBe(undefined); +}) + +test("When giving wrong fileID, should not remove transcoded video", async() => { + + const fileID = file._id; + const userID = user._id; + const wrongFileID = "123456789012"; + await await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), "metadata.owner": userID}, + {"$set": {"metadata.transcoded": true}}) + + await utilsFile.removeTranscodeVideo(wrongFileID, userID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.transcoded).toBe(true); +}) + +test("When giving the wrong userID, should not remove transcoded video", async() => { + + const fileID = file._id; + const userID = user._id; + const wrongUserID = "123456789012"; + await await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), "metadata.owner": userID}, + {"$set": {"metadata.transcoded": true}}) + + await utilsFile.removeTranscodeVideo(fileID, wrongUserID); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.transcoded).toBe(true); +}) + +test("When giving userID and search query, should return file list", async() => { + + const userID = user._id; + let search = "apple"; + search = new RegExp(search, 'i') + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + const recievedList = await utilsFile.getFileSearchList(userID, fileTwo.filename); + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileTwo._id); +}) + +test("When giving wrong userID, should not return search list", async() => { + + const userID = user._id; + const wrongUserID = "123456789012" + let search = "apple"; + search = new RegExp(search, 'i') + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + const fileTwo = await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + const recievedList = await utilsFile.getFileSearchList(wrongUserID, fileTwo.filename); + + + expect(recievedList.length).toBe(0); +}) + +test("When giving fileID, userID, and title, should rename file", async() => { + + const userID = user._id; + const fileID = file._id; + const newName = "fin.mp4"; + + await utilsFile.renameFile(fileID, userID, newName); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.filename).toBe(newName); +}) + +test("When giving wrong fileID, should not rename file", async() => { + + const userID = user._id; + const fileID = file._id; + const wrongFileID = "123456789012" + const newName = "fin.mp4"; + + await utilsFile.renameFile(wrongFileID, userID, newName); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.filename).toBe(file.filename); +}) + +test("When giving the wrong userID, should not rename file", async() => { + + const fileID = file._id; + const wrongUserID = "123456789012" + const newName = "fin.mp4"; + + await utilsFile.renameFile(fileID, wrongUserID, newName); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.filename).toBe(file.filename); +}) + +test("When giving userID and parent string, should return file list by parent", async() => { + + const userID = user._id; + + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + const fileFour = await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + const recievedList = await utilsFile.getFileListByParent(userID, fileFour.metadata.parentList) + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileFour._id); +}) + +test("When giving the wrong userID, should not return file list by parent", async() => { + + const userID = user._id; + const wrongUserID = "123456789012" + + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + const fileFour = await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + const recievedList = await utilsFile.getFileListByParent(wrongUserID, fileFour.metadata.parentList) + + + expect(recievedList.length).toBe(0); +}) + +test("When giving the userID, should return file list by owner", async() => { + + const userID = user._id; + + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + const fileFive = await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + const recievedList = await utilsFile.getFileListByOwner(fileFive.metadata.owner); + + + expect(recievedList.length).toBe(1); + expect(recievedList[0]._id).toEqual(fileFive._id); +}) + +test("When giving the wrong userID, should not return file list by owner", async() => { + + const userID = user._id; + const wrongUserID = "123456789012" + + const filepath = path.join(__dirname, "../../../fixtures/media/check.svg") + const metadata = { + owner: userID, + parent: "/", + parentList: "/" + } + + const metadataDifferentParent = { + owner: userID, + parent: "1234", + parentList:"/,1234" + } + + const metadataDifferentOwner = { + owner: "1234", + parent: "/", + parentList:"/,1234" + } + + await createFile("apple.mp4", filepath, metadata, user); + await createFile("coconut.mp4", filepath, metadata, user); + await createFile("dinnerbone.mp4", filepath, metadataDifferentParent, user); + await createFile("elephant.mp4", filepath, metadataDifferentOwner, user); + const recievedList = await utilsFile.getFileListByOwner(wrongUserID); + + + expect(recievedList.length).toBe(0); +}) + +test("When giving fileID, userID, parent, and parentList, should move file", async() => { + + const fileID = file._id; + const userID = user._id; + const parent = "1234"; + const parentList = ["/", "1234"].toString(); + + + await utilsFile.moveFile(fileID, userID, parent, parentList); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + + expect(updatedFile.metadata.parent).toBe(parent); + expect(updatedFile.metadata.parentList).toBe(parentList); +}) + +test("When giving wrong userID for move file, should not remove file", async() => { + + const fileID = file._id; + const wrongUserID = "123456789012"; + const parent = "1234"; + const parentList = ["/", "1234"].toString(); + + await utilsFile.moveFile(fileID, wrongUserID, parent, parentList); + const updatedFile = await conn.db.collection("fs.files") + .findOne({_id: fileID}); + + expect(updatedFile.metadata.parent).toBe(file.metadata.parent); + expect(updatedFile.metadata.parentList).toBe(file.metadata.parentList); +}) + diff --git a/tests/db/utils/folderUtils/index.test.js b/tests/db/utils/folderUtils/index.test.js new file mode 100644 index 0000000..d53663b --- /dev/null +++ b/tests/db/utils/folderUtils/index.test.js @@ -0,0 +1,421 @@ +const User = require("../../../../src/models/user"); +const mongoose = require("../../../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../../../fixtures/createUser"); +const UtilsFolder = require("../../../../src/db/utils/folderUtils"); +const utilsFolder = new UtilsFolder(); +const Folder = require("../../../../src/models/folder"); + +let user; +let folder; + +process.env.KEY = "1234"; + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + conn.once("open", async() => { + + //user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const folderData = { + name: "bunny", + owner: user._id, + parent: "/", + parentList: ["/"] + } + + + folder = new Folder(folderData); + await folder.save(); + + done(); + + + }) + + } else { + + //user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const folderData = { + name: "bunny", + owner: user._id, + parent: "/", + parentList: ["/"] + } + + + folder = new Folder(folderData); + await folder.save(); + + done(); + } + +}) + +afterEach( async(done) => { + + await User.deleteMany({}); + await Folder.deleteMany({}); + + done(); + +}) + +test("When giving userID, and search query, should return a searched filtered folder list", async() => { + + const userID = user._id; + let search = "coco"; + search = new RegExp(search, 'i') + + const folderTwo = await new Folder({ + name: "coconut", + owner: userID, + parent: "/", + parentList: ["/"] + }) + + const folderThree = await new Folder({ + name: "dinnerbone", + owner: userID, + parent: "1234", + parentList: ["/", "1234"] + }) + + const folderFour = await new Folder({ + name: "cocoelephant", + owner: "1234", + parent: "/", + parentList: ["/"] + }) + + await folderTwo.save(); + await folderThree.save(); + await folderFour.save(); + + + + const recievedFolderList = await utilsFolder.getFolderSearchList(userID, search); + + + expect(recievedFolderList.length).toBe(1); + expect(recievedFolderList[0]._id).toEqual(folderTwo._id) +}) + +test("When giving the wrong userID, should not return search query", async() => { + + const userID = user._id; + const wrongUserID = "123456789012" + let search = "coco"; + search = new RegExp(search, 'i') + + const folderTwo = await new Folder({ + name: "coconut", + owner: userID, + parent: "/", + parentList: ["/"] + }) + + const folderThree = await new Folder({ + name: "dinnerbone", + owner: userID, + parent: "1234", + parentList: ["/", "1234"] + }) + + const folderFour = await new Folder({ + name: "cocoelephant", + owner: "1234", + parent: "/", + parentList: ["/"] + }) + + await folderTwo.save(); + await folderThree.save(); + await folderFour.save(); + + + + const recievedFolderList = await utilsFolder.getFolderSearchList(wrongUserID, search); + + + expect(recievedFolderList.length).toBe(0); +}) + +test("When giving folderID and userID, should return folder info", async() => { + + const folderID = folder._id; + const userID = user._id; + + + const recievedFolder = await utilsFolder.getFolderInfo(folderID, userID); + + + expect(recievedFolder._id).toEqual(folderID); +}) + +test("When giving the wrong folderID, should not return folder info", async() => { + + const wrongFolderID = "123456789012"; + const userID = user._id; + + + const recievedFolder = await utilsFolder.getFolderInfo(wrongFolderID, userID); + + + expect(recievedFolder).toBe(null); +}) + + +test("When giving the wrong userID, should not return folder info", async() => { + + const folderID = folder._id; + const wrongUserID = "123456789012" + + + const recievedFolder = await utilsFolder.getFolderInfo(folderID, wrongUserID); + + + expect(recievedFolder).toBe(null); +}) + +test("When giving userID, parent, and sortby, should return filtered folder list by parent", async() => { + + const userID = user._id; + const parent = "1234"; + const sortBy = {createdAt: -1} + + const folderTwo = await new Folder({ + name: "coconut", + owner: userID, + parent: "/", + parentList: ["/"] + }) + + const folderThree = await new Folder({ + name: "dinnerbone", + owner: userID, + parent: "1234", + parentList: ["/", "1234"] + }) + + const folderFour = await new Folder({ + name: "cocoelephant", + owner: "1234", + parent: "/", + parentList: ["/"] + }) + + await folderTwo.save(); + await folderThree.save(); + await folderFour.save(); + + + + const recievedFolderList = await utilsFolder.getFolderListByParent(userID, parent, sortBy); + + + expect(recievedFolderList.length).toBe(1); + expect(recievedFolderList[0]._id).toEqual(folderThree._id); +}) + +test("When giving the wrong userID, should not return folder list filtered by parent", async() => { + + const userID = user._id; + const parent = "1234"; + const sortBy = {createdAt: -1} + const wrongUserID = "123456789012" + + + const folderTwo = await new Folder({ + name: "coconut", + owner: userID, + parent: "/", + parentList: ["/"] + }) + + const folderThree = await new Folder({ + name: "dinnerbone", + owner: userID, + parent: "1234", + parentList: ["/", "1234"] + }) + + const folderFour = await new Folder({ + name: "cocoelephant", + owner: "1234", + parent: "/", + parentList: ["/"] + }) + + await folderTwo.save(); + await folderThree.save(); + await folderFour.save(); + + + + const recievedFolderList = await utilsFolder.getFolderListByParent(wrongUserID, parent, sortBy); + + + expect(recievedFolderList.length).toBe(0) +}) + +test("When giving userID, search query, and sort by, should return folder list by search (no limit)" , async() => { + + const userID = user._id; + let search = "coco"; + search = new RegExp(search, 'i') + + const folderTwo = await new Folder({ + name: "coconut", + owner: userID, + parent: "/", + parentList: ["/"] + }) + + const folderThree = await new Folder({ + name: "dinnerbone", + owner: userID, + parent: "1234", + parentList: ["/", "1234"] + }) + + const folderFour = await new Folder({ + name: "cocoelephant", + owner: "1234", + parent: "/", + parentList: ["/"] + }) + + await folderTwo.save(); + await folderThree.save(); + await folderFour.save(); + + + + const recievedFolderList = await utilsFolder.getFolderListBySearch(userID, search); + + + expect(recievedFolderList.length).toBe(1); + expect(recievedFolderList[0]._id).toEqual(folderTwo._id) + +}) + +test("When giving wrong userID, should not return folder list by search (no limit)", async() => { + + const userID = user._id; + let search = "coco"; + search = new RegExp(search, 'i') + const wrongUserID = "123456789012" + + const folderTwo = await new Folder({ + name: "coconut", + owner: userID, + parent: "/", + parentList: ["/"] + }) + + const folderThree = await new Folder({ + name: "dinnerbone", + owner: userID, + parent: "1234", + parentList: ["/", "1234"] + }) + + const folderFour = await new Folder({ + name: "cocoelephant", + owner: "1234", + parent: "/", + parentList: ["/"] + }) + + await folderTwo.save(); + await folderThree.save(); + await folderFour.save(); + + + + const recievedFolderList = await utilsFolder.getFolderListBySearch(wrongUserID, search); + + + expect(recievedFolderList.length).toBe(0); +}) + +test("When giving folderID, userID, and title, should rename folder", async() => { + + const folderID = folder._id; + const userID = user._id; + const newName = "almonds"; + + + await utilsFolder.renameFolder(folderID, userID, newName); + const updatedFolder = await Folder.findById(folderID); + + + expect(updatedFolder.name).toBe(newName); +}) + +test("When giving wrong folderID, should not rename folder", async() => { + + const folderID = folder._id; + const userID = user._id; + const newName = "almonds"; + const wrongFolderID = "123456789012" + + + await utilsFolder.renameFolder(wrongFolderID, userID, newName); + const updatedFolder = await Folder.findById(folderID); + + + expect(updatedFolder.name).toBe(folder.name); +}) + +test("When giving the wrong userID, should not rename folder", async() => { + + const folderID = folder._id; + const wrongUserID = "123456789012"; + const newName = "almonds"; + + + await utilsFolder.renameFolder(folderID, wrongUserID, newName); + const updatedFolder = await Folder.findById(folderID); + + + expect(updatedFolder.name).toBe(folder.name); +}) + +test("When giving folderID, userID, parent, and parentList, should move folder", async() => { + + const folderID = folder._id; + const userID = user._id; + const parent = "1234"; + const parentList = ["/", "1234"]; + + + await utilsFolder.moveFolder(folderID, userID, parent, parentList); + const updatedFolder = await Folder.findById(folderID); + + expect(updatedFolder.parentList.length).toBe(2); + expect(updatedFolder.parent).toBe(parent); +}) + +test("When giving the wrong userID for folder move, should not move folder", async() => { + + const folderID = folder._id; + const wrongUserID = "123456789012"; + const parent = "1234"; + const parentList = ["/", "1234"]; + + await utilsFolder.moveFolder(folderID, wrongUserID, parent, parentList); + const updatedFolder = await Folder.findById(folderID); + + expect(updatedFolder.parentList.length).toBe(1); + expect(updatedFolder.parent).toBe(folder.parent); +}) + diff --git a/tests/express-routers/file.test.js b/tests/express-routers/file.test.js new file mode 100644 index 0000000..cf3f6af --- /dev/null +++ b/tests/express-routers/file.test.js @@ -0,0 +1,569 @@ +const User = require("../../src/models/user"); +const mongoose = require("../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../fixtures/createUser"); +const createUser2 = require("../fixtures/createUser2"); +const createFile = require("../fixtures/createFile"); +const crypto = require("crypto"); +const path = require("path"); +const env = require("../../src/enviroment/env"); +const createThumbnail = require("../../src/services/FileService/utils/createThumbnail"); +const request = require("supertest"); +const jwt = require("jsonwebtoken"); +const ObjectID = require('mongodb').ObjectID +const app = require("../../server/server"); +const Folder = require("../../src/models/folder"); +const temp = require("temp").track(); +const binaryPhraser = require("superagent-binary-parser"); + +let user; +let userToken; +let user2; +let userToken2; +let file; + +process.env.KEY = "1234"; +env.key = "1234"; + + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + conn.once("open", async() => { + + + const initVect = crypto.randomBytes(16); + + // user = await createUser(); + // userToken = user.tokens[0].token; + const {user: gotUser, token: gotToken} = await createUser(); + user = gotUser; + userToken = gotToken; + + // user2 = await createUser2(); + // userToken2 = user2.tokens[0].token; + const {user: gotUser2, token: gotToken2} = await createUser2(); + user2 = gotUser2; + userToken2 = gotToken2; + + const filename = "bunny.png"; + const filepath = path.join(__dirname, "../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: "/", + parentList: "/", + "IV": initVect + } + + file = await createFile(filename, filepath, metadata, user); + + done(); + + }) + + } else { + + const initVect = crypto.randomBytes(16); + + // user = await createUser(); + // userToken = user.tokens[0].token; + const {user: gotUser, token: gotToken} = await createUser(); + user = gotUser; + userToken = gotToken; + + // user2 = await createUser2(); + // userToken2 = user2.tokens[0].token; + const {user: gotUser2, token: gotToken2} = await createUser2(); + user2 = gotUser2; + userToken2 = gotToken2; + + // user = await createUser(); + // userToken = user.tokens[0].token; + + // user2 = await createUser2(); + // userToken2 = user2.tokens[0].token; + + const filename = "bunny.png"; + const filepath = path.join(__dirname, "../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: "/", + parentList: "/", + "IV": initVect + } + + file = await createFile(filename, filepath, metadata, user); + + done(); + } + +}) + +afterEach(async(done) => { + + console.log("After each") + + //const gfs = Grid(conn.db, mongoose.mongo); + let bucket = new mongoose.mongo.GridFSBucket(conn.db); + + await User.deleteMany({}); + + const allFiles = await conn.db.collection("fs.files").find({}).toArray(); + + for (let i = 0; i < allFiles.length; i++) { + + const currentFileID = allFiles[i]._id; + await bucket.delete(ObjectID(currentFileID)); + } + + done(); +}) + +// To-do maybe + +// test("When giving user, should send transcoded video response", async() => { + +// const response = await request(app) +// .post(`/file-service/transcode-video`) +// .set("Authorization", `Bearer ${userToken}`) +// .send({file}) +// .expect(200); + +// console.log("response", response.body); +// }) + +// test("When giving wrong user for transcode video, should return 401 error", async() => { + +// const user2 = await createUser2(); +// const userToken2 = user2.tokens[0].token; + +// await request(app) +// .get(`/file-service/transcode-video`) +// .set("Authorization", `Bearer ${userToken2}`) +// .send(user) +// .expect(401); +// }) + +test("When giving ID, should send thumbnail data", async() => { + + const thumbnailFile = await createThumbnail(file, file.filename, user); + const thumbnailID = thumbnailFile.metadata.thumbnailID; + + const response = await request(app) + .get(`/file-service/thumbnail/${thumbnailID}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); + + expect(response.body.length).not.toBe(0); +}) + +test("When giving no authorization for thumbnail data, should return error", async() => { + + const thumbnailFile = await createThumbnail(file, file.filename, user); + const thumbnailID = thumbnailFile.metadata.thumbnailID; + + const response = await request(app) + .get(`/file-service/thumbnail/${thumbnailID}`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving wrong auth data for thumbnail, should return 401 error", async() => { + + + const thumbnailFile = await createThumbnail(file, file.filename, user); + const thumbnailID = thumbnailFile.metadata.thumbnailID; + + + const response = await request(app) + .get(`/file-service/thumbnail/${thumbnailID}`) + .set("Authorization", `Bearer ${userToken2}`) + .send() + .expect(401); + + expect(response.body).toEqual({}); +}) + + +// Needs Work, cannot phrase binary data? + +// test("When giving public ID, should return public file download", async() => { + +// const userID = user._id; +// const fileID = file._id.toString(); +// const token = jwt.sign({_id: userID.toString()}, env.password); +// await conn.db.collection("fs.files") +// .findOneAndUpdate({"_id": ObjectID(fileID), +// "metadata.owner": userID}, +// {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + +// const response = await request(app) +// .get(`/file-service/public/download/${fileID}`) +// .parse(binaryPhraser) +// .buffer() +// .end(); + + +// //expect(response.body._id).toEqual(fileID); + +// // console.log("public", token.token); + +// // const url = "/file-service/public/download/" + fileID; +// // console.log("url", url); +// // const response = await request(app).get(url) +// // .set("Authorization", `Bearer ${userToken}`) +// // .set('content-type', 'application/octet-stream') +// // .send() +// // .expect(203) + +// // const writeStream = temp.createWriteStream(); + +// //response.pipe(writeStream); +// }) + +test("When giving public id, should return public info", async() => { + + const userID = user._id; + const fileID = file._id.toString(); + const token = jwt.sign({_id: userID.toString()}, env.password).toString(); + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + const response = await request(app) + .get(`/file-service/public/info/${fileID}/${token}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); + + expect(response.body._id).toEqual(fileID); +}) + +test("When giving ID for non public file, should return 404 error", async() => { + + const userID = user._id; + const fileID = file._id.toString(); + const token = jwt.sign({_id: userID.toString()}, env.password).toString(); + const response = await request(app) + .get(`/file-service/public/info/${fileID}/${token}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(404); + + expect(response.data).toBe(undefined); +}) + +test("When giving fileID, should return file info", async() => { + + const fileID = file._id; + + const response = await request(app) + .get(`/file-service/info/${fileID}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); + + expect(response.body._id).toEqual(fileID.toString()); +}) + +test("When not authorized for file info, should return 401 error", async() => { + + const fileID = file._id; + + const response = await request(app) + .get(`/file-service/info/${fileID}`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving wrong auth token for file info, should return 404 error", async() => { + + const fileID = file._id; + + const response = await request(app) + .get(`/file-service/info/${fileID}`) + .set("Authorization", `Bearer ${userToken2}`) + .send() + .expect(404); + + expect(response.body).toEqual({}); +}) + +test("When giving the wrong tempToken for public file info, should return 404 error", async() => { + + const userID = user._id; + const fileID = file._id.toString(); + const token = jwt.sign({_id: userID.toString()}, env.password).toString(); + const wrongToken = "12345" + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + const response = await request(app) + .get(`/file-service/public/info/${fileID}/${wrongToken}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(404); + + expect(response.body).toEqual({}); +}) + +test("When giving authorization, should get user quicklist", async() => { + + const response = await request(app) + .get(`/file-service/quick-list`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); + + expect(response.body.length).toBe(1); +}) + +test("When giving no authoization for quicklist, should return 401 error", async() => { + + const response = await request(app) + .get(`/file-service/quick-list`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving default query, should return file list", async() => { + + const response = await request(app) + .get(`/file-service/list`) + .set("Authorization", `Bearer ${userToken}`) + .send({}) + .expect(200); + + expect(response.body.length).toBe(1); +}) + +test("When giving no authoization for file list, should return 401 error", async() => { + + const response = await request(app) + .get(`/file-service/list`) + .send({}) + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving wrong auth for list, should return empty list", async() => { + + const response = await request(app) + .get(`/file-service/list`) + .set("Authorization", `Bearer ${userToken2}`) + .send({}) + .expect(200); + + expect(response.body.length).toBe(0); +}) + +test("When autheniticated should generate download token", async() => { + + await request(app) + .get(`/file-service/download/get-token`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When not authenticated, should not generate download token", async() => { + + await request(app) + .get(`/file-service/download/get-token`) + .send() + .expect(401); +}) + +test("When authenticated and cookie should generate video token", async() => { + + await request(app) + .get(`/file-service/download/get-token-video`) + .set("Authorization", `Bearer ${userToken}`) + .set("uuid", 1234) + .send() + .expect(200); +}) + +test("When not authenticated, should not generate video token", async() => { + + await request(app) + .get(`/file-service/download/get-token-video`) + .set("uuid", 1234) + .send() + .expect(401); +}) + +test("When not giving a Cookie, should not generate video token", async() => { + + await request(app) + .get(`/file-service/download/get-token-video`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(401); +}) + +test("When giving tempToken, should remove temp token", async() => { + + const token = await request(app) + .get(`/file-service/download/get-token-video`) + .set("Authorization", `Bearer ${userToken}`) + .set("uuid", 1234) + .send() + .expect(200); + + await request(app) + .delete(`/file-service/remove/token-video/${token}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); + +}) + +test("When not authorized, should not remove temp token", async() => { + + const token = await request(app) + .get(`/file-service/download/get-token-video`) + .set("Authorization", `Bearer ${userToken}`) + .set("uuid", 1234) + .send() + .expect(200); + + await request(app) + .delete(`/file-service/remove/token-video/${token}`) + .send() + .expect(401); +}) + +test("When authorized should return suggested list", async() => { + + await request(app) + .get(`/file-service/suggested-list`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When not authorized should not return suggested list", async() => { + + const response =await request(app) + .get(`/file-service/suggested-list`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving fileID and title, should rename file", async() => { + + const fileID = file._id; + + await request(app) + .patch(`/file-service/rename`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: fileID, + title: "new name" + }) + .expect(200); +}) + +test("When not authoized for rename file, should return 401 error", async() => { + + const fileID = file._id; + + await request(app) + .patch(`/file-service/rename`) + .send({ + id: fileID, + title: "new name" + }) + .expect(401); +}) + +test("When giving fileID and parent, should move file", async() => { + + const fileID = file._id; + const userID = user._id; + + const folder = new Folder({ + owner: userID, + name: "folder", + parent: "/", + parentList: ["/"] + }) + + await folder.save(); + + const folderID = folder._id; + + await request(app) + .patch(`/file-service/move`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: fileID, + parent: folderID + }) + .expect(200); +}) + +test("When giving a parentID that does not exist, should return 500 error", async() => { + + const fileID = file._id; + + await request(app) + .patch(`/file-service/move`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: fileID, + parent: "123456789012" + }) + .expect(404); +}) + +test("When giving fileID should remove file", async() => { + + const fileID = file._id; + + await request(app) + .delete(`/file-service/remove`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: fileID, + }) + .expect(200); +}) + +test("When giving fileID that does not exist for remove file, should return 404 error", async() => { + + const fileID = "123456789012"; + + await request(app) + .delete(`/file-service/remove`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: fileID, + }) + .expect(404); +}) \ No newline at end of file diff --git a/tests/express-routers/folder.test.js b/tests/express-routers/folder.test.js new file mode 100644 index 0000000..c6ddb87 --- /dev/null +++ b/tests/express-routers/folder.test.js @@ -0,0 +1,317 @@ +const User = require("../../src/models/user"); +const mongoose = require("../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../fixtures/createUser"); +const createUser2 = require("../fixtures/createUser2"); +const createFile = require("../fixtures/createFile"); +const crypto = require("crypto"); +const path = require("path"); +const env = require("../../src/enviroment/env"); +const createThumbnail = require("../../src/services/FileService/utils/createThumbnail"); +const request = require("supertest"); +const jwt = require("jsonwebtoken"); +const ObjectID = require('mongodb').ObjectID +const app = require("../../server/server"); +const Folder = require("../../src/models/folder"); +const temp = require("temp").track(); +const binaryPhraser = require("superagent-binary-parser"); + +let user; +let userToken; +let user2; +let userToken2; +let file; +let folder; + +process.env.KEY = "1234"; +env.key = "1234"; + + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + conn.on("open", async() => { + + // user = await createUser(); + // userToken = user.tokens[0].token; + + // user2 = await createUser2(); + // userToken2 = user2.tokens[0].token; + + const {user: gotUser, token: gotToken} = await createUser(); + user = gotUser; + userToken = gotToken; + + const {user: gotUser2, token: gotToken2} = await createUser2(); + user2 = gotUser2; + userToken2 = gotToken2; + + const folderData = { + name: "bunny", + parent: "/", + owner: user._id, + parentList: ["/"] + } + + folder = new Folder(folderData); + await folder.save(); + + done(); + + }) + + } else { + + // user = await createUser(); + // userToken = user.tokens[0].token; + + // user2 = await createUser2(); + // userToken2 = user2.tokens[0].token; + const {user: gotUser, token: gotToken} = await createUser(); + user = gotUser; + userToken = gotToken; + + const {user: gotUser2, token: gotToken2} = await createUser2(); + user2 = gotUser2; + userToken2 = gotToken2; + + + const folderData = { + name: "bunny", + parent: "/", + owner: user._id, + parentList: ["/"] + } + + folder = new Folder(folderData); + await folder.save(); + + done(); + + + } +}) + +afterEach(async(done) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db); + + await User.deleteMany({}); + await Folder.deleteMany({}); + + const allFiles = await conn.db.collection("fs.files").find({}).toArray(); + + for (let i = 0; i < allFiles.length; i++) { + + const currentFileID = allFiles[i]._id; + await bucket.delete(ObjectID(currentFileID)); + } + + done(); +}) + +test("When giving folderID, should return folder info", async() => { + + const folderID = folder._id; + + await request(app) + .get(`/folder-service/info/${folderID}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When not authorized for folder info, should return 401 error", async() => { + + const folderID = folder._id; + + await request(app) + .get(`/folder-service/info/${folderID}`) + .send() + .expect(401); +}) + +test("When giving wrong authorization for folder info, should return 404 error", async() => { + + const folderID = folder._id; + + await request(app) + .get(`/folder-service/info/${folderID}`) + .set("Authorization", `Bearer ${userToken2}`) + .send() + .expect(404); +}) + +test("When giving folderID, should return subfolder list", async() => { + + const folderID = folder._id; + + await request(app) + .get(`/folder-service/subfolder-list?id=${folderID}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When giving no folderID for subfolder list, should return 404 error", async() => { + + await request(app) + .get(`/folder-service/subfolder-list`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(404); +}) + +test("When giving no authorization for subfolder list, should return 401 error", async() => { + + const folderID = folder._id; + + await request(app) + .get(`/folder-service/subfolder-list?id=${folderID}`) + .send() + .expect(401); +}) + +test("When giving wrong authorization for subfolder list, should return 404 error", async() => { + + + const folderID = folder._id; + + await request(app) + .get(`/folder-service/subfolder-list?id=${folderID}`) + .set("Authorization", `Bearer ${userToken2}`) + .send() + .expect(404); + +}) + +test("When giving default query, should return folder list", async() => { + + const query = {} + + await request(app) + .get(`/folder-service/list?query=${query}`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When giving no authorization for folder list, should return 401 error", async() => { + + const query = {} + + await request(app) + .get(`/folder-service/list?query=${query}`) + .send() + .expect(401); +}) + +test("When giving ID and parent, should move folder", async() => { + + const folderData = { + name: "bunny", + parent: "/", + owner: user._id, + parentList: ["/"] + } + + const folder2 = new Folder(folderData); + await folder2.save(); + const parentID = folder2._id; + + const folderID = folder._id; + + await request(app) + .patch(`/folder-service/move`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: folderID, + parent: parentID + }) + .expect(200); +}) + +test("When giving a folder id that does not exist for move folder, should return 500 error", async() => { + + const folderData = { + name: "bunny", + parent: "/", + owner: user._id, + parentList: ["/"] + } + + const folder2 = new Folder(folderData); + await folder2.save(); + const parentID = folder2._id; + + await request(app) + .patch(`/folder-service/move`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: "1234", + parent: parentID + }) + .expect(500); + +}) + +test("When giving parent id that does not exist for move folder, should return 500 error", async() => { + + const folderID = folder._id; + + await request(app) + .patch(`/folder-service/move`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: folderID, + parent: "1234" + }) + .expect(500); +}) + +test("When giving folder id and title, should rename folder", async() => { + + const folderID = folder._id; + const title = "new name"; + + await request(app) + .patch(`/folder-service/rename`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: folderID, + title + }) + .expect(200); +}) + +test("When giving not authorization for rename folder, should return 401 error", async() => { + + const folderID = folder._id; + const title = "new name"; + + await request(app) + .patch(`/folder-service/rename`) + .send({ + id: folderID, + title + }) + .expect(401); +}) + +test("When giving id for folder that does not exist for rename, should return 404 error", async() => { + + + const wrongFolderID = "123456789012"; + const title = "new name"; + + await request(app) + .patch(`/folder-service/rename`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + id: wrongFolderID, + title + }) + .expect(404); + +}) \ No newline at end of file diff --git a/tests/express-routers/user.test.js b/tests/express-routers/user.test.js new file mode 100644 index 0000000..58a240e --- /dev/null +++ b/tests/express-routers/user.test.js @@ -0,0 +1,222 @@ +const User = require("../../src/models/user"); +const mongoose = require("../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../fixtures/createUser"); +const app = require("../../server/server"); +const request = require("supertest"); +const env = require("../../src/enviroment/env") + +let user; +let userToken; + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + // user = await createUser(); + // userToken = user.tokens[0].token; + const {user: gotUser, token: gotToken} = await createUser(); + user = gotUser; + userToken = gotToken; + + done() + + } else { + + // user = await createUser(); + // userToken = user.tokens[0].token; + const {user: gotUser, token: gotToken} = await createUser(); + user = gotUser; + userToken = gotToken; + + done(); + } +}) + +afterEach(async(done) => { + + await User.deleteMany({}); + done(); +}) + +test("When authorized, should return user, with no sensitive data", async() => { + + const response = await request(app) + .get(`/user-service/user`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); + + expect(response.body.password).toEqual(undefined); + expect(response.body.privateKey).toEqual(undefined); + expect(response.body.publicKey).toEqual(undefined); + expect(response.body.tokens).toEqual(undefined); + expect(response.body.tempTokens).toEqual(undefined); + expect(response.body.email).not.toEqual(undefined); +}) + +test("When not authorized should return 401 for user", async() => { + + const response = await request(app) + .get(`/user-service/user`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving credentials, should login user, and not return sensitive info", async() => { + + const response = await request(app) + .post(`/user-service/login`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + email: user.email, + password: "12345678" + }) + .expect(200); + + expect(response.body.password).toEqual(undefined); + expect(response.body.privateKey).toEqual(undefined); + expect(response.body.publicKey).toEqual(undefined); + expect(response.body.tokens).toEqual(undefined); + expect(response.body.tempTokens).toEqual(undefined); +}) + +test("When giving wrong creditentials, should return 500 error", async() => { + + const response = await request(app) + .post(`/user-service/login`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + email: user.email, + password: "1454524" + }) + .expect(500); + + expect(response.body).toEqual({}); +}) + +test("When authorized should logout user", async() => { + + await request(app) + .post(`/user-service/logout`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When not authorized for logout, should return 401 error", async() => { + + const response = await request(app) + .post(`/user-service/logout`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When authoized should logout all sessions of a user", async() => { + + await request(app) + .post(`/user-service/logout-all`) + .set("Authorization", `Bearer ${userToken}`) + .send() + .expect(200); +}) + +test("When not authorized for logout all, should return 401 error", async() => { + + const response = await request(app) + .post(`/user-service/logout-all`) + .send() + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) + +test("When giving user data, should create account without returning sensitive data", async() => { + + const response = await request(app) + .post(`/user-service/create`) + .send({ + email: "test55@gmail.com", + password: "12345678" + }) + .expect(201); + + expect(response.body.password).toEqual(undefined); + expect(response.body.privateKey).toEqual(undefined); + expect(response.body.publicKey).toEqual(undefined); + expect(response.body.tokens).toEqual(undefined); + expect(response.body.tempTokens).toEqual(undefined); +}) + +test("When env variable is set to block accounts, should return 401 error", async() => { + + env.createAcctBlocked = true; + + await request(app) + .post(`/user-service/create`) + .send({ + email: "test55@gmail.com", + password: "12345678" + }) + .expect(401); + + env.createAcctBlocked = false; +}) + +test("When giving old password, and new password, should change password", async() => { + + const oldPassword = "12345678"; + const newPassword = "987654321"; + + await request(app) + .post(`/user-service/change-password`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + oldPassword, + newPassword + }) + .expect(200); +}) + +test("When giving wrong old password for change password, should return 500 error", async() => { + + const wrongOldPassword = "12342345"; + const newPassword = "987654321"; + + await request(app) + .post(`/user-service/change-password`) + .set("Authorization", `Bearer ${userToken}`) + .send({ + wrongOldPassword, + newPassword + }) + .expect(500); +}) + +test("When not authorized for change password, should return 401 error", async() => { + + const oldPassword = "12345678"; + const newPassword = "987654321"; + + const response = await request(app) + .post(`/user-service/change-password`) + .send({ + oldPassword, + newPassword + }) + .expect(401); + + expect(response.body).toEqual({ + "error": "Error Authenticating", + }); +}) \ No newline at end of file diff --git a/tests/fixtures/createFile.js b/tests/fixtures/createFile.js new file mode 100644 index 0000000..3dc7d49 --- /dev/null +++ b/tests/fixtures/createFile.js @@ -0,0 +1,32 @@ +const mongoose = require("../../src/db/mongoose"); +const conn = mongoose.connection; +const crypto = require("crypto"); +const fs = require("fs"); + +const createFile = (filename, filepath, metadata, user) => { + return new Promise((resolve, reject) => { + + const password = user.getEncryptionKey(); //process.env.KEY; + + const initVect = metadata.IV || crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const cipher = crypto.createCipheriv('aes256', CIPHER_KEY, initVect); + + const fileReadStream = fs.createReadStream(filepath); + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + const uploadStream = bucket.openUploadStream(filename, {metadata}); + + fileReadStream.pipe(cipher).pipe(uploadStream).on("finish", (file) => { + + resolve(file); + }); + }) +} + +module.exports = createFile; \ No newline at end of file diff --git a/tests/fixtures/createUser.js b/tests/fixtures/createUser.js new file mode 100644 index 0000000..6397017 --- /dev/null +++ b/tests/fixtures/createUser.js @@ -0,0 +1,31 @@ +const jwt = require("jsonwebtoken"); +const mongoose = require("../../src/db/mongoose"); +const User = require("../../src/models/user"); +const env = require("../../src/enviroment/env"); + +const createUser = async() => { + + env.key = "1234"; + env.password = "1234"; + process.env.KEY = "1234"; + process.env.PASSWORD = "1234"; + + const userID = new mongoose.Types.ObjectId(); + const userData = { + _id: userID, + name: "Test User", + email: "test2@test.com", + password: "12345678", + } + + let user = new User(userData); + await user.save(); + + await user.generateEncryptionKeys(); + + const token = await user.generateAuthToken(); + + return {user, token}; +} + +module.exports = createUser; \ No newline at end of file diff --git a/tests/fixtures/createUser2.js b/tests/fixtures/createUser2.js new file mode 100644 index 0000000..03e75a9 --- /dev/null +++ b/tests/fixtures/createUser2.js @@ -0,0 +1,31 @@ +const jwt = require("jsonwebtoken"); +const mongoose = require("../../src/db/mongoose"); +const User = require("../../src/models/user"); +const env = require("../../src/enviroment/env"); + +const createUser2 = async() => { + + env.key = "1234"; + env.password = "1234"; + process.env.KEY = "1234"; + process.env.PASSWORD = "1234"; + + const userID = new mongoose.Types.ObjectId(); + const userData = { + _id: userID, + name: "Test User", + email: "test3@test.com", + password: "12345678", + } + + let user = new User(userData); + await user.save(); + + await user.generateEncryptionKeys(); + + const token = await user.generateAuthToken(); + + return {user, token}; +} + +module.exports = createUser2; \ No newline at end of file diff --git a/tests/fixtures/media/big_buck_bunny.mp4 b/tests/fixtures/media/big_buck_bunny.mp4 new file mode 100644 index 0000000..81d11df Binary files /dev/null and b/tests/fixtures/media/big_buck_bunny.mp4 differ diff --git a/tests/fixtures/media/check.svg b/tests/fixtures/media/check.svg new file mode 100644 index 0000000..36017ae --- /dev/null +++ b/tests/fixtures/media/check.svg @@ -0,0 +1,23 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/tests/fixtures/media/error.png b/tests/fixtures/media/error.png new file mode 100644 index 0000000..e9ef165 Binary files /dev/null and b/tests/fixtures/media/error.png differ diff --git a/tests/fixtures/media/folder.png b/tests/fixtures/media/folder.png new file mode 100644 index 0000000..3d49f45 Binary files /dev/null and b/tests/fixtures/media/folder.png differ diff --git a/tests/services/FileService/index.test.js b/tests/services/FileService/index.test.js new file mode 100644 index 0000000..b5fc32e --- /dev/null +++ b/tests/services/FileService/index.test.js @@ -0,0 +1,376 @@ +const User = require("../../../src/models/user"); +const mongoose = require("../../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../../fixtures/createUser"); +const path = require("path"); +const createFile = require("../../fixtures/createFile"); +const crypto = require("crypto"); +const createThumbnail = require("../../../src/services/FileService/utils/createThumbnail"); +const env = require("../../../src/enviroment/env"); +const jwt = require("jsonwebtoken"); +const ObjectID = require('mongodb').ObjectID +const Folder = require("../../../src/models/folder"); +const FileService = require("../../../src/services/FileService"); +const fileService = new FileService(); + +let user; +let file; + +process.env.KEY = "1234"; +env.key = "1234"; + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + conn.once("open", async() => { + + const initVect = crypto.randomBytes(16); + + // user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const filename = "bunny.png"; + const filepath = path.join(__dirname, "../../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: "/", + parentList: "/", + "IV": initVect + } + + file = await createFile(filename, filepath, metadata, user); + + done(); + }) + } else { + + const initVect = crypto.randomBytes(16); + //user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const filename = "bunny.png"; + const filepath = path.join(__dirname, "../../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: "/", + parentList: "/", + "IV": initVect + } + + file = await createFile(filename, filepath, metadata, user); + + done(); + } +}) + + +afterEach( async(done) => { + + //const gfs = Grid(conn.db, mongoose.mongo); + let bucket = new mongoose.mongo.GridFSBucket(conn.db); + + await User.deleteMany({}); + await Folder.deleteMany({}); + + const allFiles = await conn.db.collection("fs.files").find({}).toArray(); + + for (let i = 0; i < allFiles.length; i++) { + + const currentFileID = allFiles[i]._id; + await bucket.delete(ObjectID(currentFileID)); + } + + done(); + +}) + +test("When giving user, and thumbnail id, should get thumbnail" , async() => { + + const thumbnailFile = await createThumbnail(file, file.filename, user); + const thumbnailID = thumbnailFile.metadata.thumbnailID; + + + const recievedThumbnail = await fileService.getThumbnail(user, thumbnailID); + + + expect(recievedThumbnail.buffer.length).not.toBe(0); +}) + +test("When giving the wrong user for thumbnail, should throw not authorized error", async() => { + + const thumbnailFile = await createThumbnail(file, file.filename, user); + const thumbnailID = thumbnailFile.metadata.thumbnailID; + const wrongUser = { + _id: "123456789012" + } + + await expect(fileService.getThumbnail(wrongUser, thumbnailID)).rejects.toThrow(); +}) + +test("When giving file, should remove one time link", async() => { + + await fileService.removePublicOneTimeLink(file); +}) + +test("When giving userID, and fileID, should remove public link", async() => { + + const userID = user._id; + const fileID = file._id; + + await fileService.removeLink(userID, fileID); +}) + +test("When giving the wrong userID to remove public link, should throw not found error", async() => { + + const wrongUserID = "123456789012"; + const fileID = file._id; + + await expect(fileService.removeLink(wrongUserID, fileID)).rejects.toThrow(); +}) + +test("When giving user, and fileID, should make file public and return token", async() => { + + const fileID = file._id; + + const recievedToken = await fileService.makePublic(user, fileID); + + expect(recievedToken.length).not.toBe(0); +}) + +test("When giving the wrong user to make file public, should throw not found error", async() => { + + const wrongUser = { + _id: "123456789012" + } + const fileID = file._id; + + await expect(fileService.makePublic(wrongUser, fileID)).rejects.toThrow(); +}) + +test("When giving fileID, should return file info if public", async() => { + + const userID = user._id; + const fileID = file._id; + const token = jwt.sign({_id: userID.toString()}, env.password); + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + + const receivedFile = await fileService.getPublicInfo(fileID, token); + + expect(receivedFile).not.toBe(null); + expect(receivedFile).not.toBe(undefined); +}) + +test("When giving fileID of a file thats not public, should throw not found error", async() => { + + const fileID = file._id; + + await expect(fileService.getPublicInfo(fileID)).rejects.toThrow(); +}) + +test("When giving wrong tempToken for public file, should throw not found error", async() => { + + const userID = user._id; + const fileID = file._id; + const token = jwt.sign({_id: userID.toString()}, env.password); + const wrongTempToken = "12345"; + await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": ObjectID(fileID), + "metadata.owner": userID}, + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + + await expect(fileService.getPublicInfo(fileID, wrongTempToken)).rejects.toThrow(); +}) + +test("When giving userID and fileID, should make one time public link", async() => { + + const userID = user._id; + const fileID = file._id; + + await fileService.makeOneTimePublic(userID, fileID); +}) + +test("When giving wrong userID for one time public link, should throw not found error", async() => { + + const wrongUserID = "123456789012"; + const fileID = file._id; + + await expect(fileService.makeOneTimePublic(wrongUserID, fileID)).rejects.toThrow(); +}) + +test("When giving userID and fileID, should return file info", async() => { + + const userID = user._id; + const fileID = file._id; + + const receivedFile = await fileService.getFileInfo(userID, fileID); + + expect(receivedFile).not.toBe(null); + expect(receivedFile).not.toBe(undefined); +}) + +test("When giving the wrong userID for file info, should throw not found error", async() => { + + const wrongUserID = "123456789012"; + const fileID = file._id; + + await expect(fileService.getFileInfo(wrongUserID, fileID)).rejects.toThrow(); +}) + +test("When giving userID, should return quicklist", async() => { + + const userID = user._id; + + const receivedQuickList = await fileService.getQuickList(userID); + + expect(receivedQuickList.length).toBe(1); +}) + +test("When giving wrong userID for quicklist, should return list with 0 length", async() => { + + const wrongUserID = "123456789012"; + + const receivedQuickList = await fileService.getQuickList(wrongUserID); + + expect(receivedQuickList.length).toBe(0); +}) + +test("When giving userID, and default query, should get file list", async() => { + + const userID = user._id; + + const receivedFileList = await fileService.getList(userID, {}); + + expect(receivedFileList.length).toBe(1); +}) + +test("When giving wrong userID for file list, should return list with 0 length", async() => { + + const wrongUserID = "123456789012"; + + const receivedList = await fileService.getList(wrongUserID, {}); + + expect(receivedList.length).toBe(0); +}) + +test("When giving user, should create download token", async() => { + + const recievedToken = await fileService.getDownloadToken(user); + + expect(recievedToken.length).not.toBe(0); +}) + +test("When giving user and cookie, should create video download token", async() => { + + const recievedToken = await fileService.getDownloadTokenVideo(user, {}); + + expect(recievedToken.length).not.toBe(0); +}) + +test("When giving user and tempToken, should remove temp token", async() => { + + const tempToken = await user.generateTempAuthToken(); + + await fileService.removeTempToken(user, tempToken); +}) + +test("When giving userID and search query, should return suggested list", async() => { + + const userID = user._id; + const search = "bunn"; + + const {fileList} = await fileService.getSuggestedList(userID, search); + + expect(fileList.length).toBe(1); +}) + +test("When giving wrong userID for suggested list, should return list with length of 0", async() => { + + const wrongUserID = "123456789012"; + const search = "bunn"; + + const {fileList} = await fileService.getSuggestedList(wrongUserID, search); + + expect(fileList.length).toBe(0); +}) + +test("When giving userID, fileID, and title, should rename file", async() => { + + const userID = user._id; + const fileID = file._id; + const title = "new name"; + + await fileService.renameFile(userID, fileID, title); +}) + +test("When giving the wrong userID for rename file, should throw not found error", async() => { + + const wrongUserID = "123456789012"; + const fileID = file._id; + const title = "new name"; + + await expect(fileService.renameFile(wrongUserID, fileID, title)).rejects.toThrow(); +}) + +test("When giving the userID, fileID, and parentID should move file", async() => { + + const userID = user._id; + const fileID = file._id; + const folder = new Folder({ + owner: user._id, + parent: "/", + parentList: ["/"], + name: "folder" + }); + + await folder.save(); + + await fileService.moveFile(userID, fileID, folder._id) +}) + +test("When giving the wrong userID for move file should throw a not found error", async() => { + + const wrongUserID = "123456789012"; + const fileID = file._id; + const folder = new Folder({ + owner: user._id, + parent: "/", + parentList: ["/"], + name: "folder" + }); + + await folder.save(); + + await expect(fileService.moveFile(wrongUserID, fileID, folder._id)).rejects.toThrow(); +}) + +test("When giving an non existing folderID for file move, should throw not found error", async() => { + + const userID = user._id; + const fileID = file._id; + const wrongFolderID = "123456789012"; + + await expect(fileService.moveFile(userID, fileID, wrongFolderID)).rejects.toThrow(); +}) + +// test("When giving userID, and fileID, should remove file", async() => { + +// const userID = user._id; +// const fileID = file._id; + +// await fileService.deleteFile(userID, fileID); +// }) + +// test("When giving the wrong userID for delete file, should throw not found error", async() => { + +// const wrongUserID = "123456789012"; +// const fileID = file._id; + +// await expect(fileService.deleteFile(wrongUserID, fileID)).rejects.toThrow(); +// }) \ No newline at end of file diff --git a/tests/services/FolderService/index.test.js b/tests/services/FolderService/index.test.js new file mode 100644 index 0000000..80261b7 --- /dev/null +++ b/tests/services/FolderService/index.test.js @@ -0,0 +1,310 @@ +const User = require("../../../src/models/user"); +const createUser = require("../../fixtures/createUser"); +const Folder = require("../../../src/models/folder"); +const mongoose = require("../../../src/db/mongoose"); +const conn = mongoose.connection; +const crypto = require("crypto"); +const createFile = require("../../fixtures/createFile"); +const path = require("path"); +const ObjectID = require('mongodb').ObjectID +const FolderService = require("../../../src/services/FolderService"); +const folderService = new FolderService(); + +let user; +let folder; + +beforeEach(async(done) => { + + if (conn.readyState === 0) { + + conn.once("open", async() => { + + const {user: gotUser} = await createUser(); + user = gotUser; + + const folderData = { + name: "bunny", + parent: "/", + owner: user._id, + parentList: ["/"] + } + + folder = new Folder(folderData); + await folder.save(); + + done(); + }) + + } else { + + // user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + + const folderData = { + name: "bunny", + parent: "/", + owner: user._id, + parentList: ["/"] + } + + folder = new Folder(folderData); + await folder.save(); + + done(); + } + +}) + +afterEach(async(done) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db); + + await User.deleteMany({}); + await Folder.deleteMany({}); + + // const gfs = Grid(conn.db, mongoose.mongo); + + const allFiles = await conn.db.collection("fs.files").find({}).toArray(); + + for (let i = 0; i < allFiles.length; i++) { + + const currentFileID = allFiles[i]._id; + await bucket.delete(ObjectID(currentFileID)); + } + + done(); +}) + +test("When giving userID and folderID, should return folder info" , async() => { + + const userID = user._id; + const folderID = folder._id; + + const receivedFolder = await folderService.getFolderInfo(userID, folderID); + + expect(receivedFolder._id).toEqual(folderID); +}) + +test("When giving the wrong userID for folder info, should return not found error", async() => { + + const wrongUserID = "123456789012"; + const folderID = folder._id; + + await expect(folderService.getFolderInfo(wrongUserID, folderID)).rejects.toThrow(); +}) + +test("When giving userID and folderID, should return subfolder list", async() => { + + const userID = user._id; + const folderID = folder._id; + + const {folderNameList, folderIDList} = await folderService.getFolderSublist(userID, folderID); + + expect(folderNameList.length).toBe(2); + expect(folderIDList.length).toBe(2); +}) + +test("When giving the wrong userID for subfolder list, should throw not found error", async() => { + + const wrongUserID = "123456789012"; + const folderID = folder._id; + + await expect(folderService.getFolderSublist(wrongUserID, folderID)).rejects.toThrow(); +}) + +test("When giving userID and query with default values, should return folder list", async() => { + + const userID = user._id; + + + const receivedFolderList = await folderService.getFolderList(userID, {}); + + + expect(receivedFolderList.length).toBe(1); +}) + +test("When giving wrong userID for folder list, should return empty list", async() => { + + const wrongUserID = "123456789012"; + + + const receivedFolderList = await folderService.getFolderList(wrongUserID, {}); + + + expect(receivedFolderList.length).toBe(0); +}) + +test("When giving userID, folderID, and title, should rename folder", async() => { + + const userID = user._id; + const folderID = folder._id; + const title = "coconot"; + + await folderService.renameFolder(userID, folderID, title); + const updatedFolder = await Folder.findById(folderID); + + expect(updatedFolder.name).toBe(title); +}) + +test("When giving wrong userID for rename folder, should throw not found error", async() => { + + const wrongUserID = "123456789012"; + const folderID = folder._id; + const title = "coconot"; + + + await expect(folderService.renameFolder(wrongUserID, folderID, title)).rejects.toThrow(); +}) + +test("When giving userID, folderID, and parentID, should move folder", async() => { + + const newFolder = new Folder({ + name: "new folder", + owner: user._id, + parent: "1234", + parentList: ["/", "1234"]  + }) + + await newFolder.save(); + + const userID = user._id; + const folderID = folder._id; + const parentID = newFolder._id; + + + await folderService.moveFolder(userID, folderID, parentID); + const updatedFolder = await Folder.findById(folderID); + + + expect(updatedFolder.parent.toString()).toBe(parentID.toString()); +}) + +test("When giving the wrong userID for move folder, should throw new found error", async() => { + + const newFolder = new Folder({ + name: "new folder", + owner: user._id, + parent: "1234", + parentList: ["/", "1234"]  + }) + + await newFolder.save(); + + const wrongUserID = "123456789012"; + const folderID = folder._id; + const parentID = newFolder._id; + + await expect(folderService.moveFolder(wrongUserID, folderID, parentID)).rejects.toThrow() +}) + +test("When giving a parentID that does not exist for move folder, should throw not found error", async() => { + + const userID = user._id; + const folderID = folder._id; + const wrongParentID = "123456789012"; + + await expect(folderService.moveFolder(userID, folderID, wrongParentID)).rejects.toThrow() +}) + +test("When giving userID, folderID, and parentID, for folder with subitems, should move all items", async() => { + + const userID = user._id; + + const folderOne = new Folder({ + name: "new folder", + owner: user._id, + parent: "/", + parentList: ["/"]  + }) + + const folderID = folderOne._id; + + await folderOne.save(); + + const folderTwo = new Folder({ + name: "new folder 2", + owner: user._id, + parent: folderOne._id, + parentList: ["/", folderOne._id.toString()]  + }) + + await folderTwo.save(); + + const folderThree = new Folder({ + name: "new folder3 ", + owner: user._id, + parent: folderTwo._id, + parentList: ["/", folderOne._id.toString(), folderTwo._id.toString()]  + }) + + await folderThree.save(); + + const folderFour = new Folder({ + name: "new folder 4", + owner: user._id, + parent: "/", + parentList: ["/"]  + }) + + const parentID = folderFour._id; + + await folderFour.save(); + + process.env.KEY = "1234"; + + const initVect = crypto.randomBytes(16); + const filename = "bunny.png"; + const filepath = path.join(__dirname, "../../fixtures/media/check.svg") + const metadata = { + owner: user._id, + parent: folderOne._id.toString(), + parentList: "/,"+folderOne._id.toString(), + "IV": initVect + } + + const fileOne = await createFile(filename, filepath, metadata, user); + + const metadataTwo = { + owner: user._id, + parent: folderTwo._id.toString(), + parentList: "/,"+folderTwo._id.toString(), + "IV": initVect + } + + const fileTwo = await createFile(filename, filepath, metadataTwo, user); + + const metadataThree = { + owner: user._id, + parent: folderThree._id.toString(), + parentList: "/,"+folderThree._id.toString(), + "IV": initVect + } + + const fileThree = await createFile(filename, filepath, metadataThree, user); + + + + await folderService.moveFolder(userID, folderID, parentID); + const updatedFolderOne = await Folder.findById(folderOne._id); + const updatedFolderTwo = await Folder.findById(folderTwo._id); + const updatedFolderThree = await Folder.findById(folderThree._id); + + + + expect(updatedFolderOne.parent.toString()).toBe(parentID.toString()); + expect(updatedFolderOne.parentList[0].toString()).toBe("/"); + expect(updatedFolderOne.parentList[1].toString()).toBe(parentID.toString()); + expect(updatedFolderOne.parentList.length).toBe(2); + + expect(updatedFolderTwo.parentList[0].toString()).toBe("/"); + expect(updatedFolderTwo.parentList[1].toString()).toBe(parentID.toString()); + expect(updatedFolderTwo.parentList[2].toString()).toBe(folderOne._id.toString()); + expect(updatedFolderTwo.parentList.length).toBe(3); + + expect(updatedFolderThree.parentList[0].toString()).toBe("/"); + expect(updatedFolderThree.parentList[1].toString()).toBe(parentID.toString()); + expect(updatedFolderThree.parentList[2].toString()).toBe(folderOne._id.toString()); + expect(updatedFolderThree.parentList[3].toString()).toBe(folderTwo._id.toString()); + expect(updatedFolderThree.parentList.length).toBe(4); +}) \ No newline at end of file diff --git a/tests/services/UserService/index.test.js b/tests/services/UserService/index.test.js new file mode 100644 index 0000000..487cc04 --- /dev/null +++ b/tests/services/UserService/index.test.js @@ -0,0 +1,137 @@ +const User = require("../../../src/models/user"); +const mongoose = require("../../../src/db/mongoose"); +const conn = mongoose.connection; +const createUser = require("../../fixtures/createUser"); + +const UserService = require("../../../src/services/UserService"); +const userService = new UserService(); + +let user; + +beforeEach(async(done) => { + + // user = await createUser(); + const {user: gotUser} = await createUser(); + user = gotUser; + done(); + +}) + +afterEach(async(done) => { + + await User.deleteMany({}); + done(); +}) + +test("When giving email and password for login, should return user and token", async() => { + + const email = user.email; + const password = "12345678"; + + const userData = { + email, + password + } + + const {user: receivedUser, token} = await userService.login(userData); + + expect(receivedUser._id.toString()).toBe(user._id.toString()); + expect(token.toString().length).not.toBe(0); +}) + +test("When giving wrong password for login, throw error", async() => { + + const email = user.email; + const password = "87654321"; + + const userData = { + email, + password + } + + await expect(userService.login(userData)).rejects.toThrow(); +}) + +test("When giving user and token for logout, should remove token", async() => { + + const userID = user._id; + const token = user.tokens[0].token; + + await userService.logout(user, token); + const updatedUser = await User.findById(userID); + + expect(updatedUser.tokens.length).toBe(0); +}) + + +test("When giving the wrong token, should not remove token", async() => { + + const userID = user._id; + const wrongToken = "123456789012"; + + await userService.logout(user, wrongToken); + const updatedUser = await User.findById(userID); + + expect(updatedUser.tokens.length).toBe(1); +}) + +test("When giving user, should logout all", async() => { + + const userID = user._id; + + await userService.logoutAll(user); + const updatedUser = await User.findById(userID); + + expect(updatedUser.tokens.length).toBe(0); +}) + +test("When giving user data, should create new user", async() => { + + const userData = { + name: "Test User", + email: "test3@test.com", + password: "12345678", + } + + const {user: createdUser, token} = await userService.create(userData); + + expect(createdUser.email).toBe(userData.email); + expect(token.toString().length).not.toBe(0); +}) + +test("When creating user with duplicate email, should throw an error", async() => { + + const userData = { + name: "Test User", + email: "test2@test.com", + password: "12345678", + } + + await expect(userService.create(userData)).rejects.toThrow(); +}) + +test("When giving user, token, old password, and new password. Should change password", async() => { + + const oldPassword = "12345678"; + const newPassword = "87654321"; + + await userService.changePassword(user, oldPassword, newPassword); +}) + +test("When giving wrong old password for change password, should throw an error", async() => { + + const token = user.tokens[0].token; + const wrongOldPassword = "1649560569"; + const newPassword = "87654321"; + + await expect(userService.changePassword(user, token, wrongOldPassword, newPassword)).rejects.toThrow(); +}) + +test("When giving new password with insufficent length, should throw an error", async() => { + + const token = user.tokens[0].token; + const oldPassword = "12345678"; + const newPassword = "8765"; + + await expect(userService.changePassword(user, token, oldPassword, newPassword)).rejects.toThrow(); +}) \ No newline at end of file diff --git a/tests/setupTests.js b/tests/setupTests.js new file mode 100644 index 0000000..f33165a --- /dev/null +++ b/tests/setupTests.js @@ -0,0 +1,10 @@ +import Enzyme from "enzyme" +import Adapter from "enzyme-adapter-react-16" +import DotEnv from "dotenv" +import "babel-polyfill"; + +Enzyme.configure({ + adapter: new Adapter() +}) + +DotEnv.config({path: ".env.test"}) \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100755 index 0000000..f99e224 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,81 @@ +// Entry Point -> Output +const webpack = require("webpack") +const CompressionPlugin = require("compression-webpack-plugin"); + + +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const path = require("path"); + +process.env.NODE_ENV = process.env.NODE_ENV || "development" + +console.log("node env", process.env.NODE_ENV); + +if (process.env.NODE_ENV === "test") { + + require("dotenv").config({path: ".env.test"}) + +} else if (process.env.NODE_ENV === "development") { + + require("dotenv").config({path: ".env.development"}) + +} else { + require("dotenv").config({path: ".env.production"}); +} + +module.exports = (env) => { + + const isProduction = env === "production" + + const CSSExtract = new MiniCssExtractPlugin({ filename: 'styles.css' }); + return { + + entry:'./src/app.js', + output: { + path: path.join(__dirname, "public", "dist"), + filename: "bundle.js" + }, + module: { + rules: [{ + loader: "babel-loader", + test: /\.js$/, + exclude: /node_modules/ + }, { + test: /\.s?css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader + }, + { + loader: 'css-loader', + options: { + sourceMap: true + } + }, + { + loader: 'sass-loader', + options: { + implementation: require("dart-sass") , + sourceMap: true + } + } + ] + }] + }, + plugins: [ + CSSExtract, + new webpack.DefinePlugin({ + "process.env.PORT": JSON.stringify(process.env.PORT), + "process.env.URL": JSON.stringify(process.env.URL), + }), + new CompressionPlugin() + ], + devtool: isProduction ? "source-map" : "inline-source-map", + devServer: { + contentBase: path.join(__dirname, "public"), + historyApiFallback: true, + publicPath: "/dist/" + }, + + } +} +