diff --git a/.DS_Store b/.DS_Store index 7c761f1..863a1dc 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/.gitignore b/.gitignore index f89a75d..cbff4b0 100755 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ certificate.key package-lock.json ._* .DS_Store +dist/ +rds-combined-ca-bundle.pem +docker-variables.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8cf94bf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM node:13 + +WORKDIR /usr/app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +RUN npm run build:docker + +EXPOSE 8080 +EXPOSE 3000 + +CMD [ "npm", "run", "start"] \ No newline at end of file diff --git a/backend/controllers/file.js b/backend/controllers/file.ts similarity index 72% rename from backend/controllers/file.js rename to backend/controllers/file.ts index 42ff660..0978fec 100644 --- a/backend/controllers/file.js +++ b/backend/controllers/file.ts @@ -1,15 +1,33 @@ -const FileService = require("../services/FileService") +import { Request, Response } from "express"; +import FileService from "../services/FileService"; +import MongoService from "../services/ChunkService/MongoService"; +import FileSystemService from "../services/ChunkService/FileSystemService"; +import S3Service from "../services/ChunkService/S3Service"; +import {UserInterface} from "../models/user"; +import uuid from "uuid"; +import tempStorage from "../tempStorage/tempStorage"; const fileService = new FileService() +interface RequestType extends Request { + user?: UserInterface, + auth?: boolean, + busboy: any, +} + +type ChunkServiceType = MongoService | FileSystemService | S3Service; + class FileController { - constructor() { + chunkService: ChunkServiceType; + constructor(chunkService: ChunkServiceType) { + + this.chunkService = chunkService; } + getThumbnail = async(req: RequestType, res: Response) => { - async getThumbnail(req, res) { if (!req.user) { @@ -17,12 +35,14 @@ class FileController { } try { + const user = req.user; const id = req.params.id; - const decryptedThumbnail = await fileService.getThumbnail(user, id); - + const decryptedThumbnail = await this.chunkService.getThumbnail(user, id); + + res.send(decryptedThumbnail); } catch (e) { @@ -35,7 +55,7 @@ class FileController { } - async getFullThumbnail(req, res) { + getFullThumbnail = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -46,7 +66,7 @@ class FileController { const user = req.user; const fileID = req.params.id; - await fileService.getFullThumbnail(user, fileID, res); + await this.chunkService.getFullThumbnail(user, fileID, res); } catch (e) { const code = e.code || 500; @@ -55,7 +75,7 @@ class FileController { } } - async uploadFile(req, res) { + uploadFile = async(req: RequestType, res: Response) => { if (!req.user) { @@ -69,7 +89,7 @@ class FileController { req.pipe(busboy); - const file = await fileService.upload(user, busboy, req); + const file = await this.chunkService.uploadFile(user, busboy, req); res.send(file); @@ -82,14 +102,14 @@ class FileController { } } - async getPublicDownload(req, res) { + getPublicDownload = async(req: RequestType, res: Response) => { try { const ID = req.params.id; const tempToken = req.params.tempToken; - await fileService.publicDownload(ID, tempToken, res); + await this.chunkService.getPublicDownload(ID, tempToken, res); } catch (e) { @@ -101,7 +121,7 @@ class FileController { } } - async removeLink(req, res) { + removeLink = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -126,7 +146,7 @@ class FileController { } - async makePublic(req, res) { + makePublic = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -150,7 +170,7 @@ class FileController { } } - async getPublicInfo(req, res) { + getPublicInfo = async(req: RequestType, res: Response) => { try { @@ -170,7 +190,7 @@ class FileController { } } - async makeOneTimePublic(req, res) { + makeOneTimePublic = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -195,7 +215,7 @@ class FileController { } - async getFileInfo(req, res) { + getFileInfo = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -219,7 +239,7 @@ class FileController { } } - async getQuickList(req, res) { + getQuickList = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -242,7 +262,7 @@ class FileController { } } - async getList(req, res) { + getList = async(req: RequestType, res: Response) => { if (!req.user) { return @@ -266,7 +286,7 @@ class FileController { } } - async getDownloadToken(req, res) { + getDownloadToken = async(req: RequestType, res: Response) => { if (!req.user) { return @@ -289,7 +309,7 @@ class FileController { } } - async getDownloadTokenVideo(req, res) { + getDownloadTokenVideo = async(req: RequestType, res: Response) => { if (!req.user) { return @@ -298,7 +318,7 @@ class FileController { try { const user = req.user; - const cookie = req.headers.uuid + const cookie = req.headers.uuid as string; const tempToken = await fileService.getDownloadTokenVideo(user, cookie); @@ -313,7 +333,7 @@ class FileController { } } - async removeTempToken(req, res) { + removeTempToken = async(req: RequestType, res: Response) => { if (!req.user) { return @@ -323,8 +343,9 @@ class FileController { const user = req.user const tempToken = req.params.tempToken; - - await fileService.removeTempToken(user, tempToken); + const currentUUID = req.params.uuid; + + await fileService.removeTempToken(user, tempToken, currentUUID); res.send(); @@ -337,84 +358,7 @@ class FileController { } } - 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) { + streamVideo = async(req: RequestType, res: Response) => { if (!req.auth || !req.user) { return; @@ -425,11 +369,23 @@ class FileController { const user = req.user; const fileID = req.params.id; const headers = req.headers; + + //tempStorage[req.params.uuid] = uuid.v4(); - console.log("stream request", req.params.id) + console.log("stream request 2", tempStorage); + + // req.on("close", () => { + // console.log("req closed stream"); + // }) + + // req.on("abort", () => { + // console.log("Aborted"); + // }) - await fileService.streamVideo(user, fileID, headers, res); + await this.chunkService.streamVideo(user, fileID, headers, res, req); + //console.log("stream finished"); + } catch (e) { const code = e.code || 500; @@ -441,7 +397,7 @@ class FileController { } - async downloadFile(req, res) { + downloadFile = async(req: RequestType, res: Response) => { if (!req.auth || !req.user) { return; @@ -454,7 +410,7 @@ class FileController { const user = req.user; const fileID = req.params.id; - await fileService.downloadFile(user, fileID, res); + await this.chunkService.downloadFile(user, fileID, res); } catch (e) { @@ -466,7 +422,7 @@ class FileController { } } - async getSuggestedList(req, res) { + getSuggestedList = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -491,7 +447,7 @@ class FileController { } } - async renameFile(req, res) { + renameFile = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -517,7 +473,7 @@ class FileController { } - async moveFile(req, res) { + moveFile = async(req: RequestType, res: Response) => { console.log("move request"); @@ -547,7 +503,7 @@ class FileController { } - async deleteFile(req, res) { + deleteFile = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -558,7 +514,7 @@ class FileController { const userID = req.user._id; const fileID = req.body.id; - await fileService.deleteFile(userID, fileID); + await this.chunkService.deleteFile(userID, fileID); res.send() @@ -572,4 +528,4 @@ class FileController { } } -module.exports = FileController; +export default FileController; diff --git a/backend/controllers/folder.js b/backend/controllers/folder.ts similarity index 71% rename from backend/controllers/folder.js rename to backend/controllers/folder.ts index 8ee3529..f101981 100644 --- a/backend/controllers/folder.js +++ b/backend/controllers/folder.ts @@ -1,13 +1,28 @@ -const FolderService = require("../services/FolderService"); +import FolderService from "../services/FolderService"; +import { Request, Response } from "express"; +import {UserInterface} from "../models/user"; +import MongoService from "../services/ChunkService/MongoService"; +import FileSystemService from "../services/ChunkService/FileSystemService"; +import S3Service from "../services/ChunkService/S3Service"; + const folderService = new FolderService(); +interface RequestType extends Request { + user?: UserInterface, +} + +type ChunkServiceType = MongoService | FileSystemService | S3Service; + class FolderController { - constructor() { + chunkService: ChunkServiceType; + constructor(chunkService: ChunkServiceType) { + + this.chunkService = chunkService; } - async uploadFolder(req, res) { + uploadFolder = async(req: RequestType, res: Response) => { if (!req.user) { @@ -31,7 +46,7 @@ class FolderController { } } - async deleteFolder(req, res) { + deleteFolder = async(req: RequestType, res: Response) => { if (!req.user) { @@ -44,7 +59,7 @@ class FolderController { const folderID = req.body.id; const parentList = req.body.parentList - await folderService.deleteFolder(userID, folderID, parentList); + await this.chunkService.deleteFolder(userID, folderID, parentList); res.send(); @@ -57,7 +72,7 @@ class FolderController { } } - async deleteAll(req, res) { + deleteAll = async(req: RequestType, res: Response) => { if (!req.user) { @@ -68,7 +83,7 @@ class FolderController { const userID = req.user._id; - await folderService.deleteAll(userID); + await this.chunkService.deleteAll(userID); res.send(); @@ -81,7 +96,7 @@ class FolderController { } } - async getInfo(req, res) { + getInfo = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -105,7 +120,7 @@ class FolderController { } } - async getSubfolderList(req, res) { + getSubfolderList = async(req: RequestType, res: Response) => { if (!req.user) { @@ -115,7 +130,7 @@ class FolderController { try { const userID = req.user._id; - const folderID = req.query.id; + const folderID = req.query.id as string; const {folderIDList, folderNameList} = await folderService.getFolderSublist(userID, folderID); @@ -130,7 +145,7 @@ class FolderController { } } - async getFolderList(req, res) { + getFolderList = async(req: RequestType, res: Response) => { if (!req.user) { @@ -155,7 +170,7 @@ class FolderController { } } - async moveFolder(req, res) { + moveFolder = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -181,7 +196,7 @@ class FolderController { } } - async renameFolder(req, res) { + renameFolder = async(req: RequestType, res: Response) => { if (!req.user) { return; @@ -208,4 +223,4 @@ class FolderController { } } -module.exports = FolderController; \ No newline at end of file +export default FolderController; \ No newline at end of file diff --git a/backend/controllers/storage.js b/backend/controllers/storage.js deleted file mode 100644 index 4205c6c..0000000 --- a/backend/controllers/storage.js +++ /dev/null @@ -1,31 +0,0 @@ -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/backend/controllers/storage.ts b/backend/controllers/storage.ts new file mode 100644 index 0000000..221bedb --- /dev/null +++ b/backend/controllers/storage.ts @@ -0,0 +1,41 @@ +import disk from "diskusage"; +import env from "../enviroment/env"; +import { Request, Response } from "express"; +import { UserInterface } from "../models/user"; + +interface RequestType extends Request { + user?: UserInterface, +} + +class StorageController { + + constructor() { + + } + + getStorageInfo = async(req: RequestType, res: Response) => { + + if (!req.user) { + + return; + } + + try { + + if (!env.root || env.root.length === 0) { + return; + } + + const info = await disk.check(env.root!); + + res.send(info) + + } catch (e) { + + console.log(e); + res.status(500).send(e); + } + } +} + +export default StorageController; \ No newline at end of file diff --git a/backend/controllers/user.js b/backend/controllers/user.ts similarity index 68% rename from backend/controllers/user.js rename to backend/controllers/user.ts index 6dd9202..9178f42 100644 --- a/backend/controllers/user.js +++ b/backend/controllers/user.ts @@ -1,21 +1,29 @@ -const env = require("../enviroment/env"); -const UserService = require("../services/UserService") + +import env from "../enviroment/env"; +import UserService from "../services/UserService"; +import { Request, Response } from "express"; +import { UserInterface } from "../models/user"; const UserProvider = new UserService(); +interface RequestType extends Request { + user?: UserInterface, + encryptedToken?: string +} + class UserController { constructor() { } - async getUser(req, res) { + getUser = async(req: RequestType, res: Response) => { try { - const user = req.user; - user.tokens = undefined; - user.tempTokens = undefined; - user.password = undefined; + const user = req.user!; + user.tokens = []; + user.tempTokens = []; + user.password = ''; user.privateKey = undefined; user.publicKey = undefined; @@ -27,7 +35,7 @@ class UserController { } } - async login(req, res) { + login = async(req: RequestType, res: Response) => { try { @@ -35,9 +43,9 @@ class UserController { const {user, token} = await UserProvider.login(body); - user.tokens = undefined; - user.tempTokens = undefined; - user.password = undefined; + user.tokens = []; + user.tempTokens = []; + user.password = ''; user.privateKey = undefined; user.publicKey = undefined; @@ -52,7 +60,7 @@ class UserController { } } - async logout(req, res) { + logout = async(req: RequestType, res: Response) => { if (!req.user) { @@ -74,13 +82,13 @@ class UserController { } } - async logoutAll(req, res) { + logoutAll = async(req: RequestType, res: Response) => { try { const user = req.user; - await UserProvider.logoutAll(user); + await UserProvider.logoutAll(user!); res.send(); @@ -90,7 +98,7 @@ class UserController { } } - async createUser(req, res) { + createUser = async(req: RequestType, res: Response) => { if (env.createAcctBlocked) { @@ -101,9 +109,9 @@ class UserController { const {user, token} = await UserProvider.create(req.body); - user.tokens = undefined; - user.tempTokens = undefined; - user.password = undefined; + user.tokens = []; + user.tempTokens = [] + user.password = ''; user.privateKey = undefined; user.publicKey = undefined; @@ -118,7 +126,7 @@ class UserController { } } - async changePassword(req, res) { + changePassword = async(req: RequestType, res: Response) => { if (!req.user) { @@ -145,4 +153,4 @@ class UserController { } } -module.exports = UserController; \ No newline at end of file +export default UserController; \ No newline at end of file diff --git a/backend/db/mongoose.js b/backend/db/mongoose.js deleted file mode 100644 index 9e5fb05..0000000 --- a/backend/db/mongoose.js +++ /dev/null @@ -1,10 +0,0 @@ -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/backend/db/mongoose.ts b/backend/db/mongoose.ts new file mode 100644 index 0000000..3f0a7b7 --- /dev/null +++ b/backend/db/mongoose.ts @@ -0,0 +1,43 @@ +import mongoose from "mongoose"; +import env from "../enviroment/env"; +import fs from "fs"; + +const DBUrl = env.mongoURL as string; + +if (env.useDocumentDB === "true") { + + console.log("Using DocumentDB"); + + if (env.documentDBBundle === "true") { + + const fileBuffer = fs.readFileSync('./rds-combined-ca-bundle.pem'); + const mongooseCertificateConnect = mongoose as any; + + mongooseCertificateConnect.connect(DBUrl, { + + useCreateIndex: true, + useUnifiedTopology: true, + sslValidate: true, + sslCA: fileBuffer + }) + + } else { + + mongoose.connect(DBUrl, { + useNewUrlParser: true, + useCreateIndex: true, + useUnifiedTopology: true, + sslValidate: true, + }) + } + +} else { + + mongoose.connect(DBUrl, { + useNewUrlParser: true, + useCreateIndex: true, + useUnifiedTopology: true, + }) +} + +export default mongoose; \ No newline at end of file diff --git a/backend/db/mongooseServerUtils.ts b/backend/db/mongooseServerUtils.ts new file mode 100644 index 0000000..114e7a8 --- /dev/null +++ b/backend/db/mongooseServerUtils.ts @@ -0,0 +1,12 @@ +import mongoose from "mongoose"; +import env from "../enviroment/env"; + +mongoose.connect(env.mongoURL!, { + useNewUrlParser: true, + useCreateIndex: true, + useUnifiedTopology: true, + socketTimeoutMS: 30000000, + keepAlive: true, +}) + +export default mongoose; \ No newline at end of file diff --git a/backend/db/s3.ts b/backend/db/s3.ts new file mode 100644 index 0000000..17b945c --- /dev/null +++ b/backend/db/s3.ts @@ -0,0 +1,11 @@ +import AWS from "aws-sdk"; +import env from "../enviroment/env"; + +AWS.config.update({ + accessKeyId: env.s3ID, + secretAccessKey: env.s3Key +}); + +const s3 = new AWS.S3(); + +export default s3; \ No newline at end of file diff --git a/backend/db/utils/fileUtils/index.js b/backend/db/utils/fileUtils/index.ts similarity index 52% rename from backend/db/utils/fileUtils/index.js rename to backend/db/utils/fileUtils/index.ts index 7cb92d5..57a6c74 100644 --- a/backend/db/utils/fileUtils/index.js +++ b/backend/db/utils/fileUtils/index.ts @@ -1,98 +1,104 @@ -const mongoose = require("../../mongoose") +import mongoose from "../../mongoose"; +import {ObjectID} from "mongodb"; +import {FileInterface} from "../../../models/file"; +import {UserInterface} from "../../../models/user"; +import { QueryInterface } from "../../../utils/createQuery"; const conn = mongoose.connection; -const ObjectID = require('mongodb').ObjectID -const DbUtil = function() { +class DbUtil { - this.getPublicFile = async(fileID) => { + constructor() { + + } + + getPublicFile = async(fileID: string) => { let file = await conn.db.collection("fs.files") - .findOne({"_id": ObjectID(fileID)}); + .findOne({"_id": new ObjectID(fileID)}) as FileInterface; return file; } - this.removeOneTimePublicLink = async(fileID) => { + removeOneTimePublicLink = async(fileID: string) => { const file = await conn.db.collection("fs.files") - .findOneAndUpdate({"_id": ObjectID(fileID)}, { - "$unset": {"metadata.linkType": "", "metadata.link": ""}}); + .findOneAndUpdate({"_id": new ObjectID(fileID)}, { + "$unset": {"metadata.linkType": "", "metadata.link": ""}}) as FileInterface; return file; - } - this.removeLink = async(fileID, userID) => { + removeLink = async(fileID: string, userID: string) => { const file = await conn.db.collection("fs.files") - .findOneAndUpdate({"_id": ObjectID(fileID), + .findOneAndUpdate({"_id": new ObjectID(fileID), "metadata.owner": userID}, - {"$unset": {"metadata.linkType": "", "metadata.link": ""}}) + {"$unset": {"metadata.linkType": "", "metadata.link": ""}}) as FileInterface; return file; } - this.makePublic = async(fileID, userID, token) => { + makePublic = async(fileID: string, userID: string, token: string) => { const file = await conn.db.collection("fs.files") - .findOneAndUpdate({"_id": ObjectID(fileID), + .findOneAndUpdate({"_id": new ObjectID(fileID), "metadata.owner": userID}, - {"$set": {"metadata.linkType": "public", "metadata.link": token}}) + {"$set": {"metadata.linkType": "public", "metadata.link": token}}) as FileInterface return file; } - this.getPublicInfo = async(fileID, tempToken) => { + getPublicInfo = async(fileID: string, tempToken: string) => { const file = await conn.db.collection("fs.files") - .findOne({"_id": ObjectID(fileID), "metadata.link": tempToken}) + .findOne({"_id": new ObjectID(fileID), "metadata.link": tempToken}) as FileInterface; return file; } - this.makeOneTimePublic = async(fileID, userID, token) => { + makeOneTimePublic = async(fileID: string, userID: string, token: string) => { const file = await conn.db.collection("fs.files") - .findOneAndUpdate({"_id": ObjectID(fileID), + .findOneAndUpdate({"_id": new ObjectID(fileID), "metadata.owner": userID}, - {"$set": {"metadata.linkType": "one", "metadata.link": token}}) + {"$set": {"metadata.linkType": "one", "metadata.link": token}}) as FileInterface; return file; } - this.getFileInfo = async(fileID, userID) => { + getFileInfo = async(fileID: string, userID: string) => { const file = await conn.db.collection("fs.files") - .findOne({"metadata.owner": userID, "_id": ObjectID(fileID)}); + .findOne({"metadata.owner": userID, "_id": new ObjectID(fileID)}) as FileInterface; return file; } - this.getQuickList = async(userID) => { + getQuickList = async(userID: string) => { const fileList = await conn.db.collection("fs.files") .find({"metadata.owner": userID}) .sort({uploadDate: -1}) .limit(10) - .toArray(); + .toArray() as FileInterface[]; return fileList; } - this.getList = async(queryObj, sortBy, limit) => { + getList = async(queryObj: QueryInterface, sortBy: string, limit: number) => { const fileList = await conn.db.collection("fs.files") .find(queryObj) .sort(sortBy) .limit(limit) - .toArray(); + .toArray() as FileInterface[]; return fileList; } - this.removeTempToken = async(user, tempToken) => { - + removeTempToken = async(user: UserInterface, tempToken: string) => { + user.tempTokens = user.tempTokens.filter((filterToken) => { return filterToken.token !== tempToken; @@ -101,66 +107,55 @@ const DbUtil = function() { 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) => { + getFileSearchList = async(userID: string, searchQuery: RegExp) => { const fileList = await conn.db.collection("fs.files") .find({"metadata.owner": userID, "filename": searchQuery}) .limit(10) - .toArray(); + .toArray() as FileInterface[]; return fileList; } - this.renameFile = async(fileID, userID, title) => { + renameFile = async(fileID: string, userID: string, title: string) => { const file = await conn.db.collection("fs.files") - .findOneAndUpdate({"_id": ObjectID(fileID), - "metadata.owner": userID}, {"$set": {"filename": title}}) + .findOneAndUpdate({"_id": new ObjectID(fileID), + "metadata.owner": userID}, {"$set": {"filename": title}}) as FileInterface; return file; } - this.moveFile = async(fileID, userID, parent, parentList) => { + moveFile = async(fileID: string, userID: string, parent: string, parentList: string) => { const file = await conn.db.collection("fs.files") - .findOneAndUpdate({"_id": ObjectID(fileID), + .findOneAndUpdate({"_id": new ObjectID(fileID), "metadata.owner": userID}, {"$set": {"metadata.parent": parent, "metadata.parentList": parentList}}) return file; } - this.getFileListByParent = async(userID, parentListString) => { + getFileListByParent = async(userID: string, parentListString: string) => { const fileList = await conn.db.collection("fs.files") .find({"metadata.owner": userID, - "metadata.parentList": {$regex : `.*${parentListString}.*`}}).toArray(); + "metadata.parentList": {$regex : `.*${parentListString}.*`}}).toArray() as FileInterface[]; return fileList; } - this.getFileListByOwner = async(userID) => { + getFileListByOwner = async(userID: string) => { const fileList = await conn.db.collection("fs.files") - .find({"metadata.owner": userID}).toArray(); + .find({"metadata.owner": userID}).toArray() as FileInterface[]; return fileList; } - this.removeChunksByID = async(fileID) => { + removeChunksByID = async(fileID: string) => { await conn.db.collection("fs.chunks").deleteMany({files_id: fileID}); } - } -module.exports = DbUtil; \ No newline at end of file +export default DbUtil; \ No newline at end of file diff --git a/backend/db/utils/folderUtils/index.js b/backend/db/utils/folderUtils/index.js deleted file mode 100644 index 53e6883..0000000 --- a/backend/db/utils/folderUtils/index.js +++ /dev/null @@ -1,61 +0,0 @@ -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/backend/db/utils/folderUtils/index.ts b/backend/db/utils/folderUtils/index.ts new file mode 100644 index 0000000..4e2be4f --- /dev/null +++ b/backend/db/utils/folderUtils/index.ts @@ -0,0 +1,64 @@ +import Folder, { FolderInterface } from "../../../models/folder"; +import {ObjectID} from "mongodb"; + +class DbUtil { + + constructor() { + + } + + getFolderSearchList = async(userID: string, searchQuery: RegExp) => { + + const folderList = await Folder.find({"owner": userID, "name": searchQuery}).limit(10) as FolderInterface[]; + + return folderList; + } + + getFolderInfo = async(folderID: string, userID: string) => { + + const folder = await Folder.findOne({"owner": userID, "_id": new ObjectID(folderID)}) as FolderInterface; + + return folder; + } + + getFolderListByParent = async(userID: string, parent: string, sortBy: string) => { + + const folderList = await Folder.find({"owner": userID, "parent": parent}) + .sort(sortBy) as FolderInterface[]; + + return folderList; + } + + getFolderListBySearch = async(userID: string, searchQuery: string, sortBy: string) => { + + const folderList = await Folder.find({"name": searchQuery,"owner": userID}) + .sort(sortBy) as FolderInterface[]; + + return folderList; + } + + moveFolder = async(folderID: string, userID: string, parent: string, parentList: string[]) => { + + const folder = await Folder.findOneAndUpdate({"_id": new ObjectID(folderID), + "owner": userID}, {"$set": {"parent": parent, "parentList": parentList}}) as FolderInterface; + + return folder; + } + + renameFolder = async(folderID: string, userID: string, title: string) => { + + const folder = await Folder.findOneAndUpdate({"_id": new ObjectID(folderID), + "owner": userID}, {"$set": {"name": title}}) as FolderInterface; + + return folder; + } + + findAllFoldersByParent = async(parentID: string, userID: string) => { + + const folderList = await Folder.find({"parentList": parentID, "owner": userID}) as FolderInterface[]; + + return folderList; + } +} + +export default DbUtil; \ No newline at end of file diff --git a/backend/enviroment/env.js b/backend/enviroment/env.js deleted file mode 100644 index df55f36..0000000 --- a/backend/enviroment/env.js +++ /dev/null @@ -1,9 +0,0 @@ -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/backend/enviroment/env.ts b/backend/enviroment/env.ts new file mode 100644 index 0000000..9a531c8 --- /dev/null +++ b/backend/enviroment/env.ts @@ -0,0 +1,16 @@ +export default { + 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, + dbType: process.env.DB_TYPE, + fsDirectory: process.env.FS_DIRECTORY, + s3ID: process.env.S3_ID, + s3Key: process.env.S3_KEY, + s3Bucket: process.env.S3_BUCKET, + useDocumentDB: process.env.USE_DOCUMENT_DB, + documentDBBundle: process.env.DOCUMENT_DB_BUNDLE +} \ No newline at end of file diff --git a/backend/enviroment/getEnvVariables.ts b/backend/enviroment/getEnvVariables.ts new file mode 100644 index 0000000..4c7b20e --- /dev/null +++ b/backend/enviroment/getEnvVariables.ts @@ -0,0 +1,23 @@ +import path from "path"; + +const getEnvVariables = () => { + + const configPath = path.join(__dirname, "..", "..", "config"); + + const processType = process.env.NODE_ENV; + + if (processType === 'production' || processType === undefined) { + + require('dotenv').config({ path: configPath + "/prod.env"}) + + } else if (processType === 'development') { + + require('dotenv').config({ path: configPath + "/dev.env"}) + + } else if (processType === 'test') { + + require('dotenv').config({ path: configPath + "/test.env"}) + } +} + +export default getEnvVariables; \ No newline at end of file diff --git a/backend/express-routers/file.js b/backend/express-routers/file.ts similarity index 60% rename from backend/express-routers/file.js rename to backend/express-routers/file.ts index f9f9860..748f6bb 100644 --- a/backend/express-routers/file.js +++ b/backend/express-routers/file.ts @@ -1,16 +1,35 @@ -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"); +import {Router} from "express"; +import auth from "../middleware/auth"; +import tempAuth from "../middleware/tempAuth"; +import tempAuthVideo from "../middleware/tempAuthVideo"; +import FileController from "../controllers/file"; +import env from "../enviroment/env"; +import MongoService from "../services/ChunkService/MongoService"; +import FileSystemService from "../services/ChunkService/FileSystemService"; +import S3Service from "../services/ChunkService/S3Service"; -const fileController = new FileController() +let fileController: FileController; + +if (env.dbType === "mongo") { + + const mongoService = new MongoService(); + fileController = new FileController(mongoService); + +} else if (env.dbType === "fs") { + + const fileSystemService = new FileSystemService(); + fileController = new FileController(fileSystemService); + +} else { + + const s3Service = new S3Service(); + fileController = new FileController(s3Service); +} + +const router = Router(); 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); @@ -29,8 +48,6 @@ router.get("/file-service/download/get-token", auth, fileController.getDownloadT 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); @@ -47,10 +64,8 @@ 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/token-video/:tempToken/:uuid", auth, fileController.removeTempToken); router.delete("/file-service/remove", auth, fileController.deleteFile); -module.exports = router; \ No newline at end of file +export default router; \ No newline at end of file diff --git a/backend/express-routers/folder.js b/backend/express-routers/folder.js deleted file mode 100644 index 117be38..0000000 --- a/backend/express-routers/folder.js +++ /dev/null @@ -1,23 +0,0 @@ -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/backend/express-routers/folder.ts b/backend/express-routers/folder.ts new file mode 100644 index 0000000..5b23795 --- /dev/null +++ b/backend/express-routers/folder.ts @@ -0,0 +1,45 @@ +import {Router} from "express"; +import auth from "../middleware/auth"; +import env from "../enviroment/env"; +import MongoService from "../services/ChunkService/MongoService"; +import FileSystemService from "../services/ChunkService/FileSystemService"; +import S3Service from "../services/ChunkService/S3Service"; +import FolderController from "../controllers/folder"; + +let folderController: FolderController; + +if (env.dbType === "mongo") { + + const mongoService = new MongoService(); + folderController = new FolderController(mongoService); + +} else if (env.dbType === "fs") { + + const fileSystemService = new FileSystemService(); + folderController = new FolderController(fileSystemService); + +} else { + + const s3Service = new S3Service(); + folderController = new FolderController(s3Service); +} + +const router = Router(); + +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); + +export default router; \ No newline at end of file diff --git a/backend/express-routers/storage.js b/backend/express-routers/storage.js deleted file mode 100644 index 5fa436b..0000000 --- a/backend/express-routers/storage.js +++ /dev/null @@ -1,9 +0,0 @@ -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/backend/express-routers/storage.ts b/backend/express-routers/storage.ts new file mode 100644 index 0000000..abbdc6d --- /dev/null +++ b/backend/express-routers/storage.ts @@ -0,0 +1,11 @@ +import {Router} from "express"; +import auth from "../middleware/auth"; +import StorageController from "../controllers/storage"; + +const storageController = new StorageController(); + +const router = Router(); + +router.get("/storage-service/info", auth, storageController.getStorageInfo); + +export default router; \ No newline at end of file diff --git a/backend/express-routers/user.js b/backend/express-routers/user.ts similarity index 70% rename from backend/express-routers/user.js rename to backend/express-routers/user.ts index 089a4ff..fd72c7b 100644 --- a/backend/express-routers/user.js +++ b/backend/express-routers/user.ts @@ -1,9 +1,11 @@ -const express = require("express"); -const router = new express.Router; -const auth = require("../middleware/auth"); -const UserController = require("../controllers/user"); +import { Router } from "express"; +import auth from "../middleware/auth"; +import UserController from "../controllers/user"; + const userController = new UserController(); +const router = Router(); + router.get("/user-service/user", auth, userController.getUser); router.post("/user-service/login", userController.login); @@ -16,4 +18,4 @@ 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 +export default router; \ No newline at end of file diff --git a/backend/key/getKey.ts b/backend/key/getKey.ts new file mode 100644 index 0000000..ddef6fd --- /dev/null +++ b/backend/key/getKey.ts @@ -0,0 +1,33 @@ +const prompt = require('password-prompt') +import env from "../enviroment/env"; +import crypto from "crypto"; +import getWebUIKey from "./getWebUIKey"; + +const getKey = async() => { + + if (process.env.KEY) { + // For Docker + + const password = process.env.KEY; + + env.key = crypto.createHash("md5").update(password).digest("hex"); + + } else if (process.env.NODE_ENV === "production") { + + let password = await getWebUIKey(); + + 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; + } +} + +export default getKey; \ No newline at end of file diff --git a/backend/key/getWebUIKey.ts b/backend/key/getWebUIKey.ts new file mode 100644 index 0000000..42c1791 --- /dev/null +++ b/backend/key/getWebUIKey.ts @@ -0,0 +1,52 @@ +import express, {Request, Response} from "express"; +import http from "http"; +import path from "path"; +import bodyParser from "body-parser"; +const app = express(); + +const getWebUIKey = () => { + + const publicPath = path.join(__dirname, "..", "..", "webUI"); + + + return new Promise((resolve, reject) => { + + app.use(express.static(publicPath)); + app.use(express.json()); + app.use(bodyParser.json({limit: "50mb"})); + app.use(bodyParser.urlencoded({limit: "50mb", extended: true, parameterLimit:50000})) + + app.post("/submit", (req: Request, res: Response) => { + + const password = req.body.password; + + if (password && password.length > 0) { + + console.log("Got WebUI key"); + res.send(); + server.close(); + resolve(password); + } + }) + + app.get("*", (req: Request, res: Response) => { + + res.sendFile(path.join(publicPath, "index.html")); + + }) + + const port = process.env.HTTP_PORT || process.env.PORT || "3000"; + const url = process.env.DOCKER ? undefined : "localhost"; + + const server = http.createServer(app) as any; + + server.listen(port, url, () => { + + console.log(`\nPlease navigate to http://localhost:${port} to enter encryption key\n`) + + }); + + }) +} + +export default getWebUIKey; \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.ts similarity index 54% rename from backend/middleware/auth.js rename to backend/middleware/auth.ts index 35085f2..75ae995 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.ts @@ -1,18 +1,30 @@ -const jwt = require("jsonwebtoken"); -const User = require("../models/user"); -const env = require("../enviroment/env"); +import jwt from "jsonwebtoken"; +import User, {UserInterface} from "../models/user"; +import env from "../enviroment/env"; +import {Request, Response, NextFunction} from "express"; -const auth = async(req, res, next) => { +interface RequestType extends Request { + user?: UserInterface, + token?: string, + encryptedToken?: string, +} + +type jwtType = { + iv: Buffer, + _id: string, +} + +const auth = async(req: RequestType, res: Response, next: NextFunction) => { try { - const token = req.header("Authorization").replace("Bearer ", ""); + const token = req.header("Authorization")!.replace("Bearer ", ""); - const decoded = await jwt.verify(token, env.password); + const decoded = jwt.verify(token, env.password!) as jwtType; const iv = decoded.iv; - - const user = await User.findOne({_id: decoded._id}); + + const user = await User.findOne({_id: decoded._id}) as UserInterface; const encrpytionKey = user.getEncryptionKey(); const encryptedToken = user.encryptToken(token, encrpytionKey, iv); @@ -46,4 +58,4 @@ const auth = async(req, res, next) => { } } -module.exports = auth; \ No newline at end of file +export default auth; \ No newline at end of file diff --git a/backend/middleware/tempAuth.js b/backend/middleware/tempAuth.ts similarity index 63% rename from backend/middleware/tempAuth.js rename to backend/middleware/tempAuth.ts index 06dec37..1a03fb5 100644 --- a/backend/middleware/tempAuth.js +++ b/backend/middleware/tempAuth.ts @@ -1,18 +1,31 @@ -const jwt = require("jsonwebtoken"); -const User = require("../models/user"); -const env = require("../enviroment/env"); +import jwt from "jsonwebtoken"; +import User, {UserInterface} from "../models/user"; +import env from "../enviroment/env"; +import {Request, Response, NextFunction} from "express"; -const tempAuth = async(req, res, next) => { + +interface RequestType extends Request { + user?: UserInterface, + auth?: boolean, + encryptedTempToken?: string, +} + +type jwtType = { + iv: Buffer, + _id: string, +} + +const tempAuth = async(req: RequestType, res: Response, next: NextFunction) => { try { const token = req.params.tempToken; - const decoded = await jwt.verify(token, env.password); + const decoded = jwt.verify(token, env.password!) as jwtType; const iv = decoded.iv; - const user = await User.findOne({_id: decoded._id}) + const user = await User.findOne({_id: decoded._id}) as UserInterface; const encrpytionKey = user.getEncryptionKey(); const encryptedToken = user.encryptToken(token, encrpytionKey, iv); @@ -54,4 +67,4 @@ const tempAuth = async(req, res, next) => { } } -module.exports = tempAuth; \ No newline at end of file +export default tempAuth; \ No newline at end of file diff --git a/backend/middleware/tempAuthVideo.js b/backend/middleware/tempAuthVideo.ts similarity index 60% rename from backend/middleware/tempAuthVideo.js rename to backend/middleware/tempAuthVideo.ts index 3f62d06..5c2cb50 100644 --- a/backend/middleware/tempAuthVideo.js +++ b/backend/middleware/tempAuthVideo.ts @@ -1,14 +1,28 @@ -const jwt = require("jsonwebtoken"); -const User = require("../models/user"); -const env = require("../enviroment/env"); +import jwt from "jsonwebtoken"; +import User, {UserInterface} from "../models/user"; +import env from "../enviroment/env"; +import {Request, Response, NextFunction} from "express"; -const tempAuthVideo = async(req, res, next) => { + +interface RequestType extends Request { + user?: UserInterface, + auth?: boolean, + encryptedTempToken?: string, +} + +type jwtType = { + iv: Buffer, + _id: string, + cookie: string +} + +const tempAuthVideo = async(req: RequestType, res: Response, next: NextFunction) => { try { const token = req.params.tempToken; - const decoded = await jwt.verify(token, env.password); + const decoded = jwt.verify(token, env.password!) as jwtType; const iv = decoded.iv; @@ -17,7 +31,7 @@ const tempAuthVideo = async(req, res, next) => { throw new Error("Cookie mismatch") } - const user = await User.findOne({_id: decoded._id}) + const user = await User.findOne({_id: decoded._id}) as UserInterface; const encrpytionKey = user.getEncryptionKey(); const encryptedToken = user.encryptToken(token, encrpytionKey, iv); @@ -53,4 +67,4 @@ const tempAuthVideo = async(req, res, next) => { } } -module.exports = tempAuthVideo; \ No newline at end of file +export default tempAuthVideo; \ No newline at end of file diff --git a/backend/models/file.ts b/backend/models/file.ts new file mode 100644 index 0000000..3649c40 --- /dev/null +++ b/backend/models/file.ts @@ -0,0 +1,87 @@ +import mongoose, {Document} from "mongoose"; +import { Binary } from "mongodb"; + +const fileSchema = new mongoose.Schema({ + + length: { + type: Number, + required: true, + }, + chunkSize: { + type: Number, + }, + uploadDate: { + type: Date, + required: true + }, + filename: { + type: String, + required: true + }, + metadata: { + type: { + owner: { + type: String, + required: true + }, + parent: { + type: String, + required: true + }, + parentList: { + type: String, + required: true + }, + hasThumbnail: { + type: Boolean, + required: true + }, + isVideo: { + type: Boolean, + required: true + }, + thumbnailID: String, + size: { + type: Number, + required: true, + }, + IV: { + type: Binary, + required: true + }, + linkType: String, + link: String, + filePath: String, + s3ID: String, + + }, + required: true + } + +}) + +export interface FileInterface extends Document { + length: number, + chunkSize: number, + uploadDate: string, + filename: string, + lastErrorObject: {updatedExisting: any} + metadata: { + owner: string, + parent: string, + parentList: string, + hasThumbnail: boolean, + isVideo: boolean, + thumbnailID?: string, + size: number, + IV: Buffer, + linkType?: 'one' | 'public', + link?: string, + filePath?: string, + s3ID?: string, + } +} + +const File = mongoose.model("fs.files", fileSchema); + +export default File; \ No newline at end of file diff --git a/backend/models/fileSystem.ts b/backend/models/fileSystem.ts new file mode 100644 index 0000000..aaeebd6 --- /dev/null +++ b/backend/models/fileSystem.ts @@ -0,0 +1,69 @@ +import mongoose, {Document} from "mongoose"; + +const fileSystemSchema = new mongoose.Schema({ + + name: { + type: String, + required: true, + }, + owner: { + type: String, + required: true + }, + path: { + type: String, + required: true + }, + parent: { + type: String, + required: true, + }, + parentList: { + type: Array, + required: true + }, + hasThumbnail: { + type: Boolean, + required: true + }, + thumbnailID: { + type: String + }, + originalSize: { + type: Number, + required: true + }, + size: { + type: Number, + required: true + }, + isVideo: { + type: Boolean, + required: true + }, + IV: { + type: Buffer, + required: true + } + +}, { + timestamps: true +}) + +export interface FileSystemInterface extends Document { + name: string, + owner: string, + path: string, + parent: string, + parentList: string[], + hasThumbnail: boolean, + thumbnailID?: string, + originalSize: number, + size: number, + isVideo: boolean, + IV: Buffer +} + +const FileSystem = mongoose.model("FileSystem", fileSystemSchema); + +export default FileSystem; \ No newline at end of file diff --git a/backend/models/folder.js b/backend/models/folder.ts similarity index 55% rename from backend/models/folder.js rename to backend/models/folder.ts index 290fe0c..571871b 100644 --- a/backend/models/folder.js +++ b/backend/models/folder.ts @@ -1,4 +1,4 @@ -const mongoose = require("mongoose"); +import mongoose, {Document} from "mongoose"; const folderSchema = new mongoose.Schema({ @@ -27,6 +27,14 @@ const folderSchema = new mongoose.Schema({ timestamps: true }) -const Folder = mongoose.model("Folder", folderSchema); +export interface FolderInterface extends Document { + name: string, + parent: string, + owner: string, + parentList: string[], + _doc?: any +} -module.exports = Folder; \ No newline at end of file +const Folder = mongoose.model("Folder", folderSchema); + +export default Folder; \ No newline at end of file diff --git a/backend/models/thumbnail.js b/backend/models/thumbnail.js deleted file mode 100644 index fe873c9..0000000 --- a/backend/models/thumbnail.js +++ /dev/null @@ -1,25 +0,0 @@ -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/backend/models/thumbnail.ts b/backend/models/thumbnail.ts new file mode 100644 index 0000000..8526060 --- /dev/null +++ b/backend/models/thumbnail.ts @@ -0,0 +1,41 @@ +import mongoose, {Document} from "mongoose"; + +const thumbnailSchema = new mongoose.Schema({ + + name: { + type: String, + required: true, + }, + owner: { + type: String, + required: true + }, + + data: { + type: Buffer, + }, + path: { + type: String + }, + + IV: { + type: Buffer, + }, + s3ID: String +}, { + timestamps: true +}) + +export interface ThumbnailInterface extends Document { + _id: string, + name: string, + owner: string, + data?: any, + path?: string, + IV?: Buffer, + s3ID? : string, +} + +const Thumbnail = mongoose.model("Thumbnail", thumbnailSchema); + +export default Thumbnail; \ No newline at end of file diff --git a/backend/models/user.js b/backend/models/user.ts similarity index 59% rename from backend/models/user.js rename to backend/models/user.ts index 4c90626..c824fe1 100644 --- a/backend/models/user.js +++ b/backend/models/user.ts @@ -1,11 +1,11 @@ -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"); +import mongoose, {Document} from "mongoose"; +import validator from "validator"; +import crypto from "crypto"; +import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; +import env from "../enviroment/env"; -const userSchema = mongoose.Schema({ +const userSchema = new mongoose.Schema({ name: { @@ -18,7 +18,7 @@ const userSchema = mongoose.Schema({ trim: true, unique: true, lowercase: true, - validate(value) { + validate(value: any): any { if (!validator.isEmail(value)) { throw new Error("Email is invalid"); } @@ -28,7 +28,7 @@ const userSchema = mongoose.Schema({ type: String, trim: true, required: true, - validate(value) { + validate(value: any): any { if (value.length < 6) { throw new Error("Password Length Not Sufficent"); } @@ -57,12 +57,28 @@ const userSchema = mongoose.Schema({ timestamps: true }) -// userSchema.virtual("files", { -// ref: "fs.files", -// localField: "_id" -// }) +export interface UserInterface extends Document { + _id: string, + name: string, + email: string, + password: string, + tokens: any[], + tempTokens: any[], + privateKey?: string, + publicKey?: string, + token?: string, -userSchema.pre("save", async function(next) { + getEncryptionKey: () => Buffer | undefined; + generateTempAuthToken: () => any; + generateTempAuthTokenVideo: (cookie: string) => any; + encryptToken: (tempToken: any, key: any, publicKey: any) => any; + findByCreds: (email: string, password: string) => UserInterface; + generateAuthToken: () => any; + generateEncryptionKeys: () => Promise; + changeEncryptionKey: (randomKey: Buffer) => void; +} + +userSchema.pre("save", async function(this: any, next: any) { const user = this; @@ -73,7 +89,7 @@ userSchema.pre("save", async function(next) { next(); }) -userSchema.statics.findByCreds = async(email, password) => { +userSchema.statics.findByCreds = async(email: string, password: string) => { const user = await User.findOne({email}); @@ -110,8 +126,7 @@ 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; + let token = jwt.sign({_id:user._id.toString(), iv}, env.password!); const encryptionKey = user.getEncryptionKey(); @@ -123,19 +138,18 @@ userSchema.methods.generateAuthToken = async function() { return token; } -userSchema.methods.encryptToken = function(token, key, iv) { +userSchema.methods.encryptToken = function(token: string, key: string, iv: any) { 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"); + const encryptedText = cipher.update(token); - return encryptedText; + return Buffer.concat([encryptedText, cipher.final()]).toString("hex");; } -userSchema.methods.decryptToken = function(encryptedToken, key, iv) { +userSchema.methods.decryptToken = function(encryptedToken: any, key: string, iv: any) { encryptedToken = Buffer.from(encryptedToken, "hex"); iv = Buffer.from(iv, "hex") @@ -143,17 +157,16 @@ userSchema.methods.decryptToken = function(encryptedToken, key, iv) { 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(); + const tokenDecrypted = decipher.update(encryptedToken); - return tokenDecrypted; + return Buffer.concat([tokenDecrypted, decipher.final()]).toString(); } userSchema.methods.generateEncryptionKeys = async function() { const user = this; const userPassword = user.password; - const masterPassword = env.key; + const masterPassword = env.key!; const randomKey = crypto.randomBytes(32); @@ -165,10 +178,9 @@ userSchema.methods.generateEncryptionKeys = async function() { 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"); + const masterEncryptedText = masterCipher.update(encryptedText); - user.privateKey = masterEncryptedText; + user.privateKey = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex"); user.publicKey = iv.toString("hex"); await user.save(); @@ -176,32 +188,39 @@ userSchema.methods.generateEncryptionKeys = async function() { 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"); + try { + 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 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()]) + 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()]); + let decipher = crypto.createDecipheriv('aes-256-cbc', USER_CIPHER_KEY, iv); + let decrypted = decipher.update(masterDecrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted; + return decrypted; + + } catch (e) { + + console.log("Get Encryption Key Error", e); + return undefined; + } } -userSchema.methods.changeEncryptionKey = async function(randomKey) { +userSchema.methods.changeEncryptionKey = async function(randomKey: Buffer) { const user = this; const userPassword = user.password; - const masterPassword = env.key; + const masterPassword = env.key!; const iv = crypto.randomBytes(16); const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest(); @@ -211,10 +230,9 @@ userSchema.methods.changeEncryptionKey = async function(randomKey) { 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"); + const masterEncryptedText = masterCipher.update(encryptedText); - user.privateKey = masterEncryptedText; + user.privateKey = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex"); user.publicKey = iv.toString("hex"); await user.save(); @@ -222,25 +240,10 @@ userSchema.methods.changeEncryptionKey = async function(randomKey) { 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 user = this as UserInterface; + const token = jwt.sign({_id:user._id.toString(), iv}, env.password!, {expiresIn: "3000ms"}); const encryptionKey = user.getEncryptionKey(); const encryptedToken = user.encryptToken(token, encryptionKey, iv); @@ -251,25 +254,22 @@ userSchema.methods.generateTempAuthToken = async function() { return token; } -userSchema.methods.generateTempAuthTokenVideo = async function(cookie) { +userSchema.methods.generateTempAuthTokenVideo = async function(cookie: string) { 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 token = jwt.sign({_id:user._id.toString(), cookie, iv}, env.password!, {expiresIn:"5h"}); 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); -const User = mongoose.model("User", userSchema); - -module.exports = User; \ No newline at end of file +export default User; \ No newline at end of file diff --git a/backend/server/server.ts b/backend/server/server.ts new file mode 100755 index 0000000..6f4c5a2 --- /dev/null +++ b/backend/server/server.ts @@ -0,0 +1,67 @@ +import express, {Request, Response} from "express"; +import path from "path"; +import userRouter from "../express-routers/user"; +import fileRouter from "../express-routers/file"; +import folderRouter from "../express-routers/folder"; +import storageRouter from "../express-routers/storage"; +import bodyParser from "body-parser"; +import https from "https"; +import fs from "fs"; +import helmet from "helmet"; +import busboy from "connect-busboy"; +import compression from "compression"; +import http from "http"; + + + +const app = express(); +const publicPath = path.join(__dirname, "..", "..", "public"); + +let server: any; +let serverHttps: any; + +if (process.env.SSL === 'true') { + + const cert = fs.readFileSync("certificate.crt") + const ca = fs.readFileSync("certificate.ca-bundle"); + const key = fs.readFileSync("certificate.key"); + + + const options = { + cert, + ca, + key + } + + serverHttps = https.createServer( options, app ); +} + +server = http.createServer(app); + +require("../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("*", (_: Request, res: Response) => { + + res.sendFile(path.join(publicPath, "index.html")) +}) + +export default {server, serverHttps}; \ No newline at end of file diff --git a/backend/server/serverStart.ts b/backend/server/serverStart.ts new file mode 100644 index 0000000..53ca0b8 --- /dev/null +++ b/backend/server/serverStart.ts @@ -0,0 +1,42 @@ +import getEnvVariables from "../enviroment/getEnvVariables"; +getEnvVariables(); +import getKey from "../key/getKey"; +import servers from "./server"; + +const {server, serverHttps} = servers; + +const serverStart = async() => { + + await getKey(); + + console.log("ENV", process.env.NODE_ENV); + + if (process.env.NODE_ENV === 'production' && process.env.SSL === "true") { + + 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 if (process.env.NODE_ENV === 'production') { + + const port = process.env.HTTP_PORT || process.env.PORT; + + server.listen(port, process.env.URL, () => { + console.log("Http Server (No-SSL) Running On Port:", port); + }) + + } else { + + const port = process.env.HTTP_PORT || process.env.PORT; + + server.listen(port, process.env.URL, () => { + console.log("Development Server Running On Port:", port); + }) + } +} + +serverStart(); diff --git a/backend/services/ChunkService/FileSystemService.ts b/backend/services/ChunkService/FileSystemService.ts new file mode 100644 index 0000000..5bfa9b2 --- /dev/null +++ b/backend/services/ChunkService/FileSystemService.ts @@ -0,0 +1,398 @@ +import { Response, Request } from "express"; +import ChunkInterface from "./utils/ChunkInterface"; +import { UserInterface } from "../../models/user"; +import NotAuthorizedError from "../../utils/NotAuthorizedError"; +import NotFoundError from "../../utils/NotFoundError"; +import crypto from "crypto"; +import getBusboyData from "./utils/getBusboyData"; +import videoChecker from "../../utils/videoChecker"; +import fs from "fs"; +import uuid from "uuid"; +import awaitUploadStreamFS from "./utils/awaitUploadStreamFS"; +import File, { FileInterface } from "../../models/file"; +import getFileSize from "./utils/getFileSize"; +import DbUtilFile from "../../db/utils/fileUtils/index"; +import awaitStream from "./utils/awaitStream"; +import createThumbnailFS from "./utils/createThumbnailFS"; +import imageChecker from "../../utils/imageChecker"; +import Thumbnail, {ThumbnailInterface} from "../../models/thumbnail"; +import streamToBuffer from "../../utils/streamToBuffer"; +import User from "../../models/user"; +import env from "../../enviroment/env"; +import removeChunksFS from "./utils/removeChunksFS"; +import getPrevIVFS from "./utils/getPrevIVFS"; +import awaitStreamVideo from "./utils/awaitStreamVideo"; +import fixStartChunkLength from "./utils/fixStartChunkLength"; +import Folder from "../../models/folder"; + +const dbUtilsFile = new DbUtilFile(); + +class FileSystemService implements ChunkInterface { + + constructor() { + + } + + uploadFile = async(user: UserInterface, busboy: any, req: Request) => { + + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const cipher = crypto.createCipheriv('aes256', CIPHER_KEY, initVect); + + const {file, filename, formData} = await getBusboyData(busboy); + + 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 systemFileName = uuid.v4(); + + const metadata = { + owner: user._id, + parent, + parentList, + hasThumbnail, + thumbnailID, + isVideo, + size, + IV: initVect, + filePath: env.fsDirectory + systemFileName + } + + const fileWriteStream = fs.createWriteStream(metadata.filePath); + + const totalStreamsToErrorCatch = [file, cipher, fileWriteStream]; + + await awaitUploadStreamFS(file.pipe(cipher), fileWriteStream, req, metadata.filePath, totalStreamsToErrorCatch); + + const date = new Date(); + const encryptedFileSize = await getFileSize(metadata.filePath); + + const currentFile = new File({ + filename, + uploadDate: date.toISOString(), + length: encryptedFileSize, + metadata + }); + + await currentFile.save(); + + console.log(currentFile); + + const imageCheck = imageChecker(currentFile.filename); + + if (currentFile.length < 15728640 && imageCheck) { + + const updatedFile = await createThumbnailFS(currentFile, filename, user); + + return updatedFile; + + } else { + + return currentFile; + } + + } + + downloadFile = async(user: UserInterface, fileID: string, res: Response) => { + + const currentFile: FileInterface = await dbUtilsFile.getFileInfo(fileID, user._id); + + if (!currentFile) throw new NotFoundError("Download File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const filePath = currentFile.metadata.filePath!; + + const IV = currentFile.metadata.IV.buffer as Buffer; + + const readStream = fs.createReadStream(filePath); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + currentFile.filename + '"'); + res.set('Content-Length', currentFile.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + } + + getThumbnail = async(user: UserInterface, id: string) => { + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const thumbnail = await Thumbnail.findById(id) as ThumbnailInterface; + + if (thumbnail.owner !== user._id.toString()) { + + throw new NotAuthorizedError('Thumbnail Unauthorized Error'); + } + + const iv = thumbnail.IV!; + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv); + + const readStream = fs.createReadStream(thumbnail.path!); + + const allStreamsToErrorCatch = [readStream, decipher]; + + const bufferData = await streamToBuffer(readStream.pipe(decipher), allStreamsToErrorCatch); + + return bufferData; + + } + + getFullThumbnail = async(user: UserInterface, fileID: string, res: Response) => { + + const userID = user._id; + + const file: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("File Thumbnail Not Found"); + + const password = user.getEncryptionKey(); + const IV = file.metadata.IV.buffer as Buffer; + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const readStream = fs.createReadStream(file.metadata.filePath!); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + console.log("Sending Full Thumbnail...") + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + console.log("Full thumbnail sent"); + } + + streamVideo = async(user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => { + + const userID = user._id; + const currentFile: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!currentFile) throw new NotFoundError("Video File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + 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 IV = currentFile.metadata.IV.buffer as Buffer; + const chunksize = (end-start)+1 + + let head = { + 'Content-Range': 'bytes ' + start + '-' + end + '/' + fileSize, + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize, + 'Content-Type': 'video/mp4'} + + let currentIV = IV; + + let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + + if (+start === 0) { + + fixedStart = 0; + } + + const fixedEnd = currentFile.length; + + const differenceStart = start - fixedStart; + + if (fixedStart !== 0 && start !== 0) { + + currentIV = await getPrevIVFS(fixedStart - 16, currentFile.metadata.filePath!) as Buffer; + } + + const readStream = fs.createReadStream(currentFile.metadata.filePath!, { + start: fixedStart, + end: fixedEnd, + }); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, currentIV); + + decipher.setAutoPadding(false); + + res.writeHead(206, head); + + const allStreamsToErrorCatch = [readStream, decipher]; + + readStream.pipe(decipher); + + // req.on("close", () => { + // // console.log("req closed"); + // readStream.destroy(); + // }) + + const tempUUID = req.params.uuid; + + await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); + console.log("Video Stream Finished"); + readStream.destroy(); + } + + getPublicDownload = async(fileID: string, tempToken: any, res: Response) => { + + const file: FileInterface = await dbUtilsFile.getPublicFile(fileID); + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + throw new NotAuthorizedError("File Not Public"); + } + + const user = await User.findById(file.metadata.owner) as UserInterface; + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key"); + + const IV = file.metadata.IV.buffer as Buffer; + + const readStream = fs.createReadStream(file.metadata.filePath!); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + + if (file.metadata.linkType === "one") { + console.log("removing public link"); + await dbUtilsFile.removeOneTimePublicLink(fileID); + } + } + + deleteFile = async(userID: string, fileID: string) => { + + const file: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("Delete File Not Found Error"); + + if (file.metadata.thumbnailID) { + + const thumbnail = await Thumbnail.findById(file.metadata.thumbnailID) as ThumbnailInterface; + const thumbnailPath = thumbnail.path!; + await removeChunksFS(thumbnailPath); + + await Thumbnail.deleteOne({_id: file.metadata.thumbnailID}); + } + + await removeChunksFS(file.metadata.filePath!); + await File.deleteOne({_id: file._id}); + } + + deleteFolder = async(userID: string, folderID: string, parentList: string[]) => { + + const parentListString = parentList.toString() + + await Folder.deleteMany({"owner": userID, "parentList": { $all: parentList}}) + await Folder.deleteMany({"owner": userID, "_id": folderID}); + + const fileList = await dbUtilsFile.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) { + + const thumbnail = await Thumbnail.findById(currentFile.metadata.thumbnailID) as ThumbnailInterface; + const thumbnailPath = thumbnail.path!; + await removeChunksFS(thumbnailPath); + + await Thumbnail.deleteOne({_id: currentFile.metadata.thumbnailID}); + } + + await removeChunksFS(currentFile.metadata.filePath!); + await File.deleteOne({_id: currentFile._id}); + + } catch (e) { + + console.log("Could not delete file", currentFile.filename, currentFile._id); + } + + } + } + + deleteAll = async(userID: string) => { + + console.log("remove all request") + + await Folder.deleteMany({"owner": userID}); + + const fileList = await dbUtilsFile.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) { + + const thumbnail = await Thumbnail.findById(currentFile.metadata.thumbnailID) as ThumbnailInterface; + const thumbnailPath = thumbnail.path!; + await removeChunksFS(thumbnailPath); + + await Thumbnail.deleteOne({_id: currentFile.metadata.thumbnailID}); + } + + await removeChunksFS(currentFile.metadata.filePath!); + await File.deleteOne({_id: currentFile._id}); + + } catch (e) { + + console.log("Could Not Remove File", currentFile.filename, currentFile._id); + } + } + } +} + +export default FileSystemService; \ No newline at end of file diff --git a/backend/services/ChunkService/MongoService.ts b/backend/services/ChunkService/MongoService.ts new file mode 100644 index 0000000..f18cf29 --- /dev/null +++ b/backend/services/ChunkService/MongoService.ts @@ -0,0 +1,382 @@ +import mongoose from "../../db/mongoose"; +import { Response, Request } from "express"; +import DbUtilFolder from "../../db/utils/folderUtils"; +import DbUtilFile from "../../db/utils/fileUtils"; +import crypto from "crypto"; +import videoChecker from "../../utils/videoChecker" +import imageChecker from "../../utils/imageChecker" +import { ObjectID } from "mongodb"; +import createThumbnail from "./utils/createThumbnail"; +import Thumbnail, {ThumbnailInterface} from "../../models/thumbnail"; +import NotAuthorizedError from "../../utils/NotAuthorizedError"; +import NotFoundError from "../../utils/NotFoundError"; +import {GridFSBucketWriteStream} from "mongodb"; +import awaitStream from "./utils/awaitStream"; +import awaitUploadStream from "./utils/awaitUploadStream"; +import User, { UserInterface } from "../../models/user"; +import Folder from "../../models/folder"; +import { FileInterface } from "../../models/file"; +import getBusboyData from "./utils/getBusboyData"; +import ChunkInterface from "./utils/ChunkInterface"; +import fixStartChunkLength from "./utils/fixStartChunkLength"; +import getPrevIVMongo from "./utils/getPrevIVMongo"; +import awaitStreamVideo from "./utils/awaitStreamVideo"; + +const conn = mongoose.connection; + +const dbUtilsFile = new DbUtilFile(); + +class MongoService implements ChunkInterface { + + constructor() { + + } + + uploadFile = async(user: UserInterface, busboy: any, req: Request) => { + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + let bucketStream: GridFSBucketWriteStream; + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const cipher = crypto.createCipheriv('aes256', CIPHER_KEY, initVect); + + const {file, filename, formData} = await getBusboyData(busboy); + + 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); + + bucketStream = bucket.openUploadStream(filename, {metadata}); + + const allStreamsToErrorCatch = [file, cipher, bucketStream]; + + const finishedFile = await awaitUploadStream(file.pipe(cipher), bucketStream, req, allStreamsToErrorCatch) as FileInterface; + + const imageCheck = imageChecker(filename); + + if (finishedFile.length < 15728640 && imageCheck) { + + const updatedFile = await createThumbnail(finishedFile, filename, user); + + return updatedFile; + + } else { + + return finishedFile; + } + } + + downloadFile = async(user: UserInterface, fileID: string, res: Response) => { + + const currentFile = await dbUtilsFile.getFileInfo(fileID, user._id); + + if (!currentFile) throw new NotFoundError("Download File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const bucket = new mongoose.mongo.GridFSBucket(conn.db); + + const IV = currentFile.metadata.IV.buffer as Buffer; + const readStream = bucket.openDownloadStream(new ObjectID(fileID)); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + currentFile.filename + '"'); + res.set('Content-Length', currentFile.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + } + + getThumbnail = async(user: UserInterface, id: string) => { + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const thumbnail = await Thumbnail.findById(id) as ThumbnailInterface; + + if (thumbnail.owner !== user._id.toString()) { + + 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()]); + + return decryptedThumbnail; + } + + getFullThumbnail = async(user: UserInterface, fileID: string, res: Response) => { + + const userID = user._id; + + const file = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("File Thumbnail Not Found"); + + const bucket = new mongoose.mongo.GridFSBucket(conn.db); + const password = user.getEncryptionKey(); + const IV = file.metadata.IV.buffer as Buffer; + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const readStream = bucket.openDownloadStream(new ObjectID(fileID)) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size.toString()); + + console.log("Sending Full Thumbnail...") + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + console.log("Full thumbnail sent"); + } + + getPublicDownload = async(fileID: string, tempToken: any, res: Response) => { + + const file = await dbUtilsFile.getPublicFile(fileID); + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + throw new NotAuthorizedError("File Not Public"); + } + + const user = await User.findById(file.metadata.owner) as UserInterface; + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key"); + + const bucket = new mongoose.mongo.GridFSBucket(conn.db); + + const IV = file.metadata.IV.buffer as Buffer; + + const readStream = bucket.openDownloadStream(new ObjectID(fileID)) + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + + if (file.metadata.linkType === "one") { + console.log("removing public link"); + await dbUtilsFile.removeOneTimePublicLink(fileID); + } + } + + streamVideo = async(user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => { + + const userID = user._id; + const currentFile = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!currentFile) throw new NotFoundError("Video File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + 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 as Buffer; + + let head = { + 'Content-Range': 'bytes ' + start + '-' + end + '/' + fileSize, + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize, + 'Content-Type': 'video/mp4'} + + let currentIV = IV; + + let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + + if (+start === 0) { + + fixedStart = 0; + } + + const fixedEnd = currentFile.length; + + const differenceStart = start - fixedStart; + + if (fixedStart !== 0 && start !== 0) { + + currentIV = await getPrevIVMongo(fixedStart - 16, fileID) as Buffer; + } + + const bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 + }); + + const readStream = bucket.openDownloadStream(new ObjectID(fileID), { + start: fixedStart, + end: fixedEnd, + }); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, currentIV); + decipher.setAutoPadding(false); + + res.writeHead(206, head); + + const allStreamsToErrorCatch = [readStream, decipher]; + + readStream.pipe(decipher); + + // req.on("close", () => { + // // console.log("req closed"); + // readStream.destroy(); + // }) + + + const tempUUID = req.params.uuid; + + await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); + readStream.destroy(); + } + + deleteFile = async(userID: string, fileID: string) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255, + }); + + 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}); + } + + await bucket.delete(new ObjectID(fileID)); + } + + deleteFolder = async(userID: string, folderID: string, parentList: string[]) => { + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + const parentListString = parentList.toString() + + await Folder.deleteMany({"owner": userID, "parentList": { $all: parentList}}) + await Folder.deleteMany({"owner": userID, "_id": folderID}); + + const fileList = await dbUtilsFile.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}); + } + + await bucket.delete(new ObjectID(currentFile._id)); + + } catch (e) { + + console.log("Could not delete file", currentFile.filename, currentFile._id); + } + + } + } + + deleteAll = async(userID: string) => { + + console.log("remove all request") + + let bucket = new mongoose.mongo.GridFSBucket(conn.db, { + chunkSizeBytes: 1024 * 255 + }); + + await Folder.deleteMany({"owner": userID}); + + const fileList = await dbUtilsFile.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}) + } + + await bucket.delete(new ObjectID(currentFile._id)); + + } catch (e) { + + console.log("Could Not Remove File", currentFile.filename, currentFile._id); + } + } + } +} + +export default MongoService; \ No newline at end of file diff --git a/backend/services/ChunkService/S3Service.ts b/backend/services/ChunkService/S3Service.ts new file mode 100644 index 0000000..694fc99 --- /dev/null +++ b/backend/services/ChunkService/S3Service.ts @@ -0,0 +1,429 @@ +import File, { FileInterface } from "../../models/file"; +import User, {UserInterface} from "../../models/user"; +import s3 from "../../db/s3"; +import env from "../../enviroment/env"; +import { Response, Request } from "express"; +import ChunkInterface from "./utils/ChunkInterface"; +import NotAuthorizedError from "../../utils/NotAuthorizedError"; +import NotFoundError from "../../utils/NotFoundError"; +import crypto from "crypto"; +import getBusboyData from "./utils/getBusboyData"; +import videoChecker from "../../utils/videoChecker"; +import uuid from "uuid"; +import awaitUploadStreamS3 from "./utils/awaitUploadStreamS3"; +import awaitStream from "./utils/awaitStream"; +import createThumbnailS3 from "./utils/createThumbnailS3"; +import imageChecker from "../../utils/imageChecker"; +import Thumbnail, {ThumbnailInterface} from "../../models/thumbnail"; +import streamToBuffer from "../../utils/streamToBuffer"; +import removeChunksS3 from "./utils/removeChunksS3"; +import fixStartChunkLength from "./utils/fixStartChunkLength"; +import fixEndChunkLength from "./utils/fixEndChunkLength"; +import getPrevIVS3 from "./utils/getPrevIVS3"; +import awaitStreamVideo from "./utils/awaitStreamVideo"; +import Folder from "../../models/folder"; + +import DbUtilFile from "../../db/utils/fileUtils/index"; +const dbUtilsFile = new DbUtilFile(); + +class S3Service implements ChunkInterface { + + constructor() { + + } + + uploadFile = async(user: UserInterface, busboy: any, req: Request) => { + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const initVect = crypto.randomBytes(16); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const cipher = crypto.createCipheriv('aes256', CIPHER_KEY, initVect); + + const {file, filename, formData} = await getBusboyData(busboy); + + 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 randomS3ID = uuid.v4(); + + const metadata = { + owner: user._id, + parent, + parentList, + hasThumbnail, + thumbnailID, + isVideo, + size, + IV: initVect, + s3ID: randomS3ID + } + + const params = { + Bucket: env.s3Bucket, + Body : file.pipe(cipher), + Key : randomS3ID + }; + + await awaitUploadStreamS3(params); + + const date = new Date(); + const encryptedFileSize = size; + + const currentFile = new File({ + filename, + uploadDate: date.toISOString(), + length: encryptedFileSize, + metadata + }); + + await currentFile.save(); + + const imageCheck = imageChecker(currentFile.filename); + + if (currentFile.length < 15728640 && imageCheck) { + + console.log("Creating thumbnail...") + const updatedFile = await createThumbnailS3(currentFile, filename, user); + + return updatedFile; + + } else { + + return currentFile; + } + } + + downloadFile = async(user: UserInterface, fileID: string, res: Response) => { + + const currentFile: FileInterface = await dbUtilsFile.getFileInfo(fileID, user._id); + + if (!currentFile) throw new NotFoundError("Download File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const IV = currentFile.metadata.IV.buffer as Buffer; + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + currentFile.filename + '"'); + res.set('Content-Length', currentFile.metadata.size.toString()); + + const params: any = {Bucket: env.s3Bucket, Key: currentFile.metadata.s3ID!}; + + const s3ReadStream = s3.getObject(params).createReadStream(); + + const allStreamsToErrorCatch = [s3ReadStream, decipher]; + + await awaitStream(s3ReadStream.pipe(decipher), res, allStreamsToErrorCatch); + + } + + streamVideo = async(user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => { + + const userID = user._id; + const currentFile: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!currentFile) throw new NotFoundError("Video File Not Found"); + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + 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 as Buffer; + + let head = { + 'Content-Range': 'bytes ' + start + '-' + end + '/' + fileSize, + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize, + 'Content-Type': 'video/mp4'} + + let currentIV = IV; + + let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start); + + if (+start === 0) { + + fixedStart = 0; + } + + const fixedEnd = fileSize % 16 === 0 ? fileSize : fixEndChunkLength(fileSize); //end % 16 === 0 ? end + 15: (fixEndChunkLength(end) - 1) + 16; + + const differenceStart = start - fixedStart; + + if (fixedStart !== 0 && start !== 0) { + + currentIV = await getPrevIVS3(fixedStart - 16, currentFile.metadata.s3ID!) as Buffer; + } + + const params: any = {Bucket: env.s3Bucket, Key: currentFile.metadata.s3ID!, Range: `bytes=${fixedStart}-${fixedEnd}`}; + + const s3ReadStream = s3.getObject(params).createReadStream(); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, currentIV); + + res.writeHead(206, head); + + const allStreamsToErrorCatch = [s3ReadStream, decipher]; + + s3ReadStream.pipe(decipher); + + const tempUUID = req.params.uuid; + + // s3ReadStream.on("data", () => { + // console.log("data", tempUUID); + // }) + + + // req.on("close", () => { + // // console.log("Destoying read stream"); + // // s3ReadStream.destroy(); + // // console.log("Read Stream Destroyed"); + // }) + + // req.on("end", () => { + // console.log("ending stream"); + // s3ReadStream.destroy(); + // console.log("ended stream") + // }) + + // req.on("error", () => { + // console.log("req error"); + // }) + + // req.on("pause", () => { + // console.log("req pause") + // }) + + // req.on("close", () => { + // // console.log("req closed"); + // s3ReadStream.destroy(); + // }) + + //req.on("") + + // req.on("end", () => { + // console.log("req end"); + // }) + + await awaitStreamVideo(start, end, differenceStart, decipher, res, req, tempUUID, allStreamsToErrorCatch); + console.log("Video stream finished"); + s3ReadStream.destroy(); + } + + getThumbnail = async(user: UserInterface, id: string) => { + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const thumbnail = await Thumbnail.findById(id) as ThumbnailInterface; + + if (thumbnail.owner !== user._id.toString()) { + + throw new NotAuthorizedError('Thumbnail Unauthorized Error'); + } + + const iv = thumbnail.IV!; + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, iv); + + const params: any = {Bucket: env.s3Bucket, Key: thumbnail.s3ID!}; + + const readStream = s3.getObject(params).createReadStream(); + + const allStreamsToErrorCatch = [readStream, decipher]; + + const bufferData = await streamToBuffer(readStream.pipe(decipher), allStreamsToErrorCatch); + + return bufferData; + } + + getFullThumbnail = async(user: UserInterface, fileID: string, res: Response) => { + + const userID = user._id; + + const file: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("File Thumbnail Not Found"); + + const password = user.getEncryptionKey(); + const IV = file.metadata.IV.buffer as Buffer; + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key") + + const params: any = {Bucket: env.s3Bucket, Key: file.metadata.s3ID!}; + + const readStream = s3.getObject(params).createReadStream(); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + console.log("Sending Full Thumbnail...") + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + console.log("Full thumbnail sent"); + } + + getPublicDownload = async(fileID: string, tempToken: any, res: Response) => { + + const file: FileInterface = await dbUtilsFile.getPublicFile(fileID); + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + throw new NotAuthorizedError("File Not Public"); + } + + const user = await User.findById(file.metadata.owner) as UserInterface; + + const password = user.getEncryptionKey(); + + if (!password) throw new NotAuthorizedError("Invalid Encryption Key"); + + const IV = file.metadata.IV.buffer as Buffer; + + const params: any = {Bucket: env.s3Bucket, Key: file.metadata.s3ID!}; + + const readStream = s3.getObject(params).createReadStream(); + + const CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV); + + res.set('Content-Type', 'binary/octet-stream'); + res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); + res.set('Content-Length', file.metadata.size.toString()); + + const allStreamsToErrorCatch = [readStream, decipher]; + + await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch); + + if (file.metadata.linkType === "one") { + console.log("removing public link"); + await dbUtilsFile.removeOneTimePublicLink(fileID); + } + } + + deleteFile = async(userID: string, fileID: string) => { + + const file: FileInterface = await dbUtilsFile.getFileInfo(fileID, userID); + + if (!file) throw new NotFoundError("Delete File Not Found Error"); + + if (file.metadata.thumbnailID) { + + const thumbnail = await Thumbnail.findById(file.metadata.thumbnailID) as ThumbnailInterface; + const paramsThumbnail: any = {Bucket: env.s3Bucket, Key: thumbnail.s3ID!}; + await removeChunksS3(paramsThumbnail); + await Thumbnail.deleteOne({_id: file.metadata.thumbnailID}); + } + + const params: any = {Bucket: env.s3Bucket, Key: file.metadata.s3ID!}; + await removeChunksS3(params); + await File.deleteOne({_id: file._id}); + } + + deleteFolder = async(userID: string, folderID: string, parentList: string[]) => { + + const parentListString = parentList.toString() + + await Folder.deleteMany({"owner": userID, "parentList": { $all: parentList}}) + await Folder.deleteMany({"owner": userID, "_id": folderID}); + + const fileList = await dbUtilsFile.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) { + + const thumbnail = await Thumbnail.findById(currentFile.metadata.thumbnailID) as ThumbnailInterface; + const paramsThumbnail: any = {Bucket: env.s3Bucket, Key: thumbnail.s3ID!}; + await removeChunksS3(paramsThumbnail); + await Thumbnail.deleteOne({_id: currentFile.metadata.thumbnailID}); + } + + const params: any = {Bucket: env.s3Bucket, Key: currentFile.metadata.s3ID!}; + await removeChunksS3(params); + await File.deleteOne({_id: currentFile._id}); + + } catch (e) { + + console.log("Could not delete file", currentFile.filename, currentFile._id); + } + + } + } + + deleteAll = async(userID: string) => { + + console.log("remove all request") + + await Folder.deleteMany({"owner": userID}); + + const fileList = await dbUtilsFile.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) { + + const thumbnail = await Thumbnail.findById(currentFile.metadata.thumbnailID) as ThumbnailInterface; + const paramsThumbnail: any = {Bucket: env.s3Bucket, Key: thumbnail.s3ID!}; + await removeChunksS3(paramsThumbnail); + await Thumbnail.deleteOne({_id: currentFile.metadata.thumbnailID}); + } + + const params: any = {Bucket: env.s3Bucket, Key: currentFile.metadata.s3ID!}; + await removeChunksS3(params); + await File.deleteOne({_id: currentFile._id}); + + } catch (e) { + + console.log("Could Not Remove File", currentFile.filename, currentFile._id); + } + } + } +} + +export default S3Service; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/ChunkInterface.ts b/backend/services/ChunkService/utils/ChunkInterface.ts new file mode 100644 index 0000000..1d48fc5 --- /dev/null +++ b/backend/services/ChunkService/utils/ChunkInterface.ts @@ -0,0 +1,14 @@ +import { UserInterface } from "../../../models/user"; +import { FileInterface } from "../../../models/file"; +import { Request, Response } from "express"; + +interface ChunkInterface { + uploadFile: (user: UserInterface, busboy: any, req: Request) => Promise; + downloadFile: (user: UserInterface, fileID: string, res: Response) => void; + getThumbnail: (user: UserInterface, id: string) => Promise; + getFullThumbnail: (user: UserInterface, fileID: string, res: Response) => void; + getPublicDownload: (fileID: string, tempToken: any, res: Response) => void; + streamVideo: (user: UserInterface, fileID: string, headers: any, res: Response, req: Request) => void; +} + +export default ChunkInterface; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/awaitStream.ts b/backend/services/ChunkService/utils/awaitStream.ts new file mode 100644 index 0000000..1b01c8d --- /dev/null +++ b/backend/services/ChunkService/utils/awaitStream.ts @@ -0,0 +1,24 @@ +const awaitStream = (inputSteam: any, outputStream: any, allStreamsToErrorCatch: any[]) => { + + return new Promise((resolve, reject) => { + + allStreamsToErrorCatch.forEach((currentStream: any) => { + + currentStream.on("error", (e: Error) => { + reject({ + message: "Await Stream Input Error", + code: 500, + error: e + }) + }) + + }) + + inputSteam.pipe(outputStream).on("finish", (data: T) => { + console.log("await stream finished") + resolve(data); + }) + }) +} + +export default awaitStream; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/awaitStreamVideo.ts b/backend/services/ChunkService/utils/awaitStreamVideo.ts new file mode 100644 index 0000000..f9c89a3 --- /dev/null +++ b/backend/services/ChunkService/utils/awaitStreamVideo.ts @@ -0,0 +1,124 @@ +import {Response, Request} from "express" +import tempStorage from "../../../tempStorage/tempStorage"; +import uuid from "uuid"; + +const awaitStreamVideo = (start: number, end:number, differenceStart: number, + decipher: any, res: Response, req: Request, tempUUID: string, streamsToErrorCatch: any[]) => { + + const currentUUID = uuid.v4(); + tempStorage[tempUUID] = currentUUID; + + return new Promise((resolve, reject) => { + + let firstBytesRemoved = false; + //let sizeCounter = 0; + + req.on("close", () => { + //console.log("close resolved"); + streamsToErrorCatch.forEach((stream) => { + stream.destroy(); + }) + resolve(); + }) + + req.on("end", () => { + //console.log("End resolved"); + resolve(); + }) + + decipher.on("data", (data: Buffer | string) => { + + //console.log("check", tempStorage[tempUUID]); + //console.log("check b", tempStorage[tempUUID] !== currentUUID && tempStorage[tempUUID] !== undefined); + + //console.log(tempStorage[tempUUID] !== currentUUID, tempStorage[tempUUID]) + if (tempStorage[tempUUID] !== currentUUID) { + + //console.log("New Stream Requested, Desroying old stream"); + + streamsToErrorCatch.forEach((stream) => { + stream.destroy(); + }) + + //console.log("Old Stream Desroyed"); + //console.log("Old Stream Resolved"); + resolve(); + //delete tempStorage[tempUUID]; + } + + //console.log("data", currentUUID); + + //console.log("stream passed", currentUUID); + + // if (tempStorage[tempUUID] !== undefined && tempStorage[tempUUID] !== currentUUID) { + + // console.log("New Stream Requested, Desroying old stream"); + // streamsToErrorCatch[0].destroy(); + // console.log("Old Stream Desroyed"); + // delete tempStorage[tempUUID]; + + // } else { + + // tempStorage[tempUUID] = currentUUID; + // } + + + if (+start === 0 && +end === 1) { + + const dataCoverted = data.toString("hex"); + + let neededData = dataCoverted.substring(0, 4); + + const dataBack = Buffer.from(neededData, "hex"); + + res.write(dataBack); + //res.flush(); + return; + } + + if (!firstBytesRemoved) { + + const dataCoverted = data.toString("hex"); + + let neededData = dataCoverted.substring(differenceStart * 2); + + const dataBack = Buffer.from(neededData, "hex"); + + firstBytesRemoved = true; + + //sizeCounter += dataBack.length; + + res.write(dataBack); + //res.flush(); + return; + } + + + res.write(data); + //res.flush(); + //sizeCounter += data.length; + }) + + decipher.on("end", () => { + //console.log("decipher resolved"); + res.end(); + resolve(); + }) + + streamsToErrorCatch.forEach((currentStream) => { + + currentStream.on("error", (e: Error) => { + + reject({ + message: "Await Video Stream Input Error", + code: 500, + error: e + }) + + }) + }) + + }) +} + +export default awaitStreamVideo; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/awaitUploadStream.ts b/backend/services/ChunkService/utils/awaitUploadStream.ts new file mode 100644 index 0000000..f0c2540 --- /dev/null +++ b/backend/services/ChunkService/utils/awaitUploadStream.ts @@ -0,0 +1,35 @@ +import removeChunks from "./removeChunks"; +import {Request} from "express" + +const awaitUploadStream = (inputSteam: any, outputStream: any, req: Request, allStreamsToErrorCatch: any[]) => { + + return new Promise((resolve, reject) => { + + allStreamsToErrorCatch.forEach((currentStream: any) => { + + currentStream.on("error", (e: Error) => { + + removeChunks(outputStream); + + reject({ + message: "Await Stream Input Error", + code: 500, + error: e + }) + }) + }) + + req.on("aborted", () => { + + console.log("Upload Request Cancelling..."); + + removeChunks(outputStream); + }) + + inputSteam.pipe(outputStream).on("finish", (data: T) => { + resolve(data); + }) + }) +} + +export default awaitUploadStream; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/awaitUploadStreamFS.ts b/backend/services/ChunkService/utils/awaitUploadStreamFS.ts new file mode 100644 index 0000000..699a49f --- /dev/null +++ b/backend/services/ChunkService/utils/awaitUploadStreamFS.ts @@ -0,0 +1,35 @@ +import {Request} from "express" +import removeChunksFS from "./removeChunksFS"; + +const awaitUploadStream = (inputSteam: any, outputStream: any, req: Request, path: string, allStreamsToCatchError: any[]) => { + + return new Promise((resolve, reject) => { + + allStreamsToCatchError.forEach((currentStream: any) => { + + currentStream.on("error", (e: Error) => { + + removeChunksFS(path); + + reject({ + message: "Await Stream Input Error", + code: 500, + error: e + }) + }) + }) + + req.on("aborted", () => { + + console.log("Upload Request Cancelling..."); + + removeChunksFS(path); + }) + + inputSteam.pipe(outputStream).on("finish", (data: T) => { + resolve(data); + }) + }) +} + +export default awaitUploadStream; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/awaitUploadStreamS3.ts b/backend/services/ChunkService/utils/awaitUploadStreamS3.ts new file mode 100644 index 0000000..8b457a8 --- /dev/null +++ b/backend/services/ChunkService/utils/awaitUploadStreamS3.ts @@ -0,0 +1,19 @@ +import s3 from "../../../db/s3"; + +const awaitUploadStreamS3 = (params: any) => { + + return new Promise((resolve, reject) => { + + s3.upload(params, (err: any, data: any) => { + + if (err) { + reject("Amazon upload error"); + } + + resolve(); + }) + + }) +} + +export default awaitUploadStreamS3; \ No newline at end of file diff --git a/backend/services/FileService/utils/createThumbnail.js b/backend/services/ChunkService/utils/createThumbnail.ts similarity index 62% rename from backend/services/FileService/utils/createThumbnail.js rename to backend/services/ChunkService/utils/createThumbnail.ts index 8a43a3f..165ebe4 100644 --- a/backend/services/FileService/utils/createThumbnail.js +++ b/backend/services/ChunkService/utils/createThumbnail.ts @@ -1,69 +1,69 @@ -const mongoose = require("../../../db/mongoose") +import mongoose from "../../../db/mongoose"; +import crypto from "crypto"; +import Thumbnail from "../../../models/thumbnail"; +import { ObjectID } from "mongodb"; +import sharp from "sharp"; +import concat from "concat-stream"; +import { FileInterface } from "../../../models/file"; +import { UserInterface } from "../../../models/user"; + 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) => { +const createThumbnail = (file: FileInterface, filename: string, user: UserInterface) => { - return new Promise((resolve) => { + return new Promise((resolve) => { const password = user.getEncryptionKey(); - let CIPHER_KEY = crypto.createHash('sha256').update(password).digest() + 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)) + const readStream = bucket.openDownloadStream(new ObjectID(file._id)) - readStream.on("error", (e) => { + readStream.on("error", (e: Error) => { console.log("File service upload thumbnail error", e); resolve(file); }) const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, file.metadata.IV); - decipher.on("error", (e) => { + decipher.on("error", (e: Error) => { console.log("File service upload thumbnail decipher error", e); resolve(file) }) try { - const concatStream = concat(async(bufferData) => { + const concatStream = concat(async(bufferData: Buffer) => { - const thumbnailIV = crypto.randomBytes(16); - - //CIPHER_KEY = crypto.createHash('sha256').update(process.env.newPassword).digest() + const thumbnailIV = crypto.randomBytes(16); const thumbnailCipher = crypto.createCipheriv("aes256", CIPHER_KEY, thumbnailIV); - bufferData = Buffer.concat([thumbnailIV, thumbnailCipher.update(bufferData), thumbnailCipher.final()]); //thumbnailCipher.update(bufferData) + 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") + const getUpdatedFile = await conn.db.collection("fs.files") .findOneAndUpdate({"_id": file._id}, {"$set": {"metadata.hasThumbnail": true, "metadata.thumbnailID": thumbnailModel._id}}) - updatedFile = updatedFile.value; + let updatedFile: FileInterface = getUpdatedFile.value; - updatedFile = {...updatedFile, metadata: {...updatedFile.metadata, hasThumbnail: true, thumbnailID: thumbnailModel._id}} + updatedFile = {...updatedFile, metadata: {...updatedFile.metadata, hasThumbnail: true, thumbnailID: thumbnailModel._id}} as FileInterface; resolve(updatedFile); - }).on("error", (e) => { + }).on("error", (e: Error) => { console.log("File service upload concat stream error", e); resolve(file); }) - const imageResize = sharp().resize(300).on("error", (e) => { + const imageResize = sharp().resize(300).on("error", (e: Error) => { console.log("resize error", e); resolve(file); @@ -81,4 +81,4 @@ const createThumbnail = async(file, filename, user) => { }) } -module.exports = createThumbnail; \ No newline at end of file +export default createThumbnail; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/createThumbnailFS.ts b/backend/services/ChunkService/utils/createThumbnailFS.ts new file mode 100644 index 0000000..e1d414f --- /dev/null +++ b/backend/services/ChunkService/utils/createThumbnailFS.ts @@ -0,0 +1,81 @@ +import mongoose from "../../../db/mongoose"; +import crypto from "crypto"; +import Thumbnail from "../../../models/thumbnail"; +import sharp from "sharp"; +import {FileInterface} from "../../../models/file"; +import {UserInterface} from "../../../models/user"; +import fs from "fs"; +import uuid from "uuid"; +import env from "../../../enviroment/env"; + +const conn = mongoose.connection; + +const createThumbnailFS = (file: FileInterface, filename: string, user: UserInterface) => { + + return new Promise((resolve) => { + + const password = user.getEncryptionKey(); + + let CIPHER_KEY = crypto.createHash('sha256').update(password!).digest() + + const thumbnailFilename = uuid.v4(); + + const readStream = fs.createReadStream(file.metadata.filePath!); + const writeStream = fs.createWriteStream(env.fsDirectory + thumbnailFilename); + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, file.metadata.IV); + + readStream.on("error", (e: Error) => { + console.log("File service upload thumbnail error", e); + resolve(file); + }) + + writeStream.on("error", (e: Error) => { + console.log("File service upload write thumbnail error", e); + resolve(file); + }) + + decipher.on("error", (e: Error) => { + console.log("File service upload thumbnail decipher error", e); + resolve(file) + }) + + try { + + + const thumbnailIV = crypto.randomBytes(16); + + const thumbnailCipher = crypto.createCipheriv("aes256", CIPHER_KEY, thumbnailIV); + + const imageResize = sharp().resize(300).on("error", (e: Error) => { + + console.log("resize error", e); + resolve(file); + }) + + readStream.pipe(decipher).pipe(imageResize).pipe(thumbnailCipher).pipe(writeStream); + + writeStream.on("finish", async() => { + + const thumbnailModel = new Thumbnail({name: filename, owner: user._id, IV: thumbnailIV, path: env.fsDirectory + thumbnailFilename}); + + await thumbnailModel.save(); + + const getUpdatedFile = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": file._id}, {"$set": {"metadata.hasThumbnail": true, "metadata.thumbnailID": thumbnailModel._id}}) + + let updatedFile: FileInterface = getUpdatedFile.value; + + updatedFile = {...updatedFile, metadata: {...updatedFile.metadata, hasThumbnail: true, thumbnailID: thumbnailModel._id}} as FileInterface + + resolve(updatedFile); + }) + + } catch (e) { + + console.log("Thumbnail error", e); + resolve(file); + } + }) +} + +export default createThumbnailFS; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/createThumbnailS3.ts b/backend/services/ChunkService/utils/createThumbnailS3.ts new file mode 100644 index 0000000..6e6f922 --- /dev/null +++ b/backend/services/ChunkService/utils/createThumbnailS3.ts @@ -0,0 +1,85 @@ +import mongoose from "../../../db/mongoose"; +import crypto from "crypto"; +import Thumbnail from "../../../models/thumbnail"; +import sharp from "sharp"; +import { FileInterface } from "../../../models/file"; +import { UserInterface } from "../../../models/user"; +import uuid from "uuid"; +import env from "../../../enviroment/env"; +import s3 from "../../../db/s3"; + +const conn = mongoose.connection; + +const createThumbnailS3 = (file: FileInterface, filename: string, user: UserInterface) => { + + return new Promise((resolve, reject) => { + + const password = user.getEncryptionKey(); + + let CIPHER_KEY = crypto.createHash('sha256').update(password!).digest() + + const thumbnailFilename = uuid.v4(); + + const params: any = {Bucket: env.s3Bucket, Key: file.metadata.s3ID!}; + + const readStream = s3.getObject(params).createReadStream(); + + const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, file.metadata.IV); + + readStream.on("error", (e: Error) => { + console.log("File service upload thumbnail error", e); + resolve(file); + }) + + decipher.on("error", (e: Error) => { + console.log("File service upload thumbnail decipher error", e); + resolve(file) + }) + + try { + + const thumbnailIV = crypto.randomBytes(16); + const thumbnailCipher = crypto.createCipheriv("aes256", CIPHER_KEY, thumbnailIV); + + const imageResize = sharp().resize(300).on("error", (e: Error) => { + + console.log("resize error", e); + resolve(file); + }) + + const paramsWrite: any = { + Bucket: env.s3Bucket, + Body : readStream.pipe(decipher).pipe(imageResize).pipe(thumbnailCipher), + Key : thumbnailFilename + }; + + s3.upload(paramsWrite, async(err: any, data: any) => { + + if (err) { + console.log("Amazon Upload Error", err); + reject("Amazon upload error"); + } + + const thumbnailModel = new Thumbnail({name: thumbnailFilename, owner: user._id, IV: thumbnailIV, s3ID: thumbnailFilename}); + + await thumbnailModel.save(); + + const getUpdatedFile = await conn.db.collection("fs.files") + .findOneAndUpdate({"_id": file._id}, {"$set": {"metadata.hasThumbnail": true, "metadata.thumbnailID": thumbnailModel._id}}) + + let updatedFile: FileInterface = getUpdatedFile.value; + + updatedFile = {...updatedFile, metadata: {...updatedFile.metadata, hasThumbnail: true, thumbnailID: thumbnailModel._id}} as FileInterface; + + resolve(updatedFile); + }) + + } catch (e) { + + console.log("Thumbnail error", e); + resolve(file); + } + }) +} + +export default createThumbnailS3; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/fixEndChunkLength.ts b/backend/services/ChunkService/utils/fixEndChunkLength.ts new file mode 100644 index 0000000..df87d0d --- /dev/null +++ b/backend/services/ChunkService/utils/fixEndChunkLength.ts @@ -0,0 +1,6 @@ +const fixEndChunkLength = (length: number) => { + + return Math.floor((length-1) / 16) * 16 + 16; +} + +export default fixEndChunkLength; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/fixStartChunkLength.ts b/backend/services/ChunkService/utils/fixStartChunkLength.ts new file mode 100644 index 0000000..ca34370 --- /dev/null +++ b/backend/services/ChunkService/utils/fixStartChunkLength.ts @@ -0,0 +1,6 @@ +const fixStartChunkLength = (length: number) => { + + return Math.floor((length-1) / 16) * 16 - 16; +} + +export default fixStartChunkLength; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/getBusboyData.ts b/backend/services/ChunkService/utils/getBusboyData.ts new file mode 100644 index 0000000..0166e6f --- /dev/null +++ b/backend/services/ChunkService/utils/getBusboyData.ts @@ -0,0 +1,33 @@ +import { Stream } from "stream"; + +const getBusboyData = (busboy: any) => { + + type dataType = { + file: Stream, + filename: string, + formData: Map + } + + return new Promise((resolve, reject) => { + + const formData = new Map(); + + busboy.on("field", (field: any, val: any) => { + + formData.set(field, val) + + }); + + busboy.on("file", async(_: string, file: Stream, filename: string) => { + + resolve({ + file, + filename, + formData + }) + }) + + }) +} + +export default getBusboyData; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/getFileSize.ts b/backend/services/ChunkService/utils/getFileSize.ts new file mode 100644 index 0000000..f493f6e --- /dev/null +++ b/backend/services/ChunkService/utils/getFileSize.ts @@ -0,0 +1,19 @@ +import fs from "fs"; + +const getFileSize = (path: string) => { + + return new Promise((resolve, reject) => { + + fs.stat(path, (error, stats) => { + + if (error) { + + resolve(0); + } + + resolve(stats.size); + }); + }) +} + +export default getFileSize; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/getPrevIVFS.ts b/backend/services/ChunkService/utils/getPrevIVFS.ts new file mode 100644 index 0000000..fbea067 --- /dev/null +++ b/backend/services/ChunkService/utils/getPrevIVFS.ts @@ -0,0 +1,19 @@ +import fs from "fs"; + +const getPrevIV = (start: number, path: string) => { + + return new Promise((resolve, reject) => { + + const stream = fs.createReadStream(path, { + start, + end: start + 15 + }) + + stream.on("data", (data) => { + + resolve(data); + }) + }) +} + +export default getPrevIV; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/getPrevIVMongo.ts b/backend/services/ChunkService/utils/getPrevIVMongo.ts new file mode 100644 index 0000000..0a47177 --- /dev/null +++ b/backend/services/ChunkService/utils/getPrevIVMongo.ts @@ -0,0 +1,23 @@ +import mongoose from "../../../db/mongoose"; +import { ObjectID } from "mongodb" +const conn = mongoose.connection; + +const getPrevIV = (start: number, fileID: string) => { + + return new Promise((resolve, reject) => { + + const bucket = new mongoose.mongo.GridFSBucket(conn.db); + + const stream = bucket.openDownloadStream(new ObjectID(fileID), { + start: start, + end: start + 16 + }); + + stream.on("data", (data) => { + + resolve(data); + }) + }) +} + +export default getPrevIV; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/getPrevIVS3.ts b/backend/services/ChunkService/utils/getPrevIVS3.ts new file mode 100644 index 0000000..3caa34b --- /dev/null +++ b/backend/services/ChunkService/utils/getPrevIVS3.ts @@ -0,0 +1,19 @@ +import s3 from "../../../db/s3"; +import env from "../../../enviroment/env"; + +const getPrevIV = (start: number, key: string) => { + + return new Promise((resolve, reject) => { + + const params: any = {Bucket: env.s3Bucket, Key: key, Range: `bytes=${start}-${start + 15}`}; + + const stream = s3.getObject(params).createReadStream(); + + stream.on("data", (data) => { + + resolve(data); + }) + }) +} + +export default getPrevIV; \ No newline at end of file diff --git a/backend/services/FileService/utils/removeChunks.js b/backend/services/ChunkService/utils/removeChunks.ts similarity index 63% rename from backend/services/FileService/utils/removeChunks.js rename to backend/services/ChunkService/utils/removeChunks.ts index 52bc691..b76befb 100644 --- a/backend/services/FileService/utils/removeChunks.js +++ b/backend/services/ChunkService/utils/removeChunks.ts @@ -1,11 +1,13 @@ -const DbUtilsFile = require("../../../db/utils/fileUtils"); +import { GridFSBucketWriteStream } from "mongodb"; +import DbUtilsFile from "../../../db/utils/fileUtils"; + const dbUtilsFile = new DbUtilsFile(); -const removeChunks = async(bucketStream) => { +const removeChunks = async(bucketStream: GridFSBucketWriteStream) => { + + const uploadID = bucketStream.id as string; try { - - const uploadID = bucketStream.id; if (!uploadID || uploadID.length === 0) { @@ -23,4 +25,4 @@ const removeChunks = async(bucketStream) => { } } -module.exports = removeChunks; \ No newline at end of file +export default removeChunks; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/removeChunksFS.ts b/backend/services/ChunkService/utils/removeChunksFS.ts new file mode 100644 index 0000000..299b682 --- /dev/null +++ b/backend/services/ChunkService/utils/removeChunksFS.ts @@ -0,0 +1,20 @@ +import fs from "fs"; + +const removeChunksFS = (path: string) => { + + return new Promise((resolve, reject) => { + + fs.unlink(path, (err) => { + + if (err) { + console.log("Could not remove fs file", err); + resolve(); + } + + console.log("File Removed"); + resolve(); + }) + }) +} + +export default removeChunksFS; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/removeChunksS3.ts b/backend/services/ChunkService/utils/removeChunksS3.ts new file mode 100644 index 0000000..f0180ee --- /dev/null +++ b/backend/services/ChunkService/utils/removeChunksS3.ts @@ -0,0 +1,21 @@ +import s3 from "../../../db/s3"; + +const removeChunksS3 = (parmas: any) => { + + return new Promise((resolve, reject) => { + + s3.deleteObject(parmas, (err, data) => { + + if (err) { + console.log("Could not remove S3 file"); + reject("Could Not Remove S3 File"); + } + + console.log("Delete S3 file"); + resolve(); + }) + + }) +} + +export default removeChunksS3; \ No newline at end of file diff --git a/backend/services/ChunkService/utils/removeTempToken.ts b/backend/services/ChunkService/utils/removeTempToken.ts new file mode 100644 index 0000000..3d30d01 --- /dev/null +++ b/backend/services/ChunkService/utils/removeTempToken.ts @@ -0,0 +1,13 @@ +import { UserInterface } from "../../../models/user"; + +const removeTempToken = async(user: UserInterface, tempToken: any,) => { + + user.tempTokens = user.tempTokens.filter((filterToken) => { + + return filterToken.token !== tempToken; + }) + + await user.save(); +} + +export default removeTempToken; \ No newline at end of file diff --git a/backend/services/FileService/index.js b/backend/services/FileService/index.js deleted file mode 100644 index 4c5838b..0000000 --- a/backend/services/FileService/index.js +++ /dev/null @@ -1,899 +0,0 @@ -const imageChecker = require("../../../src/utils/imageChecker"); -const crypto= require("crypto"); -const videoChecker = require("../../../src/utils/videoChecker"); -const mongoose = require("../../db/mongoose") -const conn = mongoose.connection; -const createThumbnail = require("../../services/FileService/utils/createThumbnail"); -const Thumbnail = require("../../models/thumbnail"); -const ObjectID = require('mongodb').ObjectID -const NotAuthorizedError = require("../../../src/utils/NotAuthorizedError"); -const NotFoundError = require("../../../src/utils/NotFoundError"); -const env = require("../../enviroment/env"); -const jwt = require("jsonwebtoken"); -const removeChunks = require("./utils/removeChunks"); -const User = require("../../models/user"); - - -const Folder = require("../../models/folder"); -const sortBySwitch = require("../../../src/utils/sortBySwitch") -const createQuery = require("../../../src/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(async(file) => { - - if (!file || !file.metadata.link || file.metadata.link !== tempToken) { - reject({ - code: 401, - message: "File not public/Not found", - exception: undefined - }) - } else { - - const user = await User.findById(file.metadata.owner); - - 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(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/backend/services/FileService/index.ts b/backend/services/FileService/index.ts new file mode 100644 index 0000000..9208c0d --- /dev/null +++ b/backend/services/FileService/index.ts @@ -0,0 +1,223 @@ +import NotAuthorizedError from "../../utils/NotAuthorizedError"; +import NotFoundError from "../../utils/NotFoundError"; +import env from "../../enviroment/env"; +import jwt from "jsonwebtoken"; +import Folder from "../../models/folder"; +import sortBySwitch from "../../utils/sortBySwitch"; +import createQuery from "../../utils/createQuery"; +import DbUtilFile from "../../db/utils/fileUtils/index"; +import DbUtilFolder from "../../db/utils/folderUtils"; +import { UserInterface } from "../../models/user"; +import { FileInterface } from "../../models/file"; +import tempStorage from "../../tempStorage/tempStorage"; +import uuid from "uuid"; + +const dbUtilsFile = new DbUtilFile(); +const dbUtilsFolder = new DbUtilFolder(); + +class MongoFileService { + + constructor() { + + } + + removePublicOneTimeLink = async(currentFile: FileInterface) => { + + const fileID = currentFile._id; + + if (currentFile.metadata.linkType === "one") { + + await dbUtilsFile.removeOneTimePublicLink(fileID); + } + } + + removeLink = async(userID: string, fileID: string) => { + + const file = await dbUtilsFile.removeLink(fileID, userID); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Remove Link File Not Found Error") + } + + makePublic = async(user: UserInterface, fileID: string) => { + + const userID = user._id; + const token = await jwt.sign({_id: userID.toString()}, env.password!); + + const file = await dbUtilsFile.makePublic(fileID, userID, token); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Make Public File Not Found Error"); + + return token; + } + + getPublicInfo = async(fileID: string, tempToken: string) => { + + const file: FileInterface = await dbUtilsFile.getPublicInfo(fileID, tempToken); + + if (!file || !file.metadata.link || file.metadata.link !== tempToken) { + + throw new NotFoundError("Public Info Not Found"); + + } else { + + return file; + } + } + + makeOneTimePublic = async(userID: string, fileID: string) => { + + 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; + } + + getFileInfo = async(userID: string, fileID: string) => { + + 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} + } + + getQuickList = async(userID: string) => { + + const quickList = await dbUtilsFile.getQuickList(userID); + + if (!quickList) throw new NotFoundError("Quick List Not Found Error"); + + return quickList; + } + + getList = async(userID: string, query: any) => { + + 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; + } + + getDownloadToken = async(user: UserInterface) => { + + const tempToken = await user.generateTempAuthToken(); + + if (!tempToken) throw new NotAuthorizedError("Get Download Token Not Authorized Error"); + + return tempToken; + } + + getDownloadTokenVideo = async(user: UserInterface, cookie: string) => { + + 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; + } + + removeTempToken = async(user: UserInterface, tempToken: any, currentUUID: string) => { + + const key = user.getEncryptionKey(); + + const decoded = await jwt.verify(tempToken, env.password!) as any; + + 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"); + + delete tempStorage[currentUUID]; + + await removedTokenUser.save(); + } + + getSuggestedList = async(userID: string, searchQuery: any) => { + + 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 + } + } + + renameFile = async(userID: string, fileID: string, title: string) => { + + const file = await dbUtilsFile.renameFile(fileID, userID, title); + + if (!file.lastErrorObject.updatedExisting) throw new NotFoundError("Rename File Not Found Error"); + + return file; + } + + moveFile = async(userID: string, fileID: string, parentID: string) => { + + 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; + } +} + +export default MongoFileService; \ No newline at end of file diff --git a/backend/services/FolderService/index.js b/backend/services/FolderService/index.ts similarity index 53% rename from backend/services/FolderService/index.js rename to backend/services/FolderService/index.ts index a22bfd3..3a6da05 100644 --- a/backend/services/FolderService/index.js +++ b/backend/services/FolderService/index.ts @@ -1,20 +1,16 @@ -const Folder = require("../../models/folder"); -const InternalServerError = require("../../../src/utils/InternalServerError"); -const NotFoundError = require("../../../src/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("../../../src/utils/sortBySwitchFolder") +import Folder from "../../models/folder"; +import InternalServerError from "../../utils/InternalServerError"; +import NotFoundError from "../../utils/NotFoundError"; +import UtilsFile from "../../db/utils/fileUtils"; +import UtilsFolder from "../../db/utils/folderUtils"; +import sortBySwitch from "../../utils/sortBySwitchFolder"; const utilsFile = new UtilsFile(); const utilsFolder = new UtilsFolder(); -const FolderService = function() { +class FolderService { - this.uploadFolder = async(data) => { + uploadFolder = async(data: any) => { const folder = new Folder(data); @@ -24,105 +20,8 @@ const FolderService = function() { 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) => { + getFolderInfo = async(userID: string, folderID: string) => { let currentFolder = await utilsFolder.getFolderInfo(folderID, userID); @@ -160,8 +59,8 @@ const FolderService = function() { return currentFolder; } - this.getFolderSublist = async(userID, folderID) => { - + getFolderSublist = async(userID: string, folderID: string) => { + const folder = await utilsFolder.getFolderInfo(folderID, userID); if (!folder) throw new NotFoundError("Folder Sublist Not Found Error"); @@ -196,10 +95,9 @@ const FolderService = function() { folderIDList, folderNameList } - } - this.getFolderList = async(userID, query) => { + getFolderList = async(userID: string, query: any) => { let searchQuery = query.search || ""; const parent = query.parent || "/"; @@ -225,14 +123,14 @@ const FolderService = function() { } } - this.renameFolder = async(userID, folderID, title) => { + renameFolder = async(userID: string, folderID: string, title: string) => { const folder = await utilsFolder.renameFolder(folderID, userID, title); if (!folder) throw new NotFoundError("Rename Folder Not Found"); } - this.moveFolder = async(userID, folderID, parentID) => { + moveFolder = async(userID: string, folderID: string, parentID: string) => { let parentList = ["/"]; @@ -268,7 +166,7 @@ const FolderService = function() { fileChildren.map( async(currentFileChild) => { - let currentFileChildParentList = currentFileChild.metadata.parentList; + let currentFileChildParentList: string | string[] = currentFileChild.metadata.parentList; currentFileChildParentList = currentFileChildParentList.split(","); @@ -284,4 +182,4 @@ const FolderService = function() { } } -module.exports = FolderService; \ No newline at end of file +export default FolderService; \ No newline at end of file diff --git a/backend/services/UserService/index.js b/backend/services/UserService/index.ts similarity index 56% rename from backend/services/UserService/index.js rename to backend/services/UserService/index.ts index b1cc6c6..79f3a39 100644 --- a/backend/services/UserService/index.js +++ b/backend/services/UserService/index.ts @@ -1,18 +1,33 @@ -const User = require("../../models/user"); -const bcrypt = require("bcrypt"); -const crypto = require("crypto"); -const NotFoundError = require("../../../src/utils/NotFoundError"); -const InternalServerError = require("../../../src/utils/InternalServerError"); -const NotAuthorizedError = require("../../../src/utils/NotAuthorizedError"); +import User, {UserInterface} from "../../models/user"; +import bcrypt from "bcrypt"; +import NotFoundError from "../../utils/NotFoundError"; +import InternalServerError from "../../utils/InternalServerError"; +import NotAuthorizedError from "../../utils/NotAuthorizedError"; -const UserService = function() { +type UserDataType = { + email: string, + password: string, +} - this.login = async(userData) => { +const uknownUserType = User as unknown; + +const UserStaticType = uknownUserType as { + findByCreds: (email: string, password: string) => Promise; +}; + + +class UserService { + + constructor() { + + } + + login = async(userData: UserDataType) => { const email = userData.email; const password = userData.password; - const user = await User.findByCreds(email, password); + const user = await UserStaticType.findByCreds(email, password); const token = await user.generateAuthToken(); @@ -21,7 +36,7 @@ const UserService = function() { return {user, token} } - this.logout = async(user, userToken) => { + logout = async(user: UserInterface, userToken: any) => { user.tokens = user.tokens.filter((token) => { return token.token !== userToken; @@ -30,15 +45,15 @@ const UserService = function() { await user.save(); } - this.logoutAll = async(user) => { - + logoutAll = async(user: UserInterface) => { + user.tokens = [] user.tempTokens = []; await user.save(); } - this.create = async(userData) => { + create = async(userData: any) => { console.log("Create"); @@ -54,7 +69,7 @@ const UserService = function() { return {user, token} } - this.changePassword = async(user, oldPassword, newPassword) => { + changePassword = async(user: UserInterface, oldPassword: string, newPassword: string) => { const isMatch = await bcrypt.compare(oldPassword, user.password); @@ -68,12 +83,13 @@ const UserService = function() { user.tempTokens = []; await user.save(); - await user.changeEncryptionKey(encryptionKey); + await user.changeEncryptionKey(encryptionKey!); const newToken = await user.generateAuthToken(); return newToken; } + } -module.exports = UserService; \ No newline at end of file +export default UserService; \ No newline at end of file diff --git a/backend/tempStorage/tempStorage.ts b/backend/tempStorage/tempStorage.ts new file mode 100644 index 0000000..270004f --- /dev/null +++ b/backend/tempStorage/tempStorage.ts @@ -0,0 +1,3 @@ +const tempStorage: any = {}; + +export default tempStorage; \ No newline at end of file diff --git a/backend/utils/InternalServerError.ts b/backend/utils/InternalServerError.ts new file mode 100644 index 0000000..89e1441 --- /dev/null +++ b/backend/utils/InternalServerError.ts @@ -0,0 +1,12 @@ +class InternalServerError extends Error { + + code: number; + + constructor(args: any) { + super(args); + + this.code = 500; + } +} + +export default InternalServerError; \ No newline at end of file diff --git a/backend/utils/NotAuthorizedError.ts b/backend/utils/NotAuthorizedError.ts new file mode 100644 index 0000000..d35791e --- /dev/null +++ b/backend/utils/NotAuthorizedError.ts @@ -0,0 +1,12 @@ +class NotAuthorizedError extends Error { + + code: number; + + constructor(args: any) { + super(args); + + this.code = 401; + } +} + +export default NotAuthorizedError; \ No newline at end of file diff --git a/backend/utils/NotFoundError.ts b/backend/utils/NotFoundError.ts new file mode 100644 index 0000000..4715120 --- /dev/null +++ b/backend/utils/NotFoundError.ts @@ -0,0 +1,12 @@ +class NotFoundError extends Error { + + code: number; + + constructor(args: any) { + super(args); + + this.code = 404; + } +} + +export default NotFoundError; \ No newline at end of file diff --git a/backend/utils/createQuery.ts b/backend/utils/createQuery.ts new file mode 100644 index 0000000..bef3472 --- /dev/null +++ b/backend/utils/createQuery.ts @@ -0,0 +1,54 @@ +export interface QueryInterface { + "metadata.owner": string, + "metadata.parent"?: string, + filename?: string | RegExp | { + $lt?: string, + $gt?: string, + }, + uploadDate?: { + $lt?: Date, + $gt?: Date, + }, +} + +const createQuery = (owner: string, parent: string, sortBy: string, startAt: number, startAtDate: number, searchQuery: string | RegExp, startAtName: string) => { + + let query: QueryInterface = {"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; + +} + +export default createQuery; \ No newline at end of file diff --git a/backend/utils/imageChecker.ts b/backend/utils/imageChecker.ts new file mode 100644 index 0000000..9c6c62e --- /dev/null +++ b/backend/utils/imageChecker.ts @@ -0,0 +1,31 @@ +const imageExtList = [ + "jpeg", + "jpg", + "png", + "gif", + "svg", + "tiff", + "bmp" +] + +const imageChecker = (filename: string) => { + + if (filename.length < 1 || !filename.includes(".")) { + + return false; + } + + const extSplit = filename.split("."); + + if (extSplit.length <= 1) { + + return false; + } + + const ext = extSplit[extSplit.length - 1]; + + return imageExtList.includes(ext.toLowerCase()); + +} + +export default imageChecker; \ No newline at end of file diff --git a/backend/utils/mobileCheck.ts b/backend/utils/mobileCheck.ts new file mode 100644 index 0000000..2072520 --- /dev/null +++ b/backend/utils/mobileCheck.ts @@ -0,0 +1,8 @@ +declare let window:any + +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/backend/utils/sortBySwitch.ts b/backend/utils/sortBySwitch.ts new file mode 100644 index 0000000..f59316f --- /dev/null +++ b/backend/utils/sortBySwitch.ts @@ -0,0 +1,15 @@ +const sortBySwitch = (sortBy: string) => { + switch(sortBy) { + + case "alp_asc": + return {filename: 1} + case "alp_desc": + return {filename: -1} + case "date_asc": + return {uploadDate: 1} + default: + return {uploadDate: -1} + } +} + +export default sortBySwitch; \ No newline at end of file diff --git a/backend/utils/sortBySwitchFolder.ts b/backend/utils/sortBySwitchFolder.ts new file mode 100644 index 0000000..b9308e9 --- /dev/null +++ b/backend/utils/sortBySwitchFolder.ts @@ -0,0 +1,15 @@ +const sortBySwitchFolder = (sortBy: string) => { + switch(sortBy) { + + case "alp_asc": + return {name: 1} + case "alp_desc": + return {name: -1} + case "date_asc": + return {createdAt: 1} + default: + return {createdAt: -1} + } +} + +export default sortBySwitchFolder; \ No newline at end of file diff --git a/backend/utils/streamToBuffer.ts b/backend/utils/streamToBuffer.ts new file mode 100644 index 0000000..edefc02 --- /dev/null +++ b/backend/utils/streamToBuffer.ts @@ -0,0 +1,27 @@ +const streamToBuffer = (stream: any, allStreamsToErrorCatch: any[]) => { + + const chunks: any[] = [] + return new Promise((resolve, reject) => { + + allStreamsToErrorCatch.forEach((currentStream) => { + + currentStream.on("error", (e: Error) => { + + console.log("Stream To Buffer Error", e); + reject({ + message: "stream to buffer error", + code: 500, + error: e + }) + + }) + }) + + stream.on('data', (chunk: any) => chunks.push(chunk)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(chunks))); + + }) +} + +export default streamToBuffer; \ No newline at end of file diff --git a/backend/utils/videoChecker.ts b/backend/utils/videoChecker.ts new file mode 100644 index 0000000..28bd071 --- /dev/null +++ b/backend/utils/videoChecker.ts @@ -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: string) => { + + if (filename.length < 1 || !filename.includes(".")) { + + return false; + } + + const extSplit = filename.split("."); + + if (extSplit.length <= 1) { + + return false; + } + + const ext = extSplit[extSplit.length - 1]; + + return videoExtList.includes(ext.toLowerCase()); + +} + +export default videoChecker; \ No newline at end of file diff --git a/docker-compose-no-mongo.yml b/docker-compose-no-mongo.yml new file mode 100644 index 0000000..2d89b36 --- /dev/null +++ b/docker-compose-no-mongo.yml @@ -0,0 +1,13 @@ +version: '3' + +services: + app: + container_name: mydrive-node-no-mongo + restart: always + build: . + ports: + - '3000:3000' + - '8080:8080' + env_file: + - docker-variables.env + \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e57c087 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3' + +services: + app: + container_name: mydrive-node + restart: always + build: . + ports: + - '3000:3000' + - '8080:8080' + env_file: + - docker-variables.env + links: + - mongo + mongo: + container_name: mongo + image: mongo + ports: + - '27017:27017' + volumes: + - 'mongodb_data_volume:/data/db' +volumes: + mongodb_data_volume: + external: true \ No newline at end of file diff --git a/github_images/folder-screenshot.png b/github_images/folder-screenshot.png new file mode 100644 index 0000000..1380481 Binary files /dev/null and b/github_images/folder-screenshot.png differ diff --git a/github_images/image-screenshot.png b/github_images/image-screenshot.png new file mode 100644 index 0000000..502e2ba Binary files /dev/null and b/github_images/image-screenshot.png differ diff --git a/github_images/mobile-screenshot.jpeg b/github_images/mobile-screenshot.jpeg new file mode 100644 index 0000000..b44a551 Binary files /dev/null and b/github_images/mobile-screenshot.jpeg differ diff --git a/github_images/move-screenshot.png b/github_images/move-screenshot.png new file mode 100644 index 0000000..3470b97 Binary files /dev/null and b/github_images/move-screenshot.png differ diff --git a/github_images/mydrivehome.png b/github_images/mydrivehome.png new file mode 100644 index 0000000..8dd52c6 Binary files /dev/null and b/github_images/mydrivehome.png differ diff --git a/github_images/mydrivehome2.png b/github_images/mydrivehome2.png new file mode 100644 index 0000000..c85aca9 Binary files /dev/null and b/github_images/mydrivehome2.png differ diff --git a/github_images/mydrivelogogit.png b/github_images/mydrivelogogit.png new file mode 100644 index 0000000..1824ea9 Binary files /dev/null and b/github_images/mydrivelogogit.png differ diff --git a/github_images/search-screenshot.png b/github_images/search-screenshot.png new file mode 100644 index 0000000..d5b54c2 Binary files /dev/null and b/github_images/search-screenshot.png differ diff --git a/github_images/share-screenshot.png b/github_images/share-screenshot.png new file mode 100644 index 0000000..c22f14a Binary files /dev/null and b/github_images/share-screenshot.png differ diff --git a/github_images/upload-screenshot.png b/github_images/upload-screenshot.png new file mode 100644 index 0000000..c26dd34 Binary files /dev/null and b/github_images/upload-screenshot.png differ diff --git a/github_images/video-screenshot.png b/github_images/video-screenshot.png new file mode 100644 index 0000000..5a4b7ae Binary files /dev/null and b/github_images/video-screenshot.png differ diff --git a/key/getKey.js b/key/getKey.js index 6117cc3..c0dbfe7 100644 --- a/key/getKey.js +++ b/key/getKey.js @@ -1,13 +1,19 @@ const prompt = require('password-prompt') -const env = require("../backend/enviroment/env") +import env from "../backend/enviroment/env"; const crypto = require("crypto"); +// NOT IN USE + const getKey = async() => { if (process.env.KEY) { // For Docker - env.key = process.env.KEY + const password = process.env.KEY; + + env.key = password; + + //console.log("Docker Key", env.key); } else if (process.env.NODE_ENV) { diff --git a/key/getNewKey.js b/key/getNewKey.js index 6e62f2e..253ad1a 100644 --- a/key/getNewKey.js +++ b/key/getNewKey.js @@ -1,5 +1,5 @@ const prompt = require('password-prompt') -const env = require("../backend/enviroment/env") +import env from "../backend/enviroment/env"; const crypto = require("crypto"); const getKey = async() => { diff --git a/package.json b/package.json index 8537a31..e6cebc0 100755 --- a/package.json +++ b/package.json @@ -1,41 +1,50 @@ { "name": "my-drive", - "version": "1.0.0", + "version": "1.0.1", "main": "index.js", "license": "GNU General Public License v3.0", + "engines": { + "node": "13.11.0" + }, "scripts": { "server": "live-server public/", - "build": "cross-env NODE_ENV=production webpack -p --env production", - "build:no-ssl": "cross-env NODE_ENV=production-no-ssl webpack -p --env production-no-ssl", - "build:dev": "webpack", + "build": "tsc && webpack -p --env production && webpack --config webUI.config.js", + "build:docker": "export NODE_OPTIONS=--max_old_space_size=8000 && tsc && webpack -p --env production && webpack --config webUI.config.js", + "build:webUI": "webpack --config webUI.config.js", + "build:dev": "tsc && webpack --env development", "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": "cross-env NODE_ENV=production env-cmd -f ./config/prod.env node server/serverStart.js --env production", - "start:no-ssl": "cross-env NODE_ENV=production-no-ssl env-cmd -f ./config/prod.env node server/serverStart.js --env production-no-ssl", - "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", - "init": "node serverUtils/initServer.js", - "heroku-postbuild": "yarn run build:prod" + "test": "NODE_ENV=test env-cmd -f ./config/test.env jest --config=jest.config.json --watchAll --runInBand", + "start:dev": "NODE_ENV=dev node dist/server/serverStart.js", + "start": "node dist/server/serverStart.js --env production", + "create-indexes-database": "node serverUtils/createIndexes.js", + "increase-node-memory": "export NODE_OPTIONS=--max_old_space_size=8000", + "create-indexes-database:dev": "node serverUtils/createIndexes.js", + "setup": "node serverUtils/setupServer.js", + "debug": "env-cmd -f ./config/prod.env node --expose-gc --inspect=9222 dist/server/serverStart.js --env production", + "heroku-postbuild": "npm run build" }, "dependencies": { "@babel/core": "^7.8.4", + "@babel/parser": "^7.9.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", + "@babel/types": "^7.9.5", + "@types/bcrypt": "^3.0.0", + "@types/compression": "^1.7.0", + "@types/concat-stream": "^1.6.0", + "@types/connect-busboy": "0.0.2", + "@types/express": "^4.17.6", + "@types/helmet": "0.0.45", + "@types/jsonwebtoken": "^8.3.9", + "@types/mongodb": "^3.5.5", + "@types/mongoose": "^5.7.10", + "@types/node": "^13.13.5", + "@types/sharp": "^0.25.0", + "@types/uuid": "^7.0.2", + "@types/validator": "^13.0.0", + "aws-sdk": "^2.657.0", "axios": "^0.19.2", "babel-polyfill": "^6.26.0", "bcrypt": "^3.0.8", @@ -49,6 +58,7 @@ "core-js": "^3.6.4", "crypto": "^1.0.1", "diskusage": "^1.1.3", + "dotenv": "^8.2.0", "express": "^4.17.1", "fluent-ffmpeg": "^2.1.2", "helmet": "^3.21.2", @@ -76,6 +86,7 @@ "sharp": "^0.23.4", "sweetalert2": "^9.7.2", "temp": "^0.9.1", + "typescript": "^3.8.3", "uuid": "^3.4.0", "validator": "^12.2.0", "webpack": "^4.41.6" @@ -88,7 +99,6 @@ "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", diff --git a/public/images/icon.png b/public/images/icon.png new file mode 100644 index 0000000..9c6c5bf Binary files /dev/null and b/public/images/icon.png differ diff --git a/public/index.html b/public/index.html index 8eab094..869820b 100755 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,10 @@ MyDrive + + + + diff --git a/readme.md b/readme.md index b55a53d..4055657 100755 --- a/readme.md +++ b/readme.md @@ -1,117 +1,179 @@ +# ![MyDrive Homepage](github_images/mydrivehome2.png) + # MyDrive -MyDrive is an Open Source Cloud Server (Similar To Google Drive), built with Node.JS, Express, React, and MongoDB. +MyDrive is an Open Source Cloud Server (Similar To Google Drive), the service uses mongoDB to store file/folder metadata, and supports multiple databases to store the file chunks, such as Amazon S3, the Filesystem, or just MongoDB. MyDrive is built using Node.js, and Typescript. The service now even supports Docker images! -Demo: https://mydrive-demo.herokuapp.com/ -- Note: The Upload and Download Features, and other core features, are disabled in the demo. +## Index -### Wiki +* [Features](#features) +* [Installation](#installation) +* [WebUI For Encryption Key](#webui-for-encryption-key) +* [Docker](#docker) +* [Environment Variables](#environment-variables) +* [Screenshots](#screenshots) +* [Wiki](https://github.com/subnub/myDrive/wiki) +* [Video](#video) +* [Demo](#demo) -For a more detailed list of myDrive features, including examples with images, visit the wiki here: https://github.com/subnub/myDrive/wiki +## Features -MyDrive Features: - -- Upload Files -- Download Files -- Create Folders -- Share Files -- Creates Image Thumbnails -- Photo-Viewer -- Stream Video -- Transcode Video -- Create One-Time Download links for files -- Move Files/Folders -- AES256 Encryption (For file chunks, tokens, and more!) -- Search For Files/Folders -- Mobile Friendly (Including Uploading!) -- Advanced Filter Options +* Upload Files +* Download Files +* Share Files +* Multiple DB Support (MongoDB, S3, Filesystem) +* Photo Viewer +* Video Viewer +* Thumbnails +* One-time download links +* Move Folder/Files +* Mobile Support +* Docker Support +* Search/Filter Options +* AES256 Encryption ## Installation Required: -- Node.js (13.9+ Is Recommended) -- MongoDB +- Node.js (13+ Recommended) +- MongoDB (Unless using a service like Atlas) -Windows users will usually need both the microsoft visual build tools, and python 2. These are required to build the sharp module. +Windows users will usually need both the microsoft visual build tools, and python 2. These are required to build the sharp module: - Visual Tools: http://go.microsoft.com/fwlink/?LinkId=691126 - Python 2: https://www.python.org/downloads/release/python-2717/ -Optional: -- FFMPEG (For Video Transcoding, Does not work very well because of encryption ATM, But non-transcoded video streams work much better, I do not recommend using transcoding yet.) +Linux users will need to make sure they have 'build-essential' installed: +```bash +sudo apt-get install build-essential +``` Setup: -- “npm install”, Installs all dependencies. +>Install Node Modules +``` javascript +npm install +``` -- "npm run init", Asks user for some environment variable details, pressing enter on these fields will instead use the default values. +>Create Environment Variables, Users can use the built in command to easily create the needed Environment files, or view the Environment Variables section to see how to manually create the files. +``` javascript +npm run setup +``` -- "npm run build" or "npm run build:no-ssl", This will look for the HTTPS Certificate, Place this HTTPS certificate at the root of the project, with the naming convention (certificate.ca-bundle, certificate.crt, and certificate.key). -Note: If you do not have or want to use a HTTPS certificate, run "npm run build:no-ssl". This will run the server in production mode, without SSL encryption, this is vulnerable to man in the middle attacks! +>Run the build command +``` javascript +npm run build +``` -- “npm run create-indexes-database”, This will create indexes for mongoDB, this is greatly recommended, it will improve mongoDBs speed in retrieving files. +>(Optional) Create the MongoDB indexes, this increases performance. MongoDB must be running for this command to work. +```javascript +npm run create-indexes-database +``` -- "npm start" or "npm run start:no-ssl". If you do not want SSL Encryption use "npm run start:no-ssl". +>Start the server +``` javascript +npm run start +``` -- It will then ask for an encryption key, this can be whatever you would like, this key is later hashed. Do not lose this key, there is NO way to recover data from a lost key! +## WebUI For Encryption Key -- That's It! Now just Create a new account. +MyDrive will first host a server on http://localhost:3000 in order to safely get the encryption key, just navigate to this URL in a browser, and enter the encryption key. -## Enviroment Variables +If you're using a service like SSH or a Droplet, you can forward the localhost connection safely like so: +```bash +ssh -L localhost:3000:localhost:3000 username@ip_address +``` -The npm run init command will create the needed env variables, but if you would like to create them manually, structure the files like so. +Note: You can also disable using the webUI for the encryption key by providing a key in the server enviroment variables (e.g. KEY=password), but this is not recommended because it greatly reduces security. -Backend variables: Stored in /config/prod.env +## Docker -- MONGODB_URL= -- PASSWORD= -- HTTP_PORT= -- HTTPS_PORT= -- URL= -- FULL_URL= -- ROOT= +MyDrive has built in Docker support, there are two options when using Docker, users can either use the Docker image that has MongoDB built in, or use the Docker image that just has the MyDrive image (If you're using a service like Atlas). -Optional: -- KEY= +Create the Docker environment variables by running the 'npm run setup' command as seen in the installation section. Or by manually creating the file (e.g. docker-variables.env on the root of the project, see the enviroment section for more infomation). -The KEY variable is optional, without it the server will prompt you for a password on startup, with a KEY the server will skip prompting for the password, use a KEY if you want to use myDrive with docker, or similar tools. +Docker with mongoDB image: +```bash +docker-compose build +``` -Frontend variables: Stored on the root of the project .env.production +Docker without mongoDB image: +```bash +docker-compose -f docker-compose-no-mongo.yml build +``` +Start the Docker Image: +```bash +docker-compose up +``` -- PORT= -- REMOTE_URL= +## Screenshots -## Built In Server Tools +Upload Files +![MyDrive Upload](github_images/upload-screenshot.png) -MyDrive comes with some built in NPM scripts for server management, this includes: -- Backup Database: Command “npm run backup-database", creates a temporary backup of the database inside of mongoDB, please note you can only have one backup at a time, for better backups use mongoExport. +Image Viewer +![Image Viewer](github_images/image-screenshot.png) -- Restore Database: Command “npm run restore-database”, Restores Database from backup. +Video Viewer +![Video Viewer](github_images/video-screenshot.png) -- Clean Database: Command “npm run clean-database”, Cleans database, removing any orphaned chunks that do not have a file associated with them, this command will automatically run backup database, incase anything fails. +Search For Files/Folders +![Search](github_images/search-screenshot.png) -- Change Encryption Password: Command “change-password-database”, Changed the encryption password, that the server first asks for on start up, this requires the old encryption password. This will also create a new backup, incase anything fails. +Move File/Folders +![Move](github_images/move-screenshot.png) -- Delete Database: Command “npm run delete-database”, Deletes the main database, does not delete the temporary backup. +Share +![Share](github_images/share-screenshot.png) -- Delete Temp Database: Command “npm run delete-temp-database”, Deletes the database backup. +Folders +![Folders](github_images/folder-screenshot.png) -- Create Indexes: Command “npm run create-indexes-database", Creates Indexes for mongoDB, without this mongoDB will need to search through every single file on request, run this before using the server. +Mobile +
+ +
-- Run Tests: Command "npm run test", Starts unit testing. +## Environment Variables + +Create a config folder on the root of the project, and create a file with the name prod.env for the server. For the client variables create a .env.production file in the root of the project. + +Server Environment Variables: + +- MONGODB_URL (Required): Sets the MongoDB URL, this should also work with DocumentDB. +- HTTP_PORT (Required): Sets the HTTP port number. +- HTTPS_PORT (Required): Sets the HTTPS port number. +- PASSWORD (Required): Sets the JWT password. +- DB_TYPE (Required): Sets the Database Type, options include s3/mongo/fs. +- NODE_ENV (Required): Must be set to 'production'. +- SSL (Optional): Enables SSL, place certificate.crt, certificate.ca-bundle, and certificate.key at the root of the project. +- KEY (Optional): Encryption key for data, this is not recommended, please use the built in webUI for setting the key. +- DOCKER (Optional/Required): Sets the server to use docker, set this to true. +- FS_DIRECTORY (Optional/Required): Sets the directory for file data on the file system. +- S3_ID (Optional/Required): Sets the Amazon S3 ID. +- S3_KEY (Optional/Required): Sets the Amazon S3 Key. +- S3_BUCKET (Optional/Required): Sets the Amazon Bucket. +- ROOT (Optional): Uses a filesystem path, is used for storage space. +- URL (Optional): Allows to specify URL to host on, this is usually not needed. +- USE_DOCUMENT_DB (Optional): Enables documentDB, this is experimental. +- DOCUMENT_DB_BUNDLE (Optional): Enables SSL with documentDB. +- BLOCK_CREATE_ACCOUNT (Optional): Blocks the ability to create accounts. + +Client Environment Variables + +- REMOTE_URL (Required): Sets the Remote URL for the client. +- DISABLE_STORAGE (Optional): Disables storage, use this if you're not using ROOT on the server. + +## Wiki + +For a more detailed list of myDrive features, including examples with images, visit the wiki here: https://github.com/subnub/myDrive/wiki ## Video + I created a short YouTube video, showing off myDrives design and features: https://youtu.be/0YKU5CZHG4I -## Security -MyDrive encrypts all file chunks, tokens, and temp tokens. These items are first encrypted with a randomly generated 32 byte private key, and random 16 byte public key (A different random public key is used for different items).The private key is encrypted with the users salted password, and then the hashed server key (Acquired on server startup). Note: Running the command to change the servers key, will generate new private and public keys for each user, and will have to re-encrypt all chunks. +## Demo -### Using myDrive for personal use? -After you create your account, disable the ability to create a new account by adding the following value to the prod.env file (Located inside on the config folder). -BLOCK_CREATE_ACCOUNT=true +Demo: https://mydrive-demo.herokuapp.com/ +- Note: The Upload and Download Features, and other core features, are disabled in the demo. ## Questions? Feature Requests? Hiring? Contact Me! Contact Email: kyle.hoell@gmail.com - -## Follow my twitter account -Twitter: https://twitter.com/subnub2 - - diff --git a/rootpath.js b/rootpath.js deleted file mode 100644 index 68db50e..0000000 --- a/rootpath.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = __dirname \ No newline at end of file diff --git a/server/server.js b/server/server.js deleted file mode 100755 index 3f7aaab..0000000 --- a/server/server.js +++ /dev/null @@ -1,83 +0,0 @@ -const express = require("express"); -const app = express(); -const path = require("path") -const publicPath = path.join(__dirname, "..", "public"); -const userRouter = require("../backend/express-routers/user") -const fileRouter = require("../backend/express-routers/file") -const folderRouter = require("../backend/express-routers/folder"); -const storageRouter = require("../backend/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 http = require("http"); - - -let server; -let serverHttps; - -if (process.env.NODE_ENV === 'production') { - - const cert = fs.readFileSync("certificate.crt") - const ca = fs.readFileSync("certificate.ca-bundle"); - const key = fs.readFileSync("certificate.key"); - - - const options = { - cert, - ca, - key - } - - serverHttps = https.createServer( options, app ); -} - - -server = http.createServer(app); - - - -require("../backend/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 deleted file mode 100644 index 3cb8db5..0000000 --- a/server/serverStart.js +++ /dev/null @@ -1,41 +0,0 @@ -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 if (process.env.NODE_ENV === 'production-no-ssl') { - - const server = require("./server"); - - server.listen(process.env.HTTP_PORT,process.env.URL, () => { - console.log("Http Server (No-SSL) Running On Port:", process.env.HTTP_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/createIndexes.js b/serverUtils/createIndexes.js index 8056617..35adcb1 100644 --- a/serverUtils/createIndexes.js +++ b/serverUtils/createIndexes.js @@ -1,4 +1,6 @@ -const mongoose = require("../backend/db/mongoose"); +const getEnvVariables = require("./getEnvVaribables"); +getEnvVariables(); +const mongoose = require("./mongoServerUtil"); const conn = mongoose.connection; const waitForDatabase = () => { diff --git a/serverUtils/createThumbnailBuffer.js b/serverUtils/createThumbnailBuffer.js index 33de081..23e7b7c 100644 --- a/serverUtils/createThumbnailBuffer.js +++ b/serverUtils/createThumbnailBuffer.js @@ -1,7 +1,7 @@ -const mongoose = require("../backend/db/mongooseServerUtils") +import mongoose from "../backend/db/mongoose"; const conn = mongoose.connection; const crypto= require("crypto"); -const env = require("../backend/enviroment/env"); +import env from "../backend/enviroment/env"; const Thumbnail = require("../backend/models/thumbnail"); const ObjectID = require('mongodb').ObjectID const sharp = require("sharp"); diff --git a/serverUtils/getEnvVaribables.js b/serverUtils/getEnvVaribables.js new file mode 100644 index 0000000..ff38277 --- /dev/null +++ b/serverUtils/getEnvVaribables.js @@ -0,0 +1,10 @@ +const path = require("path"); + +const getEnvVariables = () => { + + const configPath = path.join(__dirname, "..", "config"); + + require('dotenv').config({ path: configPath + "/prod.env"}) +} + +module.exports = getEnvVariables; \ No newline at end of file diff --git a/serverUtils/initServer.js b/serverUtils/initServer.js deleted file mode 100644 index 403d280..0000000 --- a/serverUtils/initServer.js +++ /dev/null @@ -1,148 +0,0 @@ -const prompts = require("prompts"); -const fs = require("fs"); -const crypto = require("crypto"); - -const initServer = async() => { - - console.log("Initializing Server...\n"); - - await fs.mkdir("./config", async() => { - - const getClientPortNumber = await prompts({ - type: 'text', - message: "Enter Client Port Number (Default 3000):", - name: "value" - }) - const clientPortNumber = getClientPortNumber.value || "3000"; - - const getClientUrl = await prompts({ - type: 'text', - message: "\nEnter Client URL (Add Port Number If Needed, Default http://localhost:3000):", - name: "value" - }) - - const clientURL = getClientUrl.value || "http://localhost:3000" - - const getClientTranscode = await prompts({ - type: 'text', - message: "\nEnable Transcode Video (Default False)", - name: "value" - }) - const clientTranscode = getClientTranscode.value || "false"; - - let fullInfoClient = "PORT=" + clientPortNumber + "\n" - + "REMOTE_URL=" + clientURL - - console.log("client transcode", clientTranscode); - - if (clientTranscode.toLowerCase() === "true") { - - fullInfoClient = fullInfoClient + "\n" - + "ENABLE_VIDEO_TRANSCODING=true" - } - - console.log("\nClient Into Completed, Getting Server Info...\n") - - const getSeverMongoURL = await prompts({ - type: 'text', - message: "\nEnter MongoDB URL (Default mongodb://127.0.0.1:27017/personal-drive):", - name: "value" - }) - - const serverMongoURL = getSeverMongoURL.value || "mongodb://127.0.0.1:27017/personal-drive" - - let randomPassword = crypto.randomBytes(16); - randomPassword = randomPassword.toString("hex"); - - - const getServerPassword = await prompts({ - type: 'text', - message: `\nEnter Server JSON Token Password (Default Random Password ${randomPassword}): `, - name: "value" - }) - const serverPassword = getServerPassword.value || randomPassword; - - - const getServerHttpPort = await prompts({ - type: 'text', - message: `\nEnter Server Http Port (Default 3000): `, - name: "value" - }) - const serverHttpPort = getServerHttpPort.value || "3000"; - - - const getServerHttpsPort = await prompts({ - type: 'text', - message: `\nEnter Server Https Port (Default 8080): `, - name: "value" - }) - const serverHttpsPort = getServerHttpsPort.value || "8080" - - const getServerUrl = await prompts({ - type: 'text', - message: `\nEnter Server URL (Do Not Include Port, Default localhost):`, - name: "value" - }) - const serverURL = getServerUrl.value || "localhost" - - const getServerFullUrl = await prompts({ - type: 'text', - message: `\nEnter Full Server URL (Include Port If Needed, Default http://localhost:3000):`, - name: "value" - }) - const serverFullURL = getServerFullUrl.value || "http://localhost:3000"; - - const getServerRoot = await prompts({ - type: 'text', - message: `\nEnter Path For Storage Size Check (Default /):`, - name: "value" - }) - const serverRoot = getServerRoot.value || "/"; - - const fullServerInfo = "MONGODB_URL=" + serverMongoURL + "\n" - + "PASSWORD=" + serverPassword + "\n" - + "HTTP_PORT=" + serverHttpPort + "\n" - + "HTTPS_PORT=" + serverHttpsPort + "\n" - + "URL=" + serverURL + "\n" - + "FULL_URL=" + serverFullURL + "\n" - + "ROOT=" + serverRoot + "\n"; - - console.log("\nServer Info Finished, Displaying Results...\n"); - - console.log("Client Info:\n", fullInfoClient) - - console.log("\nServer Info:\n", fullServerInfo) - - const getConfimation = await prompts({ - type: 'text', - message: `Would You Like To Save These Files (Yes/No): `, - name: "value" - }) - - if (!getConfimation.value || getConfimation.value.toLowerCase() !== "yes") { - console.log("Exiting...") - process.exit() - return; - } - - await fs.writeFile("./.env.production", fullInfoClient, async(err) => { - - if (err) { - console.log("Error", err); - process.exit(); - } - - await fs.writeFile("./config/prod.env", fullServerInfo, (err2) => { - - if (err2) { - console.log("Error", err2); - process.exit(); - } - }) - - }) - - }); -} - -initServer(); \ No newline at end of file diff --git a/backend/db/mongooseServerUtils.js b/serverUtils/mongoServerUtil.js similarity index 63% rename from backend/db/mongooseServerUtils.js rename to serverUtils/mongoServerUtil.js index 4a4adcf..5a8c31a 100644 --- a/backend/db/mongooseServerUtils.js +++ b/serverUtils/mongoServerUtil.js @@ -1,7 +1,6 @@ const mongoose = require("mongoose"); -const env = require("../enviroment/env"); -mongoose.connect(env.mongoURL, { +mongoose.connect(process.env.MONGODB_URL, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, diff --git a/serverUtils/setupServer.js b/serverUtils/setupServer.js new file mode 100644 index 0000000..17ef0ae --- /dev/null +++ b/serverUtils/setupServer.js @@ -0,0 +1,365 @@ +const prompts = require("prompts"); +const fs = require("fs"); +const crypto = require("crypto"); + +const awaitcreateDir = (path) => { + + return new Promise((resolve, reject) => { + + fs.mkdir(path, () => { + resolve(); + }) + }) +} + +const awaitWriteFile = (path, data) => { + + return new Promise((resolve, reject) => { + + fs.writeFile(path, data, async(err) => { + + if (err) { + console.log("file write error", err); + reject(); + } + + resolve(); + }) + }) +} + +const initServer = async() => { + + console.log("Setting Up Server...\n"); + + await awaitcreateDir("./config"); + + const getDocker = await prompts({ + type: 'toggle', + name: 'value', + message: 'Use Docker With myDrive?', + initial: true, + active: 'yes', + inactive: 'no' + }) + + const docker = getDocker.value; + + + if (docker) { + + let stringBuilder = ''; + + const getUsingMongo = await prompts({ + type: 'toggle', + name: 'value', + message: "Include MongoDB In The Docker Image? (Select No If You're Using MongoDB Atlas)", + initial: true, + active: 'yes', + inactive: 'no' + }) + + const mongo = getUsingMongo.value; + + let mongoURL = "mongodb://mongo:27017/personal-drive"; + + if (!mongo) { + + const getMongoURL = await prompts({ + type: 'text', + message: "Enter The MongoDB URL", + name: "value" + }) + + mongoURL = getMongoURL.value; + } + + stringBuilder += "MONGODB_URL=" + mongoURL + "\n" + + const getKeyType = await prompts({ + type: 'toggle', + name: 'value', + message: "Use WebUI For Encryption Key (Recommended, Selecting No Will Require You To Enter An Encryption Key Now, Which Is Less Secure)", + initial: true, + active: 'yes', + inactive: 'no' + }) + + let keyType = getKeyType.value; + + if (!keyType) { + + const getKey = await prompts({ + type: 'password', + message: "Enter The Encryption Key", + name: "value" + }) + + const key = getKey.value; + + stringBuilder += "KEY=" + key + "\n"; + } + + const getClientURL = await prompts({ + type: 'text', + message: "Enter The Client URL/IP Address (Enter The Client URL/IP Address (Must Be A Valid Link, Include Port With IP Address If Needed)", + name: "value" + }) + + const clientURL = getClientURL.value; + + stringBuilder += "REMOTE_URL=" + clientURL + "\n"; + + const getChunkDB = await prompts({ + type: 'select', + name: 'value', + message: 'Pick A Database To Store File Chunks', + choices: [ + { title: 'Amazon S3', value: 's3'}, + { title: 'FileSystem', value: 'fs'}, + { title: 'MongoDB', value: 'mongo' }, + ], + initial: 1 + }) + + const chunkDB = getChunkDB.value; + + stringBuilder += "DB_TYPE=" + chunkDB + "\n"; + + if (chunkDB === "s3") { + + const gets3ID = await prompts({ + type: 'text', + message: "Enter The S3 ID", + name: "value" + }) + + const s3ID = gets3ID.value; + + stringBuilder += "S3_ID=" + s3ID + "\n"; + + const gets3Key = await prompts({ + type: 'password', + message: "Enter The S3 Key", + name: "value" + }) + + const s3Key = gets3Key.value; + + stringBuilder += "S3_KEY=" + s3Key + "\n"; + + const gets3Bucket = await prompts({ + type: 'text', + message: "Enter The S3 Bucket", + name: "value" + }) + + const s3Bucket = gets3Bucket.value; + + stringBuilder += "S3_BUCKET=" + s3Bucket + "\n"; + + } else if (chunkDB === "fs") { + + const getFSPath = await prompts({ + type: 'text', + message: "Enter The FileSystem Path", + name: "value" + }) + + const fsPath = getFSPath.value; + + stringBuilder += "FS_DIRECTORY=" + fsPath + "\n"; + } + + const getJWTSecret = await prompts({ + type: 'password', + message: "Enter JWT Secret", + name: "value", + }) + + const JWTsecret = getJWTSecret.value; + + stringBuilder += "PASSWORD=" + JWTsecret + "\n"; + + const getUseSSL = await prompts({ + type: 'toggle', + name: 'value', + message: "Use SSL? (Will Require SSL Certificate certificate.crt, certificate.ca-bundle, And certificate.key At Root Of The Project)", + initial: true, + active: 'yes', + inactive: 'no' + }) + + const useSSL = getUseSSL.value; + + if (useSSL) { + + stringBuilder += "SSL=true\n"; + } + + stringBuilder += "DISABLE_STORAGE=true\n"; + stringBuilder += "DOCKER=true\n"; + stringBuilder += "NODE_ENV=production\n"; + stringBuilder += "PORT=3000\n"; + stringBuilder += "HTTP_PORT=3000\n"; + stringBuilder += "HTTPS_PORT=8080\n" + + await awaitWriteFile("./docker-variables.env", stringBuilder); + + console.log("\nCreated Docker Env File"); + + } else { + + let stringBuilderClient = ''; + let stringBuilderServer = ''; + + const getMongoURL = await prompts({ + type: 'text', + message: "Enter The MongoDB URL", + name: "value" + }) + + const mongoURL = getMongoURL.value; + + stringBuilderServer += "MONGODB_URL=" + mongoURL + "\n"; + + const getKeyType = await prompts({ + type: 'toggle', + name: 'value', + message: "Use WebUI For Encryption Key (Recommended, Selecting No Will Require You To Enter An Encryption Key Now, Which Is Less Secure)", + initial: true, + active: 'yes', + inactive: 'no' + }) + + let keyType = getKeyType.value; + + if (!keyType) { + + const getKey = await prompts({ + type: 'password', + message: "Enter The Encryption Key", + name: "value" + }) + + const key = getKey.value; + + stringBuilderServer += "KEY=" + key + "\n"; + } + + const getClientURL = await prompts({ + type: 'text', + message: "Enter The Client URL/IP Address (Must Be A Valid Link, Include Port With IP Address If Needed)", + name: "value" + }) + + const clientURL = getClientURL.value; + + stringBuilderClient += "REMOTE_URL=" + clientURL + "\n"; + + const getChunkDB = await prompts({ + type: 'select', + name: 'value', + message: 'Pick A Database To Store File Chunks', + choices: [ + { title: 'Amazon S3', value: 's3'}, + { title: 'FileSystem', value: 'fs'}, + { title: 'MongoDB', value: 'mongo' }, + ], + initial: 1 + }) + + const chunkDB = getChunkDB.value; + + stringBuilderServer += "DB_TYPE=" + chunkDB + "\n"; + + if (chunkDB === "s3") { + + const gets3ID = await prompts({ + type: 'text', + message: "Enter The S3 ID", + name: "value" + }) + + const s3ID = gets3ID.value; + + stringBuilderServer += "S3_ID=" + s3ID + "\n"; + + const gets3Key = await prompts({ + type: 'password', + message: "Enter The S3 Key", + name: "value" + }) + + const s3Key = gets3Key.value; + + stringBuilderServer += "S3_KEY=" + s3Key + "\n"; + + const gets3Bucket = await prompts({ + type: 'text', + message: "Enter The S3 Bucket", + name: "value" + }) + + const s3Bucket = gets3Bucket.value; + + stringBuilderServer += "S3_BUCKET=" + s3Bucket + "\n"; + stringBuilderClient += "DISABLE_STORAGE=true\n"; + + } else if (chunkDB === "fs") { + + const getFSPath = await prompts({ + type: 'text', + message: "Enter The FileSystem Path", + name: "value" + }) + + const fsPath = getFSPath.value; + + stringBuilderServer += "FS_DIRECTORY=" + fsPath + "\n"; + stringBuilderServer += "ROOT=" + fsPath + "\n"; + + } else { + + stringBuilderClient += "DISABLE_STORAGE=true\n"; + } + + const getJWTSecret = await prompts({ + type: 'password', + message: "Enter JWT Secret", + name: "value", + }) + + const JWTsecret = getJWTSecret.value; + + stringBuilderServer += "PASSWORD=" + JWTsecret + "\n"; + + const getUseSSL = await prompts({ + type: 'toggle', + name: 'value', + message: "Use SSL? (Will Require SSL Certificate certificate.crt, certificate.ca-bundle, And certificate.key At Root Of The Project)", + initial: true, + active: 'yes', + inactive: 'no' + }) + + const useSSL = getUseSSL.value; + + if (useSSL) { + + stringBuilderServer += "SSL=true\n"; + } + + stringBuilderServer += "NODE_ENV=production\n"; + stringBuilderClient += "PORT=3000\n"; + stringBuilderServer += "HTTP_PORT=3000\n"; + stringBuilderServer += "HTTPS_PORT=8080\n" + + await awaitWriteFile("./.env.production", stringBuilderClient); + await awaitWriteFile("./config/prod.env", stringBuilderServer); + + console.log("\nServer And Client Env Files Created"); + } +} + +initServer(); \ No newline at end of file diff --git a/src/actions/auth.js b/src/actions/auth.js index fdbd909..0a719cf 100755 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -43,7 +43,6 @@ export const startCreateAccount = (email, password) => { return (dispatch) => { - const dt = {email, password}; axios.post(currentURL+"/user-service/create", dt).then((response) => { diff --git a/src/actions/files.js b/src/actions/files.js index 8e3e452..7239a6f 100644 --- a/src/actions/files.js +++ b/src/actions/files.js @@ -6,6 +6,7 @@ import {startSetStorage} from "./storage" import uuid from "uuid"; import axios from "axios"; import env from "../enviroment/envFrontEnd"; +import mobileCheck from "../utils/mobileCheck"; const http = require('http'); const https = require('https'); @@ -87,6 +88,10 @@ export const startLoadMoreFiles = (parent="/", sortby="DEFAULT", search="", star } dispatch(loadMoreFiles(results.data)) + + if (mobileCheck()) { + dispatch(setLoading(false)); + } }).catch((err) => { console.log(err) diff --git a/src/actions/storage.js b/src/actions/storage.js index 8805bbe..cf9db98 100644 --- a/src/actions/storage.js +++ b/src/actions/storage.js @@ -12,6 +12,10 @@ export const startSetStorage = () => { return (dispatch) => { + if (env.disableStorage === "true") { + return; + } + const config = { headers: {'Authorization': "Bearer " + window.localStorage.getItem("token")} }; diff --git a/src/components/FileItem/FileItem.js b/src/components/FileItem/FileItem.js index bff1cdc..5afb308 100644 --- a/src/components/FileItem/FileItem.js +++ b/src/components/FileItem/FileItem.js @@ -16,7 +16,10 @@ const FileItem = (props) => {
{props.fileClick(props._id, props)}} - onContextMenu={(e) => props.getContextMenu(e)}> + onContextMenu={(e) => props.getContextMenu(e)} + onTouchStart={props.onTouchStart} + onTouchEnd={props.onTouchEnd} + onTouchMove={props.onTouchMove}>
{props.image.src = "/images/file-svg.svg"; props.image.className="file__image__listview"; props.failedToLoad = true}}/> @@ -27,7 +30,7 @@ const FileItem = (props) => { {(props.rightSelected === props._id && props._id === props.selected) ? - + : undefined} @@ -37,13 +40,18 @@ const FileItem = (props) => {
{props.fileClick(props._id, props)}} - onContextMenu={(e) => props.getContextMenu(e)}> + onContextMenu={(e) => props.getContextMenu(e)} + onTouchStart={props.onTouchStart} + onTouchEnd={props.onTouchEnd} + onTouchMove={props.onTouchMove}>
- {props.image.src = "/images/file-svg.svg"; props.image.className="file__image"; props.failedToLoad = true;}}/> + {props.image.src = "/images/file-svg.svg"; props.image.className="file__image"; props.failedToLoad = true;}} + />
-
{capitalize(props.filename)}
+
{capitalize(props.filename)}
{(props.rightSelected === props._id && props._id === props.selected) ? diff --git a/src/components/FileItem/index.js b/src/components/FileItem/index.js index cd1ce06..7b404f0 100644 --- a/src/components/FileItem/index.js +++ b/src/components/FileItem/index.js @@ -15,6 +15,8 @@ class FileItemContainer extends React.Component { this.failedToLoad = false; + this.lastTouch = 0; + this.state = { contextMenuPos: {}, imageSrc: "/images/file-svg.svg", @@ -95,10 +97,42 @@ class FileItemContainer extends React.Component { } } + onTouchStart = () => { + //alert("Touch start"); + const date = new Date(); + this.lastTouch = date.getTime(); + } + + onTouchMove = () => { + + this.lastTouch = 0; + } + + onTouchEnd = () => { + + if (this.lastTouch === 0) { + + //alert("last touch 0"); + return; + } + + const date = new Date(); + const difference = date - this.lastTouch; + //alert("Touch end: " + difference) + //alert("touch end: " + difference); + this.lastTouch = 0; + + if (difference > 500) { + //alert("Context menu"); + this.getContextMenu(); + } + + } + getContextMenu = (e) => { - e.preventDefault(); - + if (e) e.preventDefault(); + const isMobile = mobileCheck(); const windowX = window.innerWidth; @@ -106,33 +140,40 @@ class FileItemContainer extends React.Component { 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"} + + } else { + + 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)) @@ -167,6 +208,9 @@ class FileItemContainer extends React.Component { return } diff --git a/src/components/FolderItem/FolderItem.js b/src/components/FolderItem/FolderItem.js index fce7027..e5532bf 100644 --- a/src/components/FolderItem/FolderItem.js +++ b/src/components/FolderItem/FolderItem.js @@ -14,7 +14,10 @@ const FolderItem = (props) => {
props.folderClick(props._id, props.name)} - onContextMenu={(e) => props.getContextMenu(e)}> + onContextMenu={(e) => props.getContextMenu(e)} + onTouchStart={props.onTouchStart} + onTouchEnd={props.onTouchEnd} + onTouchMove={props.onTouchMove}>
@@ -33,7 +36,10 @@ const FolderItem = (props) => {
props.folderClick(props._id, props.name)} - onContextMenu={(e) => props.getContextMenu(e)}> + onContextMenu={(e) => props.getContextMenu(e)} + onTouchStart={props.onTouchStart} + onTouchEnd={props.onTouchEnd} + onTouchMove={props.onTouchMove}>

{props.name}

diff --git a/src/components/FolderItem/index.js b/src/components/FolderItem/index.js index 1e82711..ec8bcde 100644 --- a/src/components/FolderItem/index.js +++ b/src/components/FolderItem/index.js @@ -8,6 +8,8 @@ class FolderItemContainer extends React.Component { constructor(props) { super(props) + + this.lastTouch = 0; } shouldComponentUpdate = (nextProp) => { @@ -19,9 +21,41 @@ class FolderItemContainer extends React.Component { || nextProp.quickFilesLength !== this.props.quickFilesLength) } + onTouchStart = () => { + //alert("Touch start"); + const date = new Date(); + this.lastTouch = date.getTime(); + } + + onTouchMove = () => { + + this.lastTouch = 0; + } + + onTouchEnd = () => { + + if (this.lastTouch === 0) { + + //alert("last touch 0"); + return; + } + + const date = new Date(); + const difference = date - this.lastTouch; + //alert("Touch end: " + difference) + //alert("touch end: " + difference); + this.lastTouch = 0; + + if (difference > 500) { + //alert("Context menu"); + this.getContextMenu(); + } + + } + getContextMenu = (e) => { - e.preventDefault(); + if (e) e.preventDefault(); const isMobile = mobileCheck(); @@ -30,34 +64,36 @@ class FolderItemContainer extends React.Component { 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"} + + } else { + + 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"} + } } this.setState(() => styleObj) @@ -95,6 +131,9 @@ class FolderItemContainer extends React.Component { return } diff --git a/src/components/Homepage/HomePage.js b/src/components/Homepage/HomePage.js index de3dfbb..747e7c2 100644 --- a/src/components/Homepage/HomePage.js +++ b/src/components/Homepage/HomePage.js @@ -8,7 +8,7 @@ import PhotoViewer from "../PhotoViewer" const HomePage = (props) => ( -
+
diff --git a/src/components/MainSection/MainSection.js b/src/components/MainSection/MainSection.js index 3545864..59d3d16 100644 --- a/src/components/MainSection/MainSection.js +++ b/src/components/MainSection/MainSection.js @@ -26,13 +26,14 @@ const MainSection = React.forwardRef((props, ref) => {
{(props.quickFiles.length !== 0 && props.parent === "/") ? -
+
: - undefined} +
+
} diff --git a/src/components/MainSection/index.js b/src/components/MainSection/index.js index 386219e..a32d93c 100644 --- a/src/components/MainSection/index.js +++ b/src/components/MainSection/index.js @@ -1,6 +1,7 @@ import {startLoadMoreFiles} from "../../actions/files"; import {startSetSelectedItem, setLastSelected} from "../../actions/selectedItem"; +import {setLoading} from "../../actions/main"; import {setPopupFile} from "../../actions/popupFile"; import mobileCheck from "../../utils/mobileCheck" import MainSection from "./MainSection"; @@ -16,6 +17,7 @@ class MainSectionContainer extends React.Component { constructor(props) { super(props); + } folderClick = (id) => { @@ -56,6 +58,44 @@ class MainSectionContainer extends React.Component { } + scrollEvent = (e) => { + + if (!mobileCheck()) return; + + const scrollY = window.pageYOffset; + const windowY = document.documentElement.scrollHeight; + + let limit = window.localStorage.getItem("list-size") || 50 + limit = parseInt(limit) + + if (this.props.loading) return; + + if ((windowY / 2) < scrollY && this.props.allowLoadMoreItems) { + console.log("Scroll Bottom Mobile"); + + 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(setLoading(true)); + this.props.dispatch(startLoadMoreFiles(parent, sortBy, search, lastFileDate, lastFileName)); + + } + } + } + + componentDidMount = () => { + window.addEventListener("scroll", this.scrollEvent); + } + + componentWillUnmount = () => { + + window.removeEventListener("scroll", this.scrollEvent); + } + downloadFile = (fileID) => { const config = { @@ -74,8 +114,18 @@ class MainSectionContainer extends React.Component { document.body.appendChild(link); link.href = finalUrl; link.setAttribute('type', 'hidden'); + link.setAttribute("download", true); link.click(); + // const form = document.createElement("form"); + // form.action = finalUrl; + // form.method = "GET"; + // form.setAttribute("type", "hidden"); + + // document.body.append(form); + + // form.submit(); + }).catch((err) => { console.log(err) }) @@ -83,6 +133,8 @@ class MainSectionContainer extends React.Component { loadMoreItems = () => { + if (mobileCheck()) return; + console.log("loading more items", this.props.loading); let limit = window.localStorage.getItem("list-size") || 50 diff --git a/src/components/PopupWindow/index.js b/src/components/PopupWindow/index.js index 78d8e38..c689b05 100644 --- a/src/components/PopupWindow/index.js +++ b/src/components/PopupWindow/index.js @@ -91,13 +91,15 @@ class PopupWindowContainer extends React.Component { componentWillUnmount = () => { + const uuidID = window.sessionStorage.getItem("uuid"); + 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}`, { + axios.delete(currentURL +`/file-service/remove/token-video/${this.tempToken}/${uuidID}`, { headers, }).catch((err) => { console.log(err); diff --git a/src/components/QuickAccessItem/QuickAccessItem.js b/src/components/QuickAccessItem/QuickAccessItem.js index 19996f5..fa29010 100644 --- a/src/components/QuickAccessItem/QuickAccessItem.js +++ b/src/components/QuickAccessItem/QuickAccessItem.js @@ -6,7 +6,10 @@ import React from "react"; const QuickAccessItem = (props) => (
{props.fileClick(props._id, props, true)}} - onContextMenu={(e) => props.getContextMenu(e)}> + onContextMenu={(e) => props.getContextMenu(e)} + onTouchStart={props.onTouchStart} + onTouchEnd={props.onTouchEnd} + onTouchMove={props.onTouchMove}>
{props.image.src = "/images/file-svg.svg"; props.image.className="quickaccess__item__image"; props.failedToLoad = true}}/> diff --git a/src/components/QuickAccessItem/index.js b/src/components/QuickAccessItem/index.js index 60907c8..f32b001 100644 --- a/src/components/QuickAccessItem/index.js +++ b/src/components/QuickAccessItem/index.js @@ -14,6 +14,7 @@ class QuickAccessItemContainer extends React.Component { super(props); this.failedToLoad = false; + this.lastTouch = 0; this.state = { contextMenuPos: {}, @@ -62,9 +63,42 @@ class QuickAccessItemContainer extends React.Component { console.log(err) }) } + + onTouchStart = () => { + //alert("Touch start"); + const date = new Date(); + this.lastTouch = date.getTime(); + } + + onTouchMove = () => { + + this.lastTouch = 0; + } + + onTouchEnd = () => { + + if (this.lastTouch === 0) { + + //alert("last touch 0"); + return; + } + + const date = new Date(); + const difference = date - this.lastTouch; + //alert("Touch end: " + difference) + //alert("touch end: " + difference); + this.lastTouch = 0; + + if (difference > 500) { + //alert("Context menu"); + this.getContextMenu(); + } + + } + getContextMenu = (e) => { - e.preventDefault(); + if (e) e.preventDefault(); const isMobile = mobileCheck() @@ -73,21 +107,24 @@ class QuickAccessItemContainer extends React.Component { 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"} + styleObj = {bottom: 0, left: "2px"}; + + } else { + + 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"} + } + } this.setState(() => ({ @@ -106,6 +143,9 @@ class QuickAccessItemContainer extends React.Component { return } diff --git a/src/components/SettingsMenu/SettingsMenu.js b/src/components/SettingsMenu/SettingsMenu.js index 1cf879b..5c403f2 100644 --- a/src/components/SettingsMenu/SettingsMenu.js +++ b/src/components/SettingsMenu/SettingsMenu.js @@ -1,5 +1,6 @@ import StorageWidget from ".././StorageWidget"; import React from "react"; +import env from "../../enviroment/envFrontEnd"; const SettingsMenu = (props) => ( @@ -27,18 +28,21 @@ const SettingsMenu = (props) => ( {/*Storage*/} -
+ {env.disableStorage ? undefined : -

Storage

+
+ +

Storage

-
+
- - -
- -
+ + +
+
+ + } {/*List Style*/} diff --git a/src/components/Subbar/Subbar.js b/src/components/Subbar/Subbar.js index 7c48ef8..1b764a5 100644 --- a/src/components/Subbar/Subbar.js +++ b/src/components/Subbar/Subbar.js @@ -11,38 +11,33 @@ export class Subbar extends React.Component { return (
- {!this.props.isMobile ? undefined : - -
+
- + -

New

+

New

-
-
- -

Folder

-
+
+
+ +

Folder

+
-
- - -

File Upload

- - -
+
+ + +

File Upload

+ + +
-
- -

Folder Upload

-
-
-
- - - } +
+ +

Folder Upload

+
+
+
diff --git a/src/components/Subbar/index.js b/src/components/Subbar/index.js index 1b6d37f..453dc10 100644 --- a/src/components/Subbar/index.js +++ b/src/components/Subbar/index.js @@ -1,8 +1,8 @@ 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 {startAddFile, setFiles, startSetFiles} from "../../actions/files"; +import {startAddFolder, setFolders, startSetFolders} from "../../actions/folders" import {enableListView, disableListView} from "../../actions/filter"; import {resetSelected} from "../../actions/selectedItem"; import Swal from "sweetalert2"; @@ -103,13 +103,25 @@ class SubbarContainer extends React.Component { showListViewEvent = () => { let listView = this.props.listView; + const parent = this.props.parent; + const sortBy = this.props.sortBy; + const search = this.props.search; if (listView) { this.props.dispatch(resetSelected()) + this.props.dispatch(setFiles([])); + this.props.dispatch(setFolders([])); this.props.dispatch(disableListView()) + this.props.dispatch(startSetFiles(parent, sortBy, search)); + this.props.dispatch(startSetFolders(parent, sortBy, search)); + } else { this.props.dispatch(resetSelected()) + this.props.dispatch(setFiles([])); + this.props.dispatch(setFolders([])); this.props.dispatch(enableListView()) + this.props.dispatch(startSetFiles(parent, sortBy, search)); + this.props.dispatch(startSetFolders(parent, sortBy, search)); } } @@ -152,7 +164,8 @@ const connectPropToState = (state) => ({ sortBy: state.filter.sortBy, showAddOptions2: state.addOptions.showAddOptions2, showSideBar: state.main.showSideBar, - currentlySearching: state.filter.currentlySearching + currentlySearching: state.filter.currentlySearching, + search: state.filter.search, }) export default connect(connectPropToState)(SubbarContainer) \ No newline at end of file diff --git a/src/enviroment/envFrontEnd.js b/src/enviroment/envFrontEnd.js index 12081ae..bebb4b6 100644 --- a/src/enviroment/envFrontEnd.js +++ b/src/enviroment/envFrontEnd.js @@ -2,6 +2,7 @@ const env = { port: process.env.PORT, url: process.env.REMOTE_URL, enableVideoTranscoding: process.env.ENABLE_VIDEO_TRANSCODING, + disableStorage: process.env.DISABLE_STORAGE } export default env; \ No newline at end of file diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss index 3707c06..e0d0ede 100644 --- a/src/styles/base/_base.scss +++ b/src/styles/base/_base.scss @@ -3,7 +3,15 @@ } html { - font-size: 62.5% + font-size: 62.5%; + // --webkit-touch-callout: default; +} + +input[type=text], +input[type=email], +input[type=password], +textarea { + -webkit-appearance: none; } body { diff --git a/src/styles/components/_ContentMenu.scss b/src/styles/components/_ContentMenu.scss index d2ce286..f37a412 100644 --- a/src/styles/components/_ContentMenu.scss +++ b/src/styles/components/_ContentMenu.scss @@ -16,6 +16,13 @@ width: 99%; border-bottom-left-radius: 0; border-bottom-right-radius: 0; + -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; } } diff --git a/src/styles/components/_File.scss b/src/styles/components/_File.scss index b71aa27..fb5451c 100644 --- a/src/styles/components/_File.scss +++ b/src/styles/components/_File.scss @@ -15,18 +15,19 @@ // 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-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 + position: unset; + //-webkit-touch-callout: default; } } @@ -44,6 +45,12 @@ max-width: 23vw; text-align: center; font-weight: 300; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; // text-transform: capitalize; } @@ -65,11 +72,26 @@ flex-direction: column; justify-content: center; overflow: hidden; + -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; + pointer-events: none; } .file__image { width: 100px; opacity: 0.7; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; } .file__item__wrapper { @@ -78,15 +100,16 @@ 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; + // -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%; + cursor: pointer; } .file__item__listview { @@ -116,7 +139,8 @@ -webkit-tap-highlight-color: transparent; @media (max-width: $desktop-breakpoint) { - position: unset + position: unset; + overflow: hidden; } } @@ -147,6 +171,12 @@ height: 33px; /* max-height: 30px; */ border-radius: 5px; + width: 31px; + opacity: 1; + max-width: 31px; + height: 33px; + max-height: 33px; + border-radius: 5px; } .file__image--no-opacity { diff --git a/src/styles/components/_Filter.scss b/src/styles/components/_Filter.scss index 0939195..5a7b22f 100644 --- a/src/styles/components/_Filter.scss +++ b/src/styles/components/_Filter.scss @@ -3,6 +3,10 @@ justify-content: space-between; align-items: center; height: 52px; + + @media (max-width: $desktop-breakpoint) { + width: 100vw; + } } .filter__folder-wrapper { @@ -14,6 +18,12 @@ margin-left: 22px; font-weight: 500; font-size: 13px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .filter__folder-title-wrapper { margin-left: 15px; diff --git a/src/styles/components/_Header.scss b/src/styles/components/_Header.scss index 7576da9..3b4d4e9 100644 --- a/src/styles/components/_Header.scss +++ b/src/styles/components/_Header.scss @@ -6,9 +6,28 @@ border-bottom: 1px solid #dadce0; justify-content: space-between; /* display: none; */ - position: absolute; + position: fixed; /* top: 0; */ width: 100%; + z-index: 1; + + @media (max-width: $desktop-breakpoint) { + text-align: center; + display: flex; + flex-direction: row; + border-bottom: 1px solid #dadce0; + justify-content: space-between; + position: fixed; + background: white; + top: 0; + left: 0; + right: 0; + width: 100%; + height: 80px; + padding: 0; + z-index: 2; + // background: blue; + } } .header__title { @@ -143,6 +162,12 @@ display: flex; flex-direction: row; justify-content: space-between; + align-items: center; + + + @media (max-width: $desktop-breakpoint) { + height: 55px; + } } .header__input__suggested__item:hover { diff --git a/src/styles/components/_HomePage.scss b/src/styles/components/_HomePage.scss index 549d692..2f47244 100644 --- a/src/styles/components/_HomePage.scss +++ b/src/styles/components/_HomePage.scss @@ -10,9 +10,15 @@ -ms-overflow-style: none; /* IE 10+ */ &::-webkit-scrollbar { width: 0px; + height: unset; background: transparent; /* Chrome/Safari/Webkit */ } + @media (max-width: $desktop-breakpoint) { + + overflow-y: hidden; + } + } .main-container { display: flex; @@ -23,7 +29,12 @@ overflow-x: hidden; @media (max-width: $desktop-breakpoint) { - width: 100vw; + // margin-top: 77px; + display: flex; + flex-direction: column; + //overflow-x: hidden; + overflow: hidden; + width: unset; } } @@ -34,14 +45,64 @@ height: -webkit-fill-available; display: inherit; overflow: hidden; + + @media (max-width: $desktop-breakpoint) { + height: unset; + } } +.outter-wrapper { + + @media (max-width: $desktop-breakpoint) { + + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + &::-webkit-scrollbar { + width: 0px; + height: unset; + background: transparent; /* Chrome/Safari/Webkit */ + } + } +} + +// .outter-wrapper { +// height: 100%; +// width: 100%; +// overflow: hidden; +// @media (max-width: $desktop-breakpoint) { +// //background: blue; +// position: fixed; +// top: 0; +// left: 0; +// right: 0; +// bottom: 0; +// } +// } + .main-page { background: white; height: 100vh; width: 100vw; display: inline-flex; padding-top: 71px; + + @media (max-width: $desktop-breakpoint) { + background: #fff; + /* height: 100vh; */ + // height: calc(100% - 80px); + height: unset; + width: 100vw; + display: inline-flex; + padding: 0; + overflow: hidden; + // position: fixed; + // padding: 0; + // bottom: 0; + // left: 0; + // right: 0; + // margin-top: 80px; + //background: red; + } } diff --git a/src/styles/components/_LeftSection.scss b/src/styles/components/_LeftSection.scss index d431f41..fd36ac0 100644 --- a/src/styles/components/_LeftSection.scss +++ b/src/styles/components/_LeftSection.scss @@ -35,6 +35,14 @@ } } +.add-button-2 { + + display: none; + @media (max-width: $desktop-breakpoint) { + display: inline-flex; + } +} + .add-button__img { width: 30px; height: 30px; @@ -108,6 +116,10 @@ // 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); + + @media (max-width: $desktop-breakpoint) { + margin-top: 75px; + } } .add_button__option__add-folder-wrapper { diff --git a/src/styles/components/_MoverMenu.scss b/src/styles/components/_MoverMenu.scss index 97110e3..b680cf3 100644 --- a/src/styles/components/_MoverMenu.scss +++ b/src/styles/components/_MoverMenu.scss @@ -15,6 +15,7 @@ @media (max-width: $desktop-breakpoint) { width: 100%; height: 100%; + z-index: 2; } } diff --git a/src/styles/components/_Popup-window.scss b/src/styles/components/_Popup-window.scss index ebd79c5..4cf1e4c 100644 --- a/src/styles/components/_Popup-window.scss +++ b/src/styles/components/_Popup-window.scss @@ -22,6 +22,7 @@ @media (max-width: $desktop-breakpoint) { width: 100%; height: 100%; + z-index: 2; } } @@ -60,12 +61,13 @@ .popup-window__image--loaded { - width: 100%; - max-height: 100%; + width: 94vw; + max-height: 70vh; object-fit: cover; opacity: 1; - max-width: 100%; + max-width: 94vw; border-radius: 6px; + height: 70vh; cursor: pointer; } @@ -94,13 +96,17 @@ align-items: center; display: flex; margin-bottom: 50px; - height: 65%; - width: 99%; - max-width: 99%; + overflow: visible; + // height: 65%; + // width: 99%; + max-width: 100%; overflow: hidden; position: relative; justify-content: center; - max-height: 50%; + // max-height: 50%; + height: 70%; + width: 100%; + max-height: 70%; } } diff --git a/src/styles/components/_QuickAccess.scss b/src/styles/components/_QuickAccess.scss index 0925d44..8f8e506 100644 --- a/src/styles/components/_QuickAccess.scss +++ b/src/styles/components/_QuickAccess.scss @@ -9,6 +9,15 @@ /* overflow-y: hidden; */ overflow: hidden; + @media (max-width: $desktop-breakpoint) { + // margin-top: calc(80px + 3vh); + width: inherit; + height: 153px; + display: flex; + flex-flow: row wrap; + align-items: center; + overflow: hidden; + } } .quickaccess__item { @@ -34,7 +43,10 @@ position: relative; @media (max-width: $desktop-breakpoint) { - + min-height: 144px; + height: 148px; + min-width: 46%; + width: 46%; position: unset; } diff --git a/src/styles/components/_SettingsMenu.scss b/src/styles/components/_SettingsMenu.scss index 69a1c27..c7bb3c8 100644 --- a/src/styles/components/_SettingsMenu.scss +++ b/src/styles/components/_SettingsMenu.scss @@ -17,6 +17,7 @@ @media (max-width: $desktop-breakpoint) { width: 100%; height: 100%; + z-index: 2; } } diff --git a/src/styles/components/_Sharemenu.scss b/src/styles/components/_Sharemenu.scss index 499998b..1d5c822 100644 --- a/src/styles/components/_Sharemenu.scss +++ b/src/styles/components/_Sharemenu.scss @@ -22,6 +22,7 @@ width: 100%; height: 100%; top: 0; + z-index: 2; } } @@ -43,6 +44,10 @@ font-size: 13.5px; font-weight: 200; white-space: nowrap; + + @media (max-width: $desktop-breakpoint) { + max-width: 80%; + } } .sharemenu__subtitle { @@ -125,6 +130,10 @@ display: flex; flex-direction: row; justify-content: center; + + @media (max-width: $desktop-breakpoint) { + height: 49px; + } } .sharemenu__image { diff --git a/src/styles/components/_Spacer.scss b/src/styles/components/_Spacer.scss index 7b32b87..57d8802 100644 --- a/src/styles/components/_Spacer.scss +++ b/src/styles/components/_Spacer.scss @@ -3,10 +3,23 @@ height: 37px; } +.spacer__mobile { + + @media (max-width: $desktop-breakpoint) { + margin-top: calc(80px + 6vh); + } +} + .spacer__title { color: #5f6368; margin-left: 22px; font-weight: 500; font-size: 13px; margin-left: 38px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } \ No newline at end of file diff --git a/src/styles/components/_Subbar.scss b/src/styles/components/_Subbar.scss index 9e00ba1..f021f1d 100644 --- a/src/styles/components/_Subbar.scss +++ b/src/styles/components/_Subbar.scss @@ -41,6 +41,21 @@ user-select: none; // overflow-x: scroll; + @media (max-width: $desktop-breakpoint) { + width: 100vw; + display: flex; + flex-direction: row; + align-items: center; + height: 7vh; + min-height: 50px; + user-select: none; + position: fixed; + top: 80px; + background: white; + z-index: 1; + } + + } .path__block { @@ -51,6 +66,12 @@ flex-direction: row; align-items: center; cursor: pointer; + + @media (max-width: $desktop-breakpoint) { + height: 52px; + max-height: 52px; + } + } .path__wrapper { @@ -89,10 +110,20 @@ /* height: fit-content; */ padding: 0px 0 2px 0; margin-top: 19px; + + @media (max-width: $desktop-breakpoint) { + line-height: normal; + padding: unset; + margin: unset; + text-align: center; + } } .path__image { height: fit-content; /* width: 30px; */ // margin-top: 7px; + @media (max-width: $desktop-breakpoint) { + //margin-top: 3px; + } } \ No newline at end of file diff --git a/src/styles/components/_Uploader.scss b/src/styles/components/_Uploader.scss index f166de7..58c7eb9 100644 --- a/src/styles/components/_Uploader.scss +++ b/src/styles/components/_Uploader.scss @@ -14,7 +14,7 @@ box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); @media (max-width: $desktop-breakpoint) { - position:absolute; + position: fixed; left:0;right:0;bottom:0; margin:0 auto; } @@ -74,6 +74,9 @@ border-bottom: 1px solid #dadce0; position: relative; + @media (max-width: $desktop-breakpoint) { + min-height: 50px; + } } .CircularProgressbar { diff --git a/tests/db/utils/fileUtils/index.test.js b/tests/db/utils/fileUtils/index.test.js index cf24ed3..6e521de 100644 --- a/tests/db/utils/fileUtils/index.test.js +++ b/tests/db/utils/fileUtils/index.test.js @@ -1,10 +1,10 @@ -const User = require("../../../../backend/models/user"); -const mongoose = require("../../../../backend/db/mongoose"); +import User from "../../../../dist/models/user"; +import mongoose from "../../../../dist/db/mongoose"; const conn = mongoose.connection; const createUser = require("../../../fixtures/createUser"); const path = require("path"); const createFile = require("../../../fixtures/createFile"); -const UtilsFile = require("../../../../backend/db/utils/fileUtils"); +import UtilsFile from "../../../../dist/db/utils/fileUtils/index"; const ObjectID = require('mongodb').ObjectID const jwt = require("jsonwebtoken"); const utilsFile = new UtilsFile(); @@ -792,55 +792,55 @@ test("When giving the wrong temp token, should not remove temp token", async() = expect(updatedUser.tempTokens[0].token._id).toBe(token._id) }) -test("When giving fileID and userID, should remove transcoded video", async() => { +// 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}}) +// 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}); +// await utilsFile.removeTranscodeVideo(fileID, userID); +// const updatedFile = await conn.db.collection("fs.files") +// .findOne({_id: fileID}); - expect(updatedFile.metadata.transcoded).toBe(undefined); -}) +// expect(updatedFile.metadata.transcoded).toBe(undefined); +// }) -test("When giving wrong fileID, should not remove transcoded video", async() => { +// 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}}) +// 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}); +// await utilsFile.removeTranscodeVideo(wrongFileID, userID); +// const updatedFile = await conn.db.collection("fs.files") +// .findOne({_id: fileID}); - expect(updatedFile.metadata.transcoded).toBe(true); -}) +// expect(updatedFile.metadata.transcoded).toBe(true); +// }) -test("When giving the wrong userID, should not remove transcoded video", async() => { +// 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}}) +// 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}); +// await utilsFile.removeTranscodeVideo(fileID, wrongUserID); +// const updatedFile = await conn.db.collection("fs.files") +// .findOne({_id: fileID}); - expect(updatedFile.metadata.transcoded).toBe(true); -}) +// expect(updatedFile.metadata.transcoded).toBe(true); +// }) test("When giving userID and search query, should return file list", async() => { diff --git a/tests/db/utils/folderUtils/index.test.js b/tests/db/utils/folderUtils/index.test.js index b143337..3d209b9 100644 --- a/tests/db/utils/folderUtils/index.test.js +++ b/tests/db/utils/folderUtils/index.test.js @@ -1,10 +1,11 @@ -const User = require("../../../../backend/models/user"); -const mongoose = require("../../../../backend/db/mongoose"); +import User from "../../../../dist/models/user"; +import mongoose from "../../../../dist/db/mongoose"; +import UtilsFolder from "../../../../dist/db/utils/folderUtils"; +import Folder from "../../../../dist/models/folder" const conn = mongoose.connection; const createUser = require("../../../fixtures/createUser"); -const UtilsFolder = require("../../../../backend/db/utils/folderUtils"); const utilsFolder = new UtilsFolder(); -const Folder = require("../../../../backend/models/folder"); + let user; let folder; diff --git a/tests/express-routers/file.test.js b/tests/express-routers/file.test.js index 39a73cc..9dc3e3e 100644 --- a/tests/express-routers/file.test.js +++ b/tests/express-routers/file.test.js @@ -1,21 +1,25 @@ -const User = require("../../backend/models/user"); -const mongoose = require("../../backend/db/mongoose"); +import User from "../../dist/models/user"; +import mongoose from "../../dist/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("../../backend/enviroment/env"); -const createThumbnail = require("../../backend/services/FileService/utils/createThumbnail"); +import env from "../../dist/enviroment/env"; +import createThumbnail from "../../dist/services/ChunkService/utils/createThumbnail"; const request = require("supertest"); const jwt = require("jsonwebtoken"); const ObjectID = require('mongodb').ObjectID -const app = require("../../server/server"); -const Folder = require("../../backend/models/folder"); +import servers from "../../dist/server/server"; +import Folder from "../../dist/models/folder"; const temp = require("temp").track(); const binaryPhraser = require("superagent-binary-parser"); +const {server, serverHttps} = servers; + +const app = server; + let user; let userToken; let user2; @@ -431,12 +435,13 @@ test("When giving tempToken, should remove temp token", async() => { .send() .expect(200); + const tempToken = token.body.tempToken; + await request(app) - .delete(`/file-service/remove/token-video/${token}`) + .delete(`/file-service/remove/token-video/${tempToken}/1234`) .set("Authorization", `Bearer ${userToken}`) .send() .expect(200); - }) test("When not authorized, should not remove temp token", async() => { @@ -448,8 +453,10 @@ test("When not authorized, should not remove temp token", async() => { .send() .expect(200); + const tempToken = token.body.tempToken; + await request(app) - .delete(`/file-service/remove/token-video/${token}`) + .delete(`/file-service/remove/token-video/${tempToken}/1234`) .send() .expect(401); }) diff --git a/tests/express-routers/folder.test.js b/tests/express-routers/folder.test.js index 9b0df30..4ad85e2 100644 --- a/tests/express-routers/folder.test.js +++ b/tests/express-routers/folder.test.js @@ -1,20 +1,24 @@ -const User = require("../../backend/models/user"); -const mongoose = require("../../backend/db/mongoose"); +import User from "../../dist/models/user"; +import mongoose from "../../dist/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("../../backend/enviroment/env"); +import env from "../../dist/enviroment/env"; const request = require("supertest"); const jwt = require("jsonwebtoken"); const ObjectID = require('mongodb').ObjectID -const app = require("../../server/server"); -const Folder = require("../../backend/models/folder"); +import servers from "../../dist/server/server"; +import Folder from "../../dist/models/folder"; const temp = require("temp").track(); const binaryPhraser = require("superagent-binary-parser"); +const {server, serverHttps} = servers; + +const app = server; + let user; let userToken; let user2; diff --git a/tests/express-routers/user.test.js b/tests/express-routers/user.test.js index a5b471b..709bee1 100644 --- a/tests/express-routers/user.test.js +++ b/tests/express-routers/user.test.js @@ -1,10 +1,14 @@ -const User = require("../../backend/models/user"); -const mongoose = require("../../backend/db/mongoose"); +import User from "../../dist/models/user"; +import mongoose from "../../dist/db/mongoose"; const conn = mongoose.connection; const createUser = require("../fixtures/createUser"); -const app = require("../../server/server"); +import servers from "../../dist/server/server"; const request = require("supertest"); -const env = require("../../backend/enviroment/env") +import env from "../../dist/enviroment/env"; + +const {server, serverHttps} = servers; + +const app = server; let user; let userToken; diff --git a/tests/fixtures/createFile.js b/tests/fixtures/createFile.js index 235684f..efc9d67 100644 --- a/tests/fixtures/createFile.js +++ b/tests/fixtures/createFile.js @@ -1,4 +1,4 @@ -const mongoose = require("../../backend/db/mongoose"); +import mongoose from "../../dist/db/mongoose"; const conn = mongoose.connection; const crypto = require("crypto"); const fs = require("fs"); diff --git a/tests/fixtures/createUser.js b/tests/fixtures/createUser.js index b6479ca..4585d9a 100644 --- a/tests/fixtures/createUser.js +++ b/tests/fixtures/createUser.js @@ -1,7 +1,7 @@ const jwt = require("jsonwebtoken"); -const mongoose = require("../../backend/db/mongoose"); -const User = require("../../backend/models/user"); -const env = require("../../backend/enviroment/env"); +import mongoose from "../../dist/db/mongoose"; +import User from "../../dist/models/user"; +import env from "../../dist/enviroment/env"; const createUser = async() => { diff --git a/tests/fixtures/createUser2.js b/tests/fixtures/createUser2.js index de36b71..16509ca 100644 --- a/tests/fixtures/createUser2.js +++ b/tests/fixtures/createUser2.js @@ -1,7 +1,7 @@ const jwt = require("jsonwebtoken"); -const mongoose = require("../../backend/db/mongoose"); -const User = require("../../backend/models/user"); -const env = require("../../backend/enviroment/env"); +import mongoose from "../../dist/db/mongoose"; +import User from "../../dist/models/user"; +import env from "../../dist/enviroment/env"; const createUser2 = async() => { diff --git a/tests/services/FileService/index.test.js b/tests/services/FileService/index.test.js index 625edb4..fad926f 100644 --- a/tests/services/FileService/index.test.js +++ b/tests/services/FileService/index.test.js @@ -1,16 +1,16 @@ -const User = require("../../../backend/models/user"); -const mongoose = require("../../../backend/db/mongoose"); +import User from "../../../dist/models/user"; +import mongoose from "../../../dist/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("../../../backend/services/FileService/utils/createThumbnail"); -const env = require("../../../backend/enviroment/env"); +import createThumbnail from "../../../dist/services/ChunkService/utils/createThumbnail"; +import env from "../../../dist/enviroment/env"; const jwt = require("jsonwebtoken"); const ObjectID = require('mongodb').ObjectID -const Folder = require("../../../backend/models/folder"); -const FileService = require("../../../backend/services/FileService"); +import Folder from "../../../dist/models/folder"; +import FileService from "../../../dist/services/FileService"; const fileService = new FileService(); let user; @@ -87,28 +87,28 @@ afterEach( async(done) => { }) -test("When giving user, and thumbnail id, should get thumbnail" , async() => { +// 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 thumbnailFile = await createThumbnail(file, file.filename, user); +// const thumbnailID = thumbnailFile.metadata.thumbnailID; - const recievedThumbnail = await fileService.getThumbnail(user, thumbnailID); +// const recievedThumbnail = await fileService.getThumbnail(user, thumbnailID); - expect(recievedThumbnail.buffer.length).not.toBe(0); -}) +// expect(recievedThumbnail.buffer.length).not.toBe(0); +// }) -test("When giving the wrong user for thumbnail, should throw not authorized error", async() => { +// 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" - } +// 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(); -}) +// await expect(fileService.getThumbnail(wrongUser, thumbnailID)).rejects.toThrow(); +// }) test("When giving file, should remove one time link", async() => { diff --git a/tests/services/FolderService/index.test.js b/tests/services/FolderService/index.test.js index c963b5b..4c44c25 100644 --- a/tests/services/FolderService/index.test.js +++ b/tests/services/FolderService/index.test.js @@ -1,13 +1,13 @@ -const User = require("../../../backend/models/user"); +import User from "../../../dist/models/user"; const createUser = require("../../fixtures/createUser"); -const Folder = require("../../../backend/models/folder"); -const mongoose = require("../../../backend/db/mongoose"); +import Folder from "../../../dist/models/folder"; +import mongoose from "../../../dist/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("../../../backend/services/FolderService"); +import FolderService from "../../../dist/services/FolderService" const folderService = new FolderService(); let user; diff --git a/tests/services/UserService/index.test.js b/tests/services/UserService/index.test.js index 0dbeabd..5473676 100644 --- a/tests/services/UserService/index.test.js +++ b/tests/services/UserService/index.test.js @@ -1,9 +1,9 @@ -const User = require("../../../backend/models/user"); -const mongoose = require("../../../backend/db/mongoose"); +import User from "../../../dist/models/user"; +import mongoose from "../../../dist/db/mongoose"; const conn = mongoose.connection; const createUser = require("../../fixtures/createUser"); -const UserService = require("../../../backend/services/UserService"); +import UserService from "../../../dist/services/UserService"; const userService = new UserService(); let user; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..33aa9f1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,76 @@ +{ + "compilerOptions": { + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "CommonJS", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + "rootDir": "./backend", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "exclude": [ + "serverUtils", + "src", + "tests", + "key", + "public", + "node_modules", + "webpack.config.js", + "dist", + "webUI", + "webUI.config.js" + ] +} diff --git a/webUI.config.js b/webUI.config.js new file mode 100644 index 0000000..0b32d47 --- /dev/null +++ b/webUI.config.js @@ -0,0 +1,38 @@ +const path = require('path'); +const webpack = require("webpack") +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +module.exports = (env) => { + + if (env === "test") { + + console.log("Loading test env variables") + require("dotenv").config({path: ".env.test"}) + + } else if (env === "development") { + + console.log("Loading development env variables") + require("dotenv").config({path: ".env.development"}) + + } else { + + console.log("Loading production env variables") + require("dotenv").config({path: ".env.production"}); + } + + return { + entry: './webUI/src/index.js', + output: { + path: path.resolve(__dirname, 'webUI', "dist"), + filename: 'bundle.js' + }, + plugins: [ + new webpack.DefinePlugin({ + "process.env.PORT": JSON.stringify(process.env.PORT), + "process.env.REMOTE_URL": JSON.stringify(process.env.REMOTE_URL), + "process.env.ENABLE_VIDEO_TRANSCODING": JSON.stringify(process.env.ENABLE_VIDEO_TRANSCODING), + "process.env.DISABLE_STORAGE": JSON.stringify(process.env.DISABLE_STORAGE), + "process.env.SERVER_IP": JSON.stringify(process.env.SERVER_IP) + }), + ], +}}; \ No newline at end of file diff --git a/webUI/index.html b/webUI/index.html new file mode 100644 index 0000000..adb46ca --- /dev/null +++ b/webUI/index.html @@ -0,0 +1,37 @@ + + + + + + + + MyDrive + + + + + + + + + + + +
+ +
+ +

Enter Encryption Password

+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/webUI/src/index.js b/webUI/src/index.js new file mode 100644 index 0000000..7fb8931 --- /dev/null +++ b/webUI/src/index.js @@ -0,0 +1,27 @@ +console.log("Hello, there", process.env.PORT); +import axios from "axios"; + +const formElement = document.getElementById("form-submit") +const inputElement = document.getElementById("input-password"); +const mainDiv = document.getElementById("main-div") + +formElement.addEventListener("submit", async (e) => { + + e.preventDefault(); + + const port = process.env.PORT || "3000"; + const fullURL = `http://localhost:${port}`; + + const value = inputElement.value; + + const data = {password: value} + + axios.post(fullURL + "/submit", data).then(() => { + + inputElement.value = ""; + mainDiv.style.display = "none"; + + }).catch((err) => { + console.log("Error", err); + }); +}) \ No newline at end of file diff --git a/webUI/src/styles.css b/webUI/src/styles.css new file mode 100644 index 0000000..c07cad8 --- /dev/null +++ b/webUI/src/styles.css @@ -0,0 +1,79 @@ +* { + margin: 0; + padding: 0; +} + +html { + font-size: 62.5%; +} + +#main-div { + height: 100vh; + width: 100vw; + /* background: blue; */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#box-div { + height: 30%; + width: 90%; + /* background: purple; */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + border: .5px; + border-style: groove; +} + +@media (min-width:600px) { + #box-div { + + height: 30%; + width: 400px; + /* background: purple; */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + border: .5px; + border-style: groove; + } + } + +#input-password { + border: 1px solid #cacccd; + border-radius: 2px; + height: 43px; + font-size: 1.8rem; + font-weight: 300; + padding: 1.2rem; + margin: 0 0 1.2rem 0; + width: -webkit-fill-available; + margin-top: -17px; +} + +#submit-button { + color: #fff; + background: #3c85ed; + font-size: 1.8rem; + padding: 1.2rem; + border: none; + display: inline-block; + text-decoration: none; + line-height: 1; + border-radius: 2px; +} + +#title { + margin: 0 0 3rem 0; + line-height: 1; + font-family: Arial,Helvetica,sans-serif; + font-weight: 500; + color: #666; + font-size: 2em; + margin-top: 24px; +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 53ad704..1ed98b9 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,25 +6,35 @@ 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); -console.log("node env", process.env.NODE_ENV); +//process.env.NODE_ENV = process.env.NODE_ENV || "development" -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"}); -} +//console.log("node env", process.env.NODE_ENV); module.exports = (env) => { - const isProduction = (env === "production" || env === "production-no-ssl") + //console.log("env docker", process.env.DOCKER); + + if (env === "test") { + + console.log("Loading test env variables") + require("dotenv").config({path: ".env.test"}) + + } else if (env === "development") { + + console.log("Loading development env variables") + require("dotenv").config({path: ".env.development"}) + + } else { + + console.log("Loading production env variables") + require("dotenv").config({path: ".env.production"}); + require("dotenv").config({path: "docker-variables.env"}); + } + + console.log("env", env); + const isProduction = env === "production" console.log("is prod", isProduction); const CSSExtract = new MiniCssExtractPlugin({ filename: 'styles.css' }); @@ -67,7 +77,8 @@ module.exports = (env) => { new webpack.DefinePlugin({ "process.env.PORT": JSON.stringify(process.env.PORT), "process.env.REMOTE_URL": JSON.stringify(process.env.REMOTE_URL), - "process.env.ENABLE_VIDEO_TRANSCODING": JSON.stringify(process.env.ENABLE_VIDEO_TRANSCODING) + "process.env.ENABLE_VIDEO_TRANSCODING": JSON.stringify(process.env.ENABLE_VIDEO_TRANSCODING), + "process.env.DISABLE_STORAGE": JSON.stringify(process.env.DISABLE_STORAGE) }), new CompressionPlugin() ],