From 286d9fe920a153db83d053cc2a44983bb3118d5f Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Fri, 28 Jun 2024 01:10:50 +0800 Subject: [PATCH] =?UTF-8?q?PROFILE=20PICTURES=20=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/assets/NoProfilePicture.jpeg | Bin 0 -> 6622 bytes client/src/components/UpdateAccountModule.tsx | 16 +- client/src/components/UserProfilePicture.tsx | 93 +++++ client/src/pages/SpringboardPage.tsx | 7 +- server/models/User.js | 2 +- server/package.json | 2 + server/pnpm-lock.yaml | 380 ++++++++++++++++++ server/routes/users.js | 77 +++- 8 files changed, 566 insertions(+), 11 deletions(-) create mode 100644 client/assets/NoProfilePicture.jpeg create mode 100644 client/src/components/UserProfilePicture.tsx diff --git a/client/assets/NoProfilePicture.jpeg b/client/assets/NoProfilePicture.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d41be62e662479d95d7aa741ffe6b64029a50199 GIT binary patch literal 6622 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
-
+
-
-
-
- -
-
+
+
diff --git a/client/src/components/UserProfilePicture.tsx b/client/src/components/UserProfilePicture.tsx new file mode 100644 index 0000000..490266f --- /dev/null +++ b/client/src/components/UserProfilePicture.tsx @@ -0,0 +1,93 @@ +import axios from "axios"; +import React, { useRef, useState } from "react"; +import config from "../config"; +import { Button, Image } from "@nextui-org/react"; + +export default function UserProfilePicture({ + userId, + token, + editable, +}: { + userId: string; + token: string; + editable: boolean; +}) { + const fileInputRef = useRef(null); + const [loading, setLoading] = useState(false); + + const uploadProfileImage = async ( + userId: string, + file: File, + token: string + ) => { + const formData = new FormData(); + formData.append("image", file); + + try { + const response = await axios.put( + `${config.serverAddress}/users/profile-image/${userId}`, + formData, + { + headers: { + "Content-Type": "multipart/form-data", + Authorization: `Bearer ${token}`, + }, + } + ); + return response.data; + } catch (error) { + throw error; + } + }; + + const handleButtonClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const handleFileChange = (event: React.ChangeEvent) => { + if (event.target.files) { + const file = event.target.files[0]; + uploadAndHandleSubmit(file); + } + }; + + const uploadAndHandleSubmit = async (file: File) => { + setLoading(true); + try { + await uploadProfileImage(userId, file, token); + } catch (error) { + } finally { + setLoading(false); + } + }; + + return ( +
+ + +
+ ); +} diff --git a/client/src/pages/SpringboardPage.tsx b/client/src/pages/SpringboardPage.tsx index b7f054b..2a8ed21 100644 --- a/client/src/pages/SpringboardPage.tsx +++ b/client/src/pages/SpringboardPage.tsx @@ -6,6 +6,7 @@ import { LockClosedIcon, PencilSquareIcon } from "../icons"; import SpringboardButton from "../components/SpringboardButton"; import { getTimeOfDay } from "../utilities"; import { retrieveUserInformation } from "../security/users"; +import UserProfilePicture from "../components/UserProfilePicture"; export default function SpringboardPage() { let { accessToken } = useParams(); // TODO: Replace AT from props with AT from localstorage @@ -65,7 +66,11 @@ export default function SpringboardPage() { Manage your account
-
+
{ allowNull: false, }, profilePicture: { - type: DataTypes.TEXT, + type: DataTypes.BLOB("long"), allowNull: true, }, isArchived: { diff --git a/server/package.json b/server/package.json index 5fecd47..208ea86 100644 --- a/server/package.json +++ b/server/package.json @@ -15,9 +15,11 @@ "dotenv": "^16.4.5", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", + "multer": "1.4.5-lts.1", "mysql2": "^3.10.1", "nodemon": "^3.1.3", "sequelize": "^6.37.3", + "sharp": "^0.33.4", "uuid": "^10.0.0", "yup": "^1.4.0" } diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index de3c181..a5679b9 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 + multer: + specifier: 1.4.5-lts.1 + version: 1.4.5-lts.1 mysql2: specifier: ^3.10.1 version: 3.10.1 @@ -29,6 +32,9 @@ dependencies: sequelize: specifier: ^6.37.3 version: 6.37.3(mysql2@3.10.1) + sharp: + specifier: ^0.33.4 + version: 0.33.4 uuid: specifier: ^10.0.0 version: 10.0.0 @@ -38,6 +44,202 @@ dependencies: packages: + /@emnapi/runtime@1.2.0: + resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} + requiresBuild: true + dependencies: + tslib: 2.6.3 + dev: false + optional: true + + /@img/sharp-darwin-arm64@0.33.4: + resolution: {integrity: sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.2 + dev: false + optional: true + + /@img/sharp-darwin-x64@0.33.4: + resolution: {integrity: sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.2 + dev: false + optional: true + + /@img/sharp-libvips-darwin-arm64@1.0.2: + resolution: {integrity: sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==} + engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-darwin-x64@1.0.2: + resolution: {integrity: sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==} + engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm64@1.0.2: + resolution: {integrity: sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==} + engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm@1.0.2: + resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==} + engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-s390x@1.0.2: + resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==} + engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-x64@1.0.2: + resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==} + engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.0.2: + resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==} + engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.0.2: + resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==} + engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-linux-arm64@0.33.4: + resolution: {integrity: sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.2 + dev: false + optional: true + + /@img/sharp-linux-arm@0.33.4: + resolution: {integrity: sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==} + engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.2 + dev: false + optional: true + + /@img/sharp-linux-s390x@0.33.4: + resolution: {integrity: sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==} + engines: {glibc: '>=2.31', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.2 + dev: false + optional: true + + /@img/sharp-linux-x64@0.33.4: + resolution: {integrity: sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==} + engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.2 + dev: false + optional: true + + /@img/sharp-linuxmusl-arm64@0.33.4: + resolution: {integrity: sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==} + engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 + dev: false + optional: true + + /@img/sharp-linuxmusl-x64@0.33.4: + resolution: {integrity: sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==} + engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.2 + dev: false + optional: true + + /@img/sharp-wasm32@0.33.4: + resolution: {integrity: sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.2.0 + dev: false + optional: true + + /@img/sharp-win32-ia32@0.33.4: + resolution: {integrity: sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-win32-x64@0.33.4: + resolution: {integrity: sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@phc/format@1.0.0: resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} engines: {node: '>=10'} @@ -79,6 +281,10 @@ packages: picomatch: 2.3.1 dev: false + /append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + dev: false + /argon2@0.40.3: resolution: {integrity: sha512-FrSmz4VeM91jwFvvjsQv9GYp6o/kARWoYKjbjDB2U5io1H3e5X67PYGclFDeQff6UXIhUd4aHR3mxCdBbMMuQw==} engines: {node: '>=16.17.0'} @@ -140,6 +346,17 @@ packages: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} dev: false + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: false + + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -171,10 +388,46 @@ packages: fsevents: 2.3.3 dev: false + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: false + /concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + dev: false + /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -196,6 +449,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + /cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -252,6 +509,11 @@ packages: engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dev: false + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + dev: false + /dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -481,6 +743,10 @@ packages: engines: {node: '>= 0.10'} dev: false + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -509,6 +775,10 @@ packages: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} dev: false + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: false + /jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -624,6 +894,17 @@ packages: brace-expansion: 1.1.11 dev: false + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: false + /moment-timezone@0.5.45: resolution: {integrity: sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==} dependencies: @@ -646,6 +927,19 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false + /multer@1.4.5-lts.1: + resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==} + engines: {node: '>= 6.0.0'} + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + dev: false + /mysql2@3.10.1: resolution: {integrity: sha512-6zo1T3GILsXMCex3YEu7hCz2OXLUarxFsxvFcUHWMpkPtmZLeTTWgRdc1gWyNJiYt6AxITmIf9bZDRy/jAfWew==} engines: {node: '>= 8.0'} @@ -738,6 +1032,10 @@ packages: engines: {node: '>=8.6'} dev: false + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: false + /property-expr@2.0.6: resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} dev: false @@ -776,6 +1074,18 @@ packages: unpipe: 1.0.0 dev: false + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -787,6 +1097,10 @@ packages: resolution: {integrity: sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==} dev: false + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false @@ -913,6 +1227,36 @@ packages: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false + /sharp@0.33.4: + resolution: {integrity: sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==} + engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.4 + '@img/sharp-darwin-x64': 0.33.4 + '@img/sharp-libvips-darwin-arm64': 1.0.2 + '@img/sharp-libvips-darwin-x64': 1.0.2 + '@img/sharp-libvips-linux-arm': 1.0.2 + '@img/sharp-libvips-linux-arm64': 1.0.2 + '@img/sharp-libvips-linux-s390x': 1.0.2 + '@img/sharp-libvips-linux-x64': 1.0.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 + '@img/sharp-libvips-linuxmusl-x64': 1.0.2 + '@img/sharp-linux-arm': 0.33.4 + '@img/sharp-linux-arm64': 0.33.4 + '@img/sharp-linux-s390x': 0.33.4 + '@img/sharp-linux-x64': 0.33.4 + '@img/sharp-linuxmusl-arm64': 0.33.4 + '@img/sharp-linuxmusl-x64': 0.33.4 + '@img/sharp-wasm32': 0.33.4 + '@img/sharp-win32-ia32': 0.33.4 + '@img/sharp-win32-x64': 0.33.4 + dev: false + /side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -923,6 +1267,12 @@ packages: object-inspect: 1.13.1 dev: false + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -940,6 +1290,17 @@ packages: engines: {node: '>= 0.8'} dev: false + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -976,6 +1337,12 @@ packages: hasBin: true dev: false + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + requiresBuild: true + dev: false + optional: true + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -989,6 +1356,10 @@ packages: mime-types: 2.1.35 dev: false + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: false + /undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: false @@ -1002,6 +1373,10 @@ packages: engines: {node: '>= 0.8'} dev: false + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -1033,6 +1408,11 @@ packages: '@types/node': 20.14.6 dev: false + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /yup@1.4.0: resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} dependencies: diff --git a/server/routes/users.js b/server/routes/users.js index 21a26ed..765dc31 100644 --- a/server/routes/users.js +++ b/server/routes/users.js @@ -5,8 +5,9 @@ const { User } = require("../models"); const { validateToken } = require("../middlewares/auth"); const argon2 = require("argon2"); const router = express.Router(); -const { v4: uuidV4 } = require("uuid"); const { sign } = require("jsonwebtoken"); +const multer = require("multer"); +const sharp = require("sharp"); require("dotenv").config(); @@ -222,4 +223,78 @@ router.put("/archive/:id", validateToken, async (req, res) => { } }); +router.get("/profile-image/:id", async (req, res) => { + let id = req.params.id; + let user = await User.findByPk(id); + + if (!user || !user.profilePicture) { + res.sendStatus(404); + return; + } + + try { + res.set("Content-Type", "image/jpeg"); // Adjust the content type as necessary + res.send(user.profilePicture); + } catch (err) { + res + .status(500) + .json({ message: "Error retrieving profile image", error: err }); + } +}); + +const upload = multer({ storage: multer.memoryStorage() }); + +router.put( + "/profile-image/:id", + validateToken, + upload.single("image"), + async (req, res) => { + const id = req.params.id; + + // Check if user exists + const user = await User.findByPk(id); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + // Check if file is uploaded + if (!req.file) { + return res.status(400).json({ message: "No file uploaded" }); + } + + try { + const { buffer, mimetype, size } = req.file; + + // Validate file type and size (example: max 5MB, only images) + const allowedTypes = ["image/jpeg", "image/png", "image/gif"]; + const maxSize = 5 * 1024 * 1024; // 5MB + + if (!allowedTypes.includes(mimetype)) { + return res.status(400).json({ message: "Invalid file type" }); + } + + if (size > maxSize) { + return res.status(400).json({ message: "File too large" }); + } + + // Crop the image to a square + const croppedBuffer = await sharp(buffer) + .resize({ width: 512, height: 512, fit: sharp.fit.cover }) // Adjust size as necessary + .toBuffer(); + + // Update user's profile picture + await User.update( + { profilePicture: croppedBuffer }, + { where: { id: id } } + ); + + res.json({ message: "Profile image uploaded and cropped successfully." }); + } catch (err) { + res + .status(500) + .json({ message: "Internal server error", errors: err.errors }); + } + } +); + module.exports = router;