diff --git a/file-uploader/package-lock.json b/file-uploader/package-lock.json index 54fdc63..cb6b664 100644 --- a/file-uploader/package-lock.json +++ b/file-uploader/package-lock.json @@ -10,12 +10,15 @@ "license": "ISC", "dependencies": { "@prisma/client": "^6.5.0", + "@quixo3/prisma-session-store": "^3.1.13", "bcrypt": "^5.1.1", "ejs": "^3.1.10", "express": "^5.1.0", "express-session": "^1.18.1", "express-validator": "^7.2.1", - "passport": "^0.7.0" + "multer": "^1.4.5-lts.2", + "passport": "^0.7.0", + "passport-local": "^1.0.0" }, "devDependencies": { "css-loader": "^7.1.2", @@ -521,6 +524,27 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@prisma/client": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.5.0.tgz", @@ -597,6 +621,24 @@ "@prisma/debug": "6.5.0" } }, + "node_modules/@quixo3/prisma-session-store": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/@quixo3/prisma-session-store/-/prisma-session-store-3.1.13.tgz", + "integrity": "sha512-EAuOvYAaAsQ0OqxkdJG/Qs3cxlT4VV8SFHjtsA3G01uB1b6r7xftX3oeg7mcG0HN/DI1qOqwvy3YFoJ38ls0iA==", + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.0", + "ts-dedent": "^2.2.0", + "type-fest": "^2.5.2" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "@prisma/client": ">=2.16.1", + "express-session": ">=1.17.1" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -1009,6 +1051,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -1146,9 +1194,19 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1322,6 +1380,51 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -1360,6 +1463,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2539,6 +2648,12 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2754,6 +2869,15 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -2805,6 +2929,79 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3057,6 +3254,17 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -3286,6 +3494,12 @@ } } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3791,6 +4005,14 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3975,6 +4197,15 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3982,6 +4213,18 @@ "dev": true, "license": "0BSD" }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -3995,6 +4238,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -4304,6 +4553,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/file-uploader/package.json b/file-uploader/package.json index 5cd5be6..3573956 100644 --- a/file-uploader/package.json +++ b/file-uploader/package.json @@ -11,12 +11,15 @@ "description": "", "dependencies": { "@prisma/client": "^6.5.0", + "@quixo3/prisma-session-store": "^3.1.13", "bcrypt": "^5.1.1", "ejs": "^3.1.10", "express": "^5.1.0", "express-session": "^1.18.1", "express-validator": "^7.2.1", - "passport": "^0.7.0" + "multer": "^1.4.5-lts.2", + "passport": "^0.7.0", + "passport-local": "^1.0.0" }, "devDependencies": { "css-loader": "^7.1.2", diff --git a/file-uploader/prisma/migrations/20250417211854_init/migration.sql b/file-uploader/prisma/migrations/20250417211854_init/migration.sql new file mode 100644 index 0000000..eb992fb --- /dev/null +++ b/file-uploader/prisma/migrations/20250417211854_init/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sid" TEXT NOT NULL, + "data" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sid_key" ON "Session"("sid"); diff --git a/file-uploader/prisma/migrations/20250417214032_init/migration.sql b/file-uploader/prisma/migrations/20250417214032_init/migration.sql new file mode 100644 index 0000000..0cc80e8 --- /dev/null +++ b/file-uploader/prisma/migrations/20250417214032_init/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Added the required column `mimetype` to the `File` table without a default value. This is not possible if the table is not empty. + - Added the required column `size` to the `File` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "File" ADD COLUMN "mimetype" TEXT NOT NULL, +ADD COLUMN "size" INTEGER NOT NULL; diff --git a/file-uploader/prisma/migrations/20250501183435_init/migration.sql b/file-uploader/prisma/migrations/20250501183435_init/migration.sql new file mode 100644 index 0000000..fa2f31f --- /dev/null +++ b/file-uploader/prisma/migrations/20250501183435_init/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - Added the required column `full_path` to the `Folder` table without a default value. This is not possible if the table is not empty. + - Added the required column `modification_date` to the `Folder` table without a default value. This is not possible if the table is not empty. + - Added the required column `parent_folder_id` to the `Folder` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Folder" ADD COLUMN "creation_date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "full_path" TEXT NOT NULL, +ADD COLUMN "modification_date" TIMESTAMP(3) NOT NULL, +ADD COLUMN "owner_user_id" INTEGER, +ADD COLUMN "parent_folder_id" INTEGER NOT NULL; + +-- AddForeignKey +ALTER TABLE "Folder" ADD CONSTRAINT "Folder_owner_user_id_fkey" FOREIGN KEY ("owner_user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/file-uploader/prisma/migrations/20250501194745_init/migration.sql b/file-uploader/prisma/migrations/20250501194745_init/migration.sql new file mode 100644 index 0000000..ca2f07d --- /dev/null +++ b/file-uploader/prisma/migrations/20250501194745_init/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - You are about to drop the column `full_path` on the `Folder` table. All the data in the column will be lost. + - You are about to drop the column `parent_folder_id` on the `Folder` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Folder" DROP COLUMN "full_path", +DROP COLUMN "parent_folder_id", +ADD COLUMN "parentId" INTEGER; + +-- AddForeignKey +ALTER TABLE "Folder" ADD CONSTRAINT "Folder_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/file-uploader/prisma/migrations/20250501195045_init/migration.sql b/file-uploader/prisma/migrations/20250501195045_init/migration.sql new file mode 100644 index 0000000..003e322 --- /dev/null +++ b/file-uploader/prisma/migrations/20250501195045_init/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Folder" ALTER COLUMN "modification_date" SET DEFAULT CURRENT_TIMESTAMP; diff --git a/file-uploader/prisma/schema.prisma b/file-uploader/prisma/schema.prisma index f677305..4534c09 100644 --- a/file-uploader/prisma/schema.prisma +++ b/file-uploader/prisma/schema.prisma @@ -15,17 +15,20 @@ datasource db { } model User { - id Int @id @default(autoincrement()) - username String @unique @db.VarChar(50) - email String @unique + id Int @id @default(autoincrement()) + username String @unique @db.VarChar(50) + email String @unique password String files File[] + Folder Folder[] } model File { id Int @id @default(autoincrement()) name String @db.VarChar(255) url String + size Int + mimetype String createdAt DateTime @default(now()) owner User? @relation(fields: [ownerId], references: [id]) ownerId Int? @@ -34,7 +37,21 @@ model File { } model Folder { - id Int @id @default(autoincrement()) - name String @db.VarChar(255) - File File[] + id Int @id @default(autoincrement()) + name String @db.VarChar(255) + creation_date DateTime @default(now()) + modification_date DateTime @default(now()) + File File[] + owner User? @relation(fields: [owner_user_id], references: [id]) + owner_user_id Int? + parentId Int? + parent Folder? @relation("ParentDirectory", fields: [parentId], references: [id]) + Directories Folder[] @relation("ParentDirectory") +} + +model Session { + id String @id + sid String @unique + data String + expiresAt DateTime } diff --git a/file-uploader/public/css/style.css b/file-uploader/public/css/style.css new file mode 100644 index 0000000..ab92e28 --- /dev/null +++ b/file-uploader/public/css/style.css @@ -0,0 +1,142 @@ +/* 1. Use a more-intuitive box-sizing model */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* 2. Remove default margin */ +* { + margin: 0; +} + +/* 3. Enable keyword animations */ +@media (prefers-reduced-motion: no-preference) { + html { + interpolate-size: allow-keywords; + } +} + +body { + /* 4. Add accessible line-height */ + line-height: 1.5; + /* 5. Improve text rendering */ + -webkit-font-smoothing: antialiased; +} + +/* 6. Improve media defaults */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +/* 7. Inherit fonts for form controls */ +input, +button, +textarea, +select { + font: inherit; +} + +/* 8. Avoid text overflows */ +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +/* 9. Improve line wrapping */ +p { + text-wrap: pretty; +} +h1, +h2, +h3, +h4, +h5, +h6 { + text-wrap: balance; +} + +/* + 10. Create a root stacking context +*/ +#root, +#__next { + isolation: isolate; +} + +.register-form { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.form-input { + display: flex; + flex-direction: column; + gap: 0.5em; +} + +input[type='file'] { + outline: none; + padding: 4px; + margin: -4px; +} + +input[type='file']::file-selector-button { + cursor: pointer; + border-radius: 4px; + padding: 0 1em; + height: 2em; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.05); + margin-right: 16px; + transition: background-color 200ms; +} + +/* file upload button hover state */ +input[type='file']::file-selector-button:hover { + background-color: #f3f4f6; +} + +/* file upload button active state */ +input[type='file']::file-selector-button:active { + background-color: #e5e7eb; +} + +input[type='file'] { + position: relative; + outline: none; + padding: 4px; + margin: -4px; +} + +input[type='file']:focus-within::file-selector-button, +input[type='file']:focus::file-selector-button { + outline: 2px solid #0964b0; + outline-offset: 2px; +} + +@supports (-moz-appearance: none) { + input[type='file']::file-selector-button { + color: #0964b0; + } +} + +/* .file { */ +/* opacity: 0; */ +/* width: 0.1px; */ +/* height: 0.1px; */ +/* position: absolute; */ +/* } */ diff --git a/file-uploader/public/js/form.js b/file-uploader/public/js/form.js index 6bb79b4..f83c9a0 100644 --- a/file-uploader/public/js/form.js +++ b/file-uploader/public/js/form.js @@ -1,14 +1,57 @@ -console.log('form stuff'); - const usernameCheck = async (username) => { - let res = await fetch(`/auth/username?username=${username}`); - return await res.json(); + if (username.length > 0) { + let res = await fetch(`/auth/username?username=${username}`); + const data = await res.json(); + // return true if username is available + return data.results === false; + } }; -const input = document.querySelector("input[name='username']"); +const emailCheck = async (email) => { + if (email.length > 0) { + let res = await fetch(`/auth/email?email=${email}`); + const data = await res.json(); + return data.results === false; + } +}; -input.addEventListener('input', async (e) => { - let input = e.target.value; - let test = await usernameCheck(input); - test.results ? console.log('user exists') : console.log('user doesnt'); -}); +const isDataValid = async (e, callback) => { + let test = await callback(e.target.value); + test + ? (e.target.style.border = '3px green solid') + : (e.target.style.border = 'red solid 3px'); +}; + +const passwordValid = () => { + const password = document.querySelector('#password'); + const passwordConfirmation = document.querySelector( + '#password-confirmation', + ); + + const passwordConfirmationCheck = (value) => { + return password.value === value; + }; + + const passwordValidityCheck = (password) => { + if (!/\d/.test(password)) return false; + if (!/[\!@#%\^&\*\(\)\_\+\-=\[\]{}\|;':",\.\/\<\>\~\`]/.test(password)) + return false; + if (password.length < 8) return false; + + return true; + }; + password.addEventListener('input', (e) => + isDataValid(e, passwordValidityCheck), + ); + passwordConfirmation.addEventListener('input', (e) => + isDataValid(e, passwordConfirmationCheck), + ); +}; + +const usernameInput = document.querySelector("input[name='username']"); +usernameInput.addEventListener('input', (e) => isDataValid(e, usernameCheck)); + +const emailInput = document.querySelector("input[name='email']"); +emailInput.addEventListener('input', (e) => isDataValid(e, emailCheck)); + +passwordValid(); diff --git a/file-uploader/src/controllers/authController.js b/file-uploader/src/controllers/authController.js index 62eabb3..bac5ea0 100644 --- a/file-uploader/src/controllers/authController.js +++ b/file-uploader/src/controllers/authController.js @@ -1,21 +1,30 @@ -const { Auth } = require('../models/auth'); +const Db = require('../models/db'); const bcrypt = require('bcrypt'); -const db = new Auth(); +const db = new Db(); const loginGet = (req, res, next) => { res.send('Login Route'); }; +const logout = (req, res, next) => { + req.logout((err) => { + if (err) { + return next(err); + } + res.redirect('/'); + }); +}; + const registerGet = (req, res) => { - res.render('register'); + res.render('register', { pageTitle: 'Register' }); }; const registerPost = async (req, res, next) => { const { username, email, password } = req.body; try { - const hashedPassword = bcrypt.hash(password); - const result = await db.createUser({ + const hashedPassword = await bcrypt.hash(password, 10); + const result = await db.auth.createUser({ username: username, password: hashedPassword, email: email, @@ -38,7 +47,7 @@ const findByUsername = async (req, res, next) => { try { if (username && username.length > 0) { - const exists = (await db.getUserByUsername(username)) + const exists = (await db.auth.getUserByUsername(username)) ? true : false; @@ -50,4 +59,24 @@ const findByUsername = async (req, res, next) => { } }; -module.exports = { findByUsername, loginGet, registerPost, registerGet }; +const findByEmail = async (req, res, next) => { + const { email } = req.query; + try { + if (email && email.length > 0) { + const exists = (await db.auth.getByEmail(email)) ? true : false; + const data = { results: exists }; + res.json(data); + } + } catch (e) { + res.json({ error: e }); + } +}; + +module.exports = { + findByUsername, + findByEmail, + loginGet, + logout, + registerPost, + registerGet, +}; diff --git a/file-uploader/src/controllers/fileController.js b/file-uploader/src/controllers/fileController.js new file mode 100644 index 0000000..c4b9f1d --- /dev/null +++ b/file-uploader/src/controllers/fileController.js @@ -0,0 +1,65 @@ +const multer = require('multer'); +const Db = require('../models/db'); +const upload = multer({ dest: '/tmp/testing/' }); + +const db = new Db(); +// +// const createUserRootDir = async () => { +// const user = await db.auth.getUserByUsername(username); +// console.log(`USERID: ${user.id}`); +// const dir = await db.file.createDirectory(user, 'root'); +// if (dir.error) { +// console.log(dir.error); +// } else { +// console.log(dir.message); +// } +// }; + +const createDirectory = async (req, res) => { + const { parentId, 'directory-name': directoryName } = req.body; + + const result = await db.file.createDirectory( + req.user, + directoryName, + Number(parentId), + ); + + res.redirect('/'); +}; + +const fileUpload = async (req, res) => { + console.log(req.file); + const { folderId } = req.body; + const file = { + name: req.file.originalname, + path: req.file.path, + filename: req.file.filename, + folderId: Number(folderId), + mimetype: req.file.mimetype, + size: req.file.size, + }; + console.log(file, req.user); + try { + const result = await db.file.createFile(req.user.id, file); + console.log(result); + } catch (e) { + console.log(e); + } + + res.redirect('/'); +}; + +const userDirs = (req, res) => { + if (req.user) { + // logic to grab the current dir based on path maybe + // logic to grab all user dirs? + } +}; + +const getFiles = (req, res) => { + if (req.user) { + // logic to grab the files + } +}; + +module.exports = { createDirectory, upload, fileUpload }; diff --git a/file-uploader/src/controllers/indexController.js b/file-uploader/src/controllers/indexController.js index 77bdda6..bd51222 100644 --- a/file-uploader/src/controllers/indexController.js +++ b/file-uploader/src/controllers/indexController.js @@ -1,9 +1,17 @@ -// const db = require("../models/auth"); +const Db = require('../models/db'); + +const db = new Db(); const indexGet = async (req, res) => { - // const data = await db.allUsers() - // console.dir(data) - res.render("main"); + // const data = await db.allUsers() + // console.dir(data) + if (req.user) { + const id = await db.file.getUserRootDirectoryId(req.user.id); + if (id === null) console.log('id is null'); + console.log(id); + res.render('main', { pageTitle: 'FileUpload', folder: { id: id } }); + } else { + res.render('main', { pageTitle: 'FileUpload', folder: {} }); + } }; module.exports = { indexGet }; - diff --git a/file-uploader/src/index.js b/file-uploader/src/index.js index 14b252d..fc3eaa9 100644 --- a/file-uploader/src/index.js +++ b/file-uploader/src/index.js @@ -1,6 +1,8 @@ const express = require('express'); -const session = require('express-session'); -const passport = require('passport'); +const expressSession = require('express-session'); +const { PrismaSessionStore } = require('@quixo3/prisma-session-store'); +const { PrismaClient } = require('@prisma/client'); +const { passport } = require('./middlewares/auth'); const path = require('node:path'); const port = process.env.APP_PORT || 3000; @@ -9,17 +11,40 @@ const app = express(); const assetsPath = path.join(path.dirname(__dirname), 'public'); app.use(express.static(assetsPath)); +app.use( + expressSession({ + cookie: { + maxAge: 7 * 24 * 60 * 60 * 1000, + }, + secret: 'deez', + resave: false, + saveUninitialized: false, + store: new PrismaSessionStore(new PrismaClient(), { + checkPeriod: 2 * 60 * 1000, + dbRecordIdFunction: undefined, + dbRecordIdIsSessionId: true, + }), + }), +); +app.use(passport.session()); +app.use(express.urlencoded({ extended: false })); + const authRouter = require('./routes/authRouter'); const indexRouter = require('./routes/indexRouter'); +const fileRouter = require('./routes/fileRouter'); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); -app.use(express.urlencoded({ extended: false })); + +app.use((req, res, next) => { + res.locals.currentUser = req.user; + next(); +}); app.use('/', indexRouter); app.use('/auth', authRouter); +app.use('/file', fileRouter); app.listen(port, () => { console.log(`App listening on ${port}`); }); - diff --git a/file-uploader/src/middlewares/auth.js b/file-uploader/src/middlewares/auth.js new file mode 100644 index 0000000..12f4129 --- /dev/null +++ b/file-uploader/src/middlewares/auth.js @@ -0,0 +1,51 @@ +const bcrypt = require('bcrypt'); +const passport = require('passport'); +const Db = require('../models/db'); +const LocalStrategy = require('passport-local').Strategy; + +const db = new Db(); + +passport.use( + new LocalStrategy(async (username, password, done) => { + try { + const user = await db.auth.getUserByUsername(username); + + if (!user) { + return done(null, false, { + message: 'Incorrect username or passowrd', + }); + } + + const passwordMatch = await bcrypt.compare(password, user.password); + if (!passwordMatch) { + return done(null, false, { + message: 'inccorect username or password', + }); + } + + // ensure users root directory has been created + const rootDirId = await db.file.getUserRootDirectoryId(user.id); + console.log(rootDirId); + if (rootDirId === null) + await db.file.createDirectory(user, 'root', null); + return done(null, user); + } catch (err) { + return done(err); + } + }), +); + +passport.serializeUser((user, done) => { + done(null, user.id); +}); + +passport.deserializeUser(async (id, done) => { + try { + const user = await db.auth.getUserById(id); + done(null, user); + } catch (err) { + done(err); + } +}); + +module.exports = { passport }; diff --git a/file-uploader/src/models/auth.js b/file-uploader/src/models/auth.js index 284d0a7..c6dca14 100644 --- a/file-uploader/src/models/auth.js +++ b/file-uploader/src/models/auth.js @@ -1,45 +1,72 @@ -const { PrismaClient, Prisma } = require("@prisma/client"); +const { PrismaClient, Prisma } = require('@prisma/client'); // const { Prisma } = require("prisma"); class Auth { - constructor() { - this.primsa = new PrismaClient(); - } - async createUser(user) { - try { - await this.primsa.user.create({ - data: { - username: user.username, - email: user?.email, - password: user.password, - }, - }); - return { message: "user created" }; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - if (e.code === "P2002") { - console.log("User must use a unique username"); - return { error: "must use unique username" }; + constructor() { + this.prisma = new PrismaClient(); + } + async createUser(user) { + try { + await this.prisma.user.create({ + data: { + username: user.username, + email: user?.email, + password: user.password, + }, + }); + return { message: 'user created' }; + } catch (e) { + console.log(`PRISMA error ${e}`); + if (e instanceof Prisma.PrismaClientKnownRequestError) { + if (e.code === 'P2002') { + console.log('There is a unique constraint violation'); + return { + error: 'must use unique username and a unique email.', + }; + } + } } - } } - } - async allUsers() { - return await this.primsa.user.findMany(); - } - - async getUserByUsername(username) { - try { - return await this.primsa.user.findUnique({ - where: { - username: username, - }, - }); - } catch (e) { - return { error: e }; + async allUsers() { + return await this.prisma.user.findMany(); + } + + async getUserByUsername(username) { + try { + return await this.prisma.user.findUnique({ + where: { + username: username, + }, + }); + } catch (e) { + return { error: e }; + } + } + + async getUserById(id) { + try { + return await this.prisma.user.findUnique({ + where: { + id: id, + }, + }); + } catch (e) { + return { error: e }; + } + } + + async getByEmail(email) { + try { + return await this.prisma.user.findUnique({ + where: { + email: email, + }, + }); + } catch (e) { + return { error: e }; + } } - } } // async function main() { @@ -58,4 +85,3 @@ class Auth { // }) module.exports = { Auth }; - diff --git a/file-uploader/src/models/db.js b/file-uploader/src/models/db.js new file mode 100644 index 0000000..2b1749b --- /dev/null +++ b/file-uploader/src/models/db.js @@ -0,0 +1,11 @@ +const { Auth } = require('./auth'); +const { File } = require('./file'); + +class Db { + constructor() { + this.auth = new Auth(); + this.file = new File(); + } +} + +module.exports = Db; diff --git a/file-uploader/src/models/file.js b/file-uploader/src/models/file.js new file mode 100644 index 0000000..d5c222c --- /dev/null +++ b/file-uploader/src/models/file.js @@ -0,0 +1,77 @@ +const { PrismaClient } = require('@prisma/client'); + +class File { + constructor() { + this.prisma = new PrismaClient(); + } + + async createDirectory(user, dirName, parentId) { + try { + await this.prisma.folder.create({ + data: { + name: dirName, + owner_user_id: user.id, + parentId: parentId, + }, + }); + return { message: 'dir created' }; + } catch (e) { + console.error(e); + return { error: 'issue creating dir' }; + } + } + + async getDirectoryIdByName(dirId, userId) { + try { + await this.prisma.folder.findUnique({ + where: { + id: dirId, + }, + }); + } catch (e) { + console.error(e); + } + } + + async getUserRootDirectoryId(userId) { + try { + const dir = await this.prisma.folder.findFirst({ + where: { + owner_user_id: userId, + name: 'root', + parentId: null, + }, + }); + + if (dir !== null) { + return dir.id; + } else { + return null; + } + } catch (e) { + console.error(e); + return null; + } + } + + async getDirectoriesByUser() {} + + async createFile(userId, file) { + try { + await this.prisma.file.create({ + data: { + name: file.name, + size: file.size, + mimetype: file.mimetype, + url: file.path, + folderId: file.folderId, + ownerId: userId, + }, + }); + } catch (e) { + console.error(e); + } + } +} + +module.exports = { File }; diff --git a/file-uploader/src/routes/authRouter.js b/file-uploader/src/routes/authRouter.js index 5c478a2..47b922a 100644 --- a/file-uploader/src/routes/authRouter.js +++ b/file-uploader/src/routes/authRouter.js @@ -1,12 +1,21 @@ const { Router } = require('express'); const authController = require('../controllers/authController'); +const { passport } = require('../middlewares/auth'); const authRouter = Router(); authRouter.get('/login', authController.loginGet); +authRouter.post( + '/login', + passport.authenticate('local', { + successRedirect: '/', + failureRedirect: '/', + }), +); +authRouter.get('/logout', authController.logout); authRouter.get('/register', authController.registerGet); authRouter.post('/register', authController.registerPost); authRouter.get('/username', authController.findByUsername); +authRouter.get('/email', authController.findByEmail); module.exports = authRouter; - diff --git a/file-uploader/src/routes/fileRouter.js b/file-uploader/src/routes/fileRouter.js new file mode 100644 index 0000000..3a33c6e --- /dev/null +++ b/file-uploader/src/routes/fileRouter.js @@ -0,0 +1,13 @@ +const { Router } = require('express'); +const fileController = require('../controllers/fileController'); + +const fileRouter = Router(); + +fileRouter.post( + '/', + fileController.upload.single('fileUpload'), + fileController.fileUpload, +); +fileRouter.post('/directory', fileController.createDirectory); + +module.exports = fileRouter; diff --git a/file-uploader/src/views/main.ejs b/file-uploader/src/views/main.ejs index 6510ffe..ac5deba 100644 --- a/file-uploader/src/views/main.ejs +++ b/file-uploader/src/views/main.ejs @@ -1,13 +1,10 @@ - - - -
- - -Hello <%= currentUser.username %>
+ sign out + <%- include('partials/fileUpload') %> + <% } else { %> - - \ No newline at end of file + <% } %> +<%- include('partials/footer') %> diff --git a/file-uploader/src/views/partials/fileUpload.ejs b/file-uploader/src/views/partials/fileUpload.ejs new file mode 100644 index 0000000..ed6b2d7 --- /dev/null +++ b/file-uploader/src/views/partials/fileUpload.ejs @@ -0,0 +1,17 @@ + + + + diff --git a/file-uploader/src/views/partials/footer.ejs b/file-uploader/src/views/partials/footer.ejs new file mode 100644 index 0000000..15b9011 --- /dev/null +++ b/file-uploader/src/views/partials/footer.ejs @@ -0,0 +1,3 @@ +