diff --git a/client/assets/NoProfilePicture.jpeg b/client/assets/NoProfilePicture.jpeg
new file mode 100644
index 0000000..d41be62
Binary files /dev/null and b/client/assets/NoProfilePicture.jpeg differ
diff --git a/client/src/components/UpdateAccountModule.tsx b/client/src/components/UpdateAccountModule.tsx
index b07ce77..2b7e60c 100644
--- a/client/src/components/UpdateAccountModule.tsx
+++ b/client/src/components/UpdateAccountModule.tsx
@@ -16,8 +16,8 @@ import {
} from "@nextui-org/react";
import { Form, Formik } from "formik";
import NextUIFormikInput from "./NextUIFormikInput";
-import { PencilSquareIcon } from "../icons";
import { useNavigate } from "react-router-dom";
+import UserProfilePicture from "./UserProfilePicture";
export default function UpdateAccountModule({
accessToken,
@@ -157,7 +157,7 @@ export default function UpdateAccountModule({
-
+
-
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;