diff --git a/.gitignore b/.gitignore index 8055497..a449874 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +### Security ### +.env.local +.env.prod + node_modules HELP.md target/ diff --git a/bruno/VoD-System/Edit/Edit.bru b/bruno/VoD-System/Edit/Edit.bru index 13bce1f..4836047 100644 --- a/bruno/VoD-System/Edit/Edit.bru +++ b/bruno/VoD-System/Edit/Edit.bru @@ -11,6 +11,6 @@ post { } body:form-urlencoded { - startPoint: 30 - endPoint: 120 + startPoint: 130 + endPoint: 140 } diff --git a/docker-compose.yml b/docker-compose.yml index 5d62a4a..38dfd15 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: POSTGRES_USER: myuser POSTGRES_PASSWORD: mypassword - POSTGRES_DB: mydb + POSTGRES_DB: vodSystem ports: - "5432:5432" volumes: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 84131bb..2a1c6b0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,10 +7,14 @@ "": { "name": "frontend", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { "@tailwindcss/vite": "^4.1.7", "clsx": "^2.1.1", + "dotenv": "^16.5.0", + "flowbite-react": "^0.11.8", "lucide-react": "^0.511.0", + "path": "^0.12.7", "react": "^19.1.0", "react-dom": "^19.1.0", "react-range-slider-input": "^3.2.1", @@ -880,6 +884,68 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", + "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom/node_modules/@floating-ui/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", + "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz", + "integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz", + "integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -946,6 +1012,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1010,7 +1082,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1024,7 +1095,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1034,7 +1104,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2002,11 +2071,28 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/brace-expansion": { @@ -2024,7 +2110,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2114,6 +2199,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2123,6 +2223,12 @@ "node": ">=18" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2152,6 +2258,22 @@ "dev": true, "license": "MIT" }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2186,6 +2308,12 @@ "url": "https://opencollective.com/core-js" } }, + "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", @@ -2208,11 +2336,22 @@ "dev": true, "license": "MIT" }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2233,6 +2372,15 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge-ts": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.4.tgz", + "integrity": "sha512-fxqo6nHGQ9zOVgI4KXqtWXJR/yCLtC7aXIVq+6jc8tHPFUxlFmuUcm2kC4vztQ+LJxQ3gER/XAWearGYQ8niGA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2242,6 +2390,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.159", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz", @@ -2430,7 +2590,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2457,6 +2616,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -2514,7 +2686,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2531,7 +2702,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -2558,7 +2728,6 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -2581,7 +2750,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -2628,6 +2796,128 @@ "dev": true, "license": "ISC" }, + "node_modules/flowbite-react": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.11.8.tgz", + "integrity": "sha512-ntaPUGaVqnN/dk0ivdc2UAhoCMVCyMhWj6uT9mkUueXxeSL3Q+/4opcLXMsuIrJdRrPJQIQI8ptd3AoGsxnQMg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "1.6.9", + "@floating-ui/react": "0.27.3", + "@iarna/toml": "2.2.5", + "@typescript-eslint/typescript-estree": "8.26.0", + "chokidar": "4.0.3", + "classnames": "2.5.1", + "comment-json": "4.2.5", + "debounce": "2.2.0", + "deepmerge-ts": "7.1.4", + "klona": "2.0.6", + "package-manager-detector": "0.2.9", + "recast": "0.23.11", + "tailwind-merge-v2": "npm:tailwind-merge@2.6.0", + "tailwind-merge-v3": "npm:tailwind-merge@3.0.1" + }, + "bin": { + "flowbite-react": "dist/cli/bin.js" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "tailwindcss": "^3 || ^4" + } + }, + "node_modules/flowbite-react/node_modules/@typescript-eslint/types": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/flowbite-react/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/flowbite-react/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.26.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/flowbite-react/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/flowbite-react/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flowbite-react/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2701,6 +2991,15 @@ "node": ">=8" } }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2738,11 +3037,16 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2752,7 +3056,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -2765,7 +3068,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -2864,6 +3166,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3161,7 +3472,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3171,7 +3481,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3234,7 +3543,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3319,6 +3627,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", + "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3332,6 +3646,16 @@ "node": ">=6" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3362,7 +3686,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -3409,6 +3732,15 @@ "node": ">= 0.8.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3423,7 +3755,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -3528,6 +3859,44 @@ "react-dom": ">=18" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3542,7 +3911,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -3592,7 +3960,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -3657,6 +4024,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3692,6 +4068,34 @@ "node": ">=8" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tailwind-merge-v2": { + "name": "tailwind-merge", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-merge-v3": { + "name": "tailwind-merge", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz", + "integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz", @@ -3733,6 +4137,12 @@ "node": ">=18" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -3779,7 +4189,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -3792,7 +4201,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -3801,6 +4209,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3818,7 +4232,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -3892,6 +4305,15 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index d1a89d6..d265c35 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,12 +8,16 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "postinstall": "flowbite-react patch" }, "dependencies": { "@tailwindcss/vite": "^4.1.7", "clsx": "^2.1.1", + "dotenv": "^16.5.0", + "flowbite-react": "^0.11.8", "lucide-react": "^0.511.0", + "path": "^0.12.7", "react": "^19.1.0", "react-dom": "^19.1.0", "react-range-slider-input": "^3.2.1", diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx new file mode 100644 index 0000000..a818fa1 --- /dev/null +++ b/frontend/src/components/Dropdown.tsx @@ -0,0 +1,65 @@ +import React, {useEffect, useRef} from "react"; +import clsx from "clsx"; + +type DropdownItemProps = { + item: string; + onClick: (item: string) => void; + className?: string; +} + +const DropdownItem = ({ item, onClick, className }: DropdownItemProps) => { + return ( +
  • onClick(item)}> + {item} +
  • + ); +} + +type DropdownProps = { + label: string; + children: React.ReactNode; + className?: string; +} + +const Dropdown = ({ label, children, className }: DropdownProps) => { + const [isOpen, setIsOpen] = React.useState(false); + const ref = useRef(null); + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + useEffect(() => { + function handleClickOutside(event: { target: any; }) { + if (ref.current && !ref.current.contains(event.target)) { + setIsOpen(false); + } + } + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
    + + {isOpen && ( + + )} +
    + ); +}; + + + +export { Dropdown, DropdownItem }; \ No newline at end of file diff --git a/frontend/src/components/Selector.tsx b/frontend/src/components/Selector.tsx index afb575f..7792c7a 100644 --- a/frontend/src/components/Selector.tsx +++ b/frontend/src/components/Selector.tsx @@ -12,7 +12,7 @@ const Selector = ({children, label}: props) => { className={"w-full"}> { label } -
    +
    {children}
    ) diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index dcfdb47..4e6e272 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -2,12 +2,14 @@ import clsx from "clsx"; import SidebarButton from "./buttons/SidebarButton.tsx"; import { Plus, Film, Home } from 'lucide-react'; import Box from "./Box.tsx"; +import type {User} from "../utils/types.ts"; type props = { + user: User | null; className?: string } -const Sidebar = ({className}: props) => { +const Sidebar = ({user, className}: props) => { return ( { label={"Create Clip"} /> - } - label={"My Clips"} - /> + { user && ( + } + label={"My Clips"} + /> + )} ); }; diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index 46a5c6c..3fdfbdb 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -1,19 +1,49 @@ -import { Menu, X} from 'lucide-react'; +import { Menu, X } from 'lucide-react'; import MenuButton from "./buttons/MenuButton.tsx"; import clsx from "clsx"; +import type {User} from "../utils/types.ts"; +import { Dropdown, DropdownItem } from "./Dropdown.tsx"; type props = { - sidebarToggled: boolean, - setSidebarToggled: Function + sidebarToggled: boolean; + setSidebarToggled: Function; + user: User | null; className?: string; } -const Topbar = ({sidebarToggled, setSidebarToggled, className}: props) => { +const Topbar = ({sidebarToggled, setSidebarToggled, user, className}: props) => { + const apiUrl = import.meta.env.VITE_API_URL; + const loginUrl = `${apiUrl}/oauth2/authorization/google`; + const logoutUrl = `${apiUrl}/api/v1/auth/logout`; + return ( -
    +
    setSidebarToggled(!sidebarToggled)}> {sidebarToggled ? : } + + { user ? ( +
    + + + + globalThis.location.href = logoutUrl} + className={"text-red-500 font-medium"} /> + +
    + ) : + ( + globalThis.location.href = loginUrl}> + Login + + )} +
    ) } diff --git a/frontend/src/index.css b/frontend/src/index.css index 90c61ce..fc20e05 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@plugin "flowbite-react/plugin/tailwindcss"; +@source "../.flowbite-react/class-list.json"; @theme { diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 1490916..db8d02d 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -2,19 +2,37 @@ import Sidebar from '../components/Sidebar' import Topbar from '../components/Topbar' import { Outlet } from 'react-router-dom'; -import {useState} from "react"; +import {useEffect, useState} from "react"; +import type {User} from "../utils/types"; +import { getUser } from "../utils/endpoints"; const MainLayout = () => { const [sidebarToggled, setSidebarToggled] = useState(false); + const [user, setUser] = useState(null); + + useEffect(() => { + const fetchUser = async () => { + try { + const userData = await getUser(); + setUser(userData); + } catch (error) { + console.error('Failed to fetch user:', error); + } + }; + + fetchUser(); + }, []); return (
    + setSidebarToggled={setSidebarToggled} + user={user}/>
    diff --git a/frontend/src/pages/ClipEdit.tsx b/frontend/src/pages/ClipEdit.tsx index e43959f..db93065 100644 --- a/frontend/src/pages/ClipEdit.tsx +++ b/frontend/src/pages/ClipEdit.tsx @@ -129,7 +129,7 @@ const ClipEdit = () => { /> {error && ( -
    +
    {error}
    )} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index a2bdad0..85e3807 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,39 +1,39 @@ const Home = () => { return ( -
    +
    {/* Logo */} VoD System Logo {/* Description Container */} -
    -

    +
    +

    What is the VoD System?

    -

    +

    The VoD System is a powerful clip management platform designed to streamline how you handle your video content. Whether you're a content creator, streamer, or educator, VoD System lets you:

    -
      +
      • Upload clips effortlessly and securely.
      • Edit and trim videos with intuitive controls.
      • Compress files to specific file sizes.
      • Organize your clips for quick access and sharing.
      -

      +

      Designed with simplicity and efficiency in mind, VoD System adapts to your workflow, making video clip management faster and more enjoyable than ever.

    {/* File Support Note */} -
    +
    Note: Currently, only .mp4 files are supported for upload and processing. Support for additional video formats will be added in future updates.
    diff --git a/frontend/src/utils/endpoints.ts b/frontend/src/utils/endpoints.ts index a916dfc..a5b20fe 100644 --- a/frontend/src/utils/endpoints.ts +++ b/frontend/src/utils/endpoints.ts @@ -1,4 +1,4 @@ -import type {VideoMetadata, APIResponse} from "./types.ts"; +import type {VideoMetadata, APIResponse, User} from "./types.ts"; /** * Uploads a file to the backend. @@ -129,10 +129,23 @@ const getMetadata = async (uuid: string): Promise => { } }; +const getUser = async (): Promise => { + try { + const response = await fetch('/api/v1/auth/user', {credentials: "include",}); + + const result = await response.json(); + return result.data; + } catch (error: unknown) { + console.error('Error fetching user:', error); + return null; + } +} + export { uploadFile, editFile, processFile, getProgress, getMetadata, + getUser }; \ No newline at end of file diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index 0572b48..ffb6931 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -13,7 +13,14 @@ type APIResponse = { message: string } +type User = { + name: string, + email: string, + profilePicture: string +} + export type { APIResponse, - VideoMetadata + VideoMetadata, + User } \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e75ff99..7559c01 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,11 +1,20 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' +import flowbiteReact from "flowbite-react/plugin/vite"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react(), tailwindcss()], + plugins: [react(), tailwindcss(), flowbiteReact()], + preview: { + port: 5173, + strictPort: true, + }, server: { + port: 5173, + strictPort: true, + host: true, + origin: "http://0.0.0.0:5173", proxy: { '/api/v1': { target: 'http://localhost:8080', @@ -14,4 +23,4 @@ export default defineConfig({ } } } -}) +}) \ No newline at end of file diff --git a/pom.xml b/pom.xml index af35711..d722517 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,14 @@ runtime true + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-security + diff --git a/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java new file mode 100644 index 0000000..d7d8d04 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/configuration/SecurityConfig.java @@ -0,0 +1,63 @@ +package com.ddf.vodsystem.configuration; + +import com.ddf.vodsystem.security.CustomOAuth2UserService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final CustomOAuth2UserService customOAuth2UserService; + + @Value("${frontend.url}") + private String frontendUrl; + + public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) { + this.customOAuth2UserService = customOAuth2UserService; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/auth/login", "/api/v1/auth/user").permitAll() + .requestMatchers("/api/v1/upload", "/api/v1/download/**").permitAll() + .requestMatchers("/api/v1/edit/**", "/api/v1/process/**", "/api/v1/progress/**").permitAll() + .requestMatchers("/api/v1/metadata/**").permitAll() + .anyRequest().authenticated() + ) + .oauth2Login(oauth2 -> oauth2 + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + ) + .successHandler(successHandler())) + .logout(logout -> logout + .logoutUrl("/api/v1/auth/logout") + .logoutSuccessHandler(logoutSuccessHandler()) + .invalidateHttpSession(true) + .deleteCookies("JSESSIONID") + ); + + return http.build(); + } + + @Bean + public AuthenticationSuccessHandler successHandler() { + return (request, response, authentication) -> response.sendRedirect(frontendUrl); + } + + @Bean + public LogoutSuccessHandler logoutSuccessHandler() { + return (request, response, authentication) -> response.sendRedirect(frontendUrl); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/controllers/AuthController.java b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java new file mode 100644 index 0000000..de455e0 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/controllers/AuthController.java @@ -0,0 +1,48 @@ +package com.ddf.vodsystem.controllers; + +import com.ddf.vodsystem.entities.APIResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/auth/") +public class AuthController { + + @GetMapping("/user") + public ResponseEntity>> user(@AuthenticationPrincipal OAuth2User principal) { + if (principal == null) { + return ResponseEntity.status(HttpStatus.FORBIDDEN). + body(new APIResponse<>( + "error", + "User not authenticated", + null + )); + } + + if (principal.getAttribute("email") == null + || principal.getAttribute("name") == null + || principal.getAttribute("picture") == null) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST). + body(new APIResponse<>( + "error", + "Required user attributes are missing", + null + )); + } + + return ResponseEntity.ok( + new APIResponse<>("success", "User details retrieved successfully", Map.of( + "name", principal.getAttribute("name"), + "email", principal.getAttribute("email"), + "profilePicture", principal.getAttribute("picture")) + ) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/entities/Clip.java b/src/main/java/com/ddf/vodsystem/entities/Clip.java new file mode 100644 index 0000000..f58d6ea --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/Clip.java @@ -0,0 +1,49 @@ +package com.ddf.vodsystem.entities; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "clips") +@Data +public class Clip { + @Id + @Column(name = "id", nullable = false, unique = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @ToString.Exclude + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "title", nullable = false, length = 255) + private String title; + + @Column(name = "description") + private String description; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "width", nullable = false) + private Integer width; + + @Column(name = "height", nullable = false) + private Integer height; + + @Column(name = "fps", nullable = false) + private Integer fps; + + @Column(name = "duration", nullable = false) + private Integer duration; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "video_path", nullable = false, length = 255) + private String videoPath; +} diff --git a/src/main/java/com/ddf/vodsystem/entities/User.java b/src/main/java/com/ddf/vodsystem/entities/User.java new file mode 100644 index 0000000..20e47a2 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/User.java @@ -0,0 +1,35 @@ +package com.ddf.vodsystem.entities; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "users") +@Data +public class User { + @Id + @Column(name = "id", nullable = false, unique = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "google_id", nullable = false, unique = true) + private String googleId; + + @Column(name = "username", nullable = false, unique = true) + private String username; + + @Column(name = "email", nullable = false, unique = true) + private String email; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "role", nullable = false) + private Integer role; // 0: user, 1: admin + + @Column(name = "created_at") + private LocalDateTime createdAt; +} + diff --git a/src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java b/src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java new file mode 100644 index 0000000..5c804ff --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/ClipRepository.java @@ -0,0 +1,9 @@ +package com.ddf.vodsystem.repositories; + +import com.ddf.vodsystem.entities.Clip; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ClipRepository extends JpaRepository { +} diff --git a/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java b/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java new file mode 100644 index 0000000..50262d6 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/repositories/UserRepository.java @@ -0,0 +1,14 @@ +package com.ddf.vodsystem.repositories; + +import com.ddf.vodsystem.entities.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + @Query("SELECT u FROM User u WHERE u.googleId = ?1") + Optional findByGoogleId(String googleId); +} diff --git a/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java new file mode 100644 index 0000000..2cf1212 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/security/CustomOAuth2UserService.java @@ -0,0 +1,47 @@ +package com.ddf.vodsystem.security; +import com.ddf.vodsystem.entities.User; +import com.ddf.vodsystem.repositories.UserRepository; + +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + + +@Service +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final UserRepository userRepository; + + public CustomOAuth2UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + String email = oAuth2User.getAttribute("email"); + String name = oAuth2User.getAttribute("name"); + String googleId = oAuth2User.getAttribute("sub"); + + Optional existingUser = userRepository.findByGoogleId(googleId); + + if (existingUser.isEmpty()) { + User user = new User(); + user.setEmail(email); + user.setName(name); + user.setGoogleId(googleId); + user.setUsername(email); + user.setRole(0); + user.setCreatedAt(LocalDateTime.now()); + userRepository.save(user); + } + + return oAuth2User; + } +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..99edc4e --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,18 @@ +# Database +spring.datasource.url=jdbc:postgresql://postgres:5432/vodSystem +spring.datasource.username=myuser +spring.datasource.password=mypassword +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:db/schema.sql +spring.sql.init.data-locations=classpath:db/data.sql + +# Security +spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET} +spring.security.oauth2.client.registration.google.scope=profile,email + +# Frontend +frontend.url=http://localhost:5173 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c798028..773728b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ spring.application.name=vodSystem +spring.profiles.active=local # VODs spring.servlet.multipart.max-file-size=2GB @@ -6,14 +7,5 @@ spring.servlet.multipart.max-request-size=2GB temp.vod.storage=videos/inputs/ temp.vod.output=videos/outputs/ -# Database -spring.datasource.url=jdbc:postgresql://localhost:5432/mydb -spring.datasource.username=myuser -spring.datasource.password=mypassword -spring.datasource.driver-class-name=org.postgresql.Driver -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect - # Logging logging.level.org.springframework.web=DEBUG \ No newline at end of file diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql new file mode 100644 index 0000000..ddb0c87 --- /dev/null +++ b/src/main/resources/db/data.sql @@ -0,0 +1,25 @@ +INSERT INTO users ( google_id, username, email, name) +VALUES + ('google-uid-001', 'alice', 'alice@example.com', 'Alice Example'), + ( 'google-uid-002', 'bob', 'bob@example.com', 'Bob Example'), + ('google-uid-003', 'carol', 'carol@example.com', 'Carol Example'), + ( 'google-uid-004', 'wizard42', 'gandalf@middle.earth', 'Gandalf the Grey'), + ( 'google-uid-005', 'catnap', 'whiskers@meowmail.com', 'Sir Whiskers McFluff'), + ( 'google-uid-006', 'robotron', 'bender@futurama.tv', 'Bender Rodriguez'), + ('google-uid-007', 'unicorn', 'sparkle@rainbow.com', 'Princess Sparklehoof'), + ( 'google-uid-008', 'pirate', 'blackbeard@seas.com', 'Edward Teach'), + ( 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'), + ( 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown'); + +INSERT INTO clips (id, user_id, title, description, width, height, fps, duration, file_size, video_path) +VALUES + (1, 4, 'Fireworks Over Hobbiton', 'A magical display of fireworks by Gandalf.', 1920, 1080, 30, 120, 104857600, '/videos/fireworks_hobbiton.mp4'), + (2, 5, 'Catnap Chronicles', 'Sir Whiskers McFluff naps in 12 different positions.', 1280, 720, 24, 60, 52428800, '/videos/catnap_chronicles.mp4'), + (3, 6, 'Bite My Shiny Metal...', 'Bender shows off his new upgrades.', 1920, 1080, 60, 45, 73400320, '/videos/bender_upgrades.mp4'), + (4, 7, 'Rainbow Dash', 'Princess Sparklehoof gallops across a double rainbow.', 1920, 1080, 30, 90, 67108864, '/videos/rainbow_dash.mp4'), + (5, 8, 'Pirate Karaoke Night', 'Blackbeard sings sea shanties with his crew.', 1280, 720, 25, 180, 157286400, '/videos/pirate_karaoke.mp4'), + (6, 9, 'The Case of the Missing Sandwich', 'Sherlock Holmes investigates a lunchtime mystery.', 1920, 1080, 30, 75, 50331648, '/videos/missing_sandwich.mp4'), + (7, 10, '88 Miles Per Hour', 'Doc Brown demonstrates time travel with style.', 1920, 1080, 60, 30, 41943040, '/videos/88mph.mp4'), + (8, 1, 'Alice in Videoland', 'Alice explores a surreal digital wonderland.', 1280, 720, 30, 150, 94371840, '/videos/alice_videoland.mp4'), + (9, 2, 'Bob''s Building Bonanza', 'Bob constructs a house out of cheese.', 1920, 1080, 24, 200, 209715200, '/videos/bob_cheesehouse.mp4'), + (10, 3, 'Carol''s Coding Catastrophe', 'Carol debugs a spaghetti codebase.', 1280, 720, 30, 100, 73400320, '/videos/carol_coding.mp4'); \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql new file mode 100644 index 0000000..bb261e5 --- /dev/null +++ b/src/main/resources/db/schema.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS clips; +DROP TABLE IF EXISTS users; + +CREATE TABLE IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + google_id VARCHAR(64), + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + role INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + +CREATE TABLE IF NOT EXISTS clips ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + width INTEGER NOT NULL, + height INTEGER NOT NULL, + fps INTEGER NOT NULL, + duration INTEGER NOT NULL, + file_size BIGINT NOT NULL, + video_path VARCHAR(255) NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); \ No newline at end of file