diff --git a/backend/core/core.js b/backend/core/core.js index 4f3dfa2..6d704c1 100644 --- a/backend/core/core.js +++ b/backend/core/core.js @@ -101,7 +101,7 @@ async function getUser({ user_id, username, include_password = false }) { } // TODO: Rename patchUser async function editUser({ requester_id, user_id, user_content }) { - const valid_settings = ['display_name', 'password', 'role']; // Valid settings that can be changed + const valid_settings = ["display_name", "password", "role", "profile_image"]; // Valid settings that can be changed let user = await getUser({ user_id: user_id, include_password: true }); if (!user.success) return _r(false, "User not found"); @@ -109,22 +109,35 @@ async function editUser({ requester_id, user_id, user_content }) { // TODO: // If there was a role change, see if the acting user can make these changes - const setting_name = user_content.setting_name + const setting_name = user_content.setting_name; if (!valid_settings.includes(setting_name)) return _r(false, "Invalid setting."); - if (setting_name == 'password'){ + if (setting_name == "password") { // Check if current password value is correct const password_match = await bcrypt.compare(user_content.original_password, user.password); - if (!password_match) return _r(false, "Incorrect password") + if (!password_match) return _r(false, "Incorrect password"); // If successful, compute new password hash user_content.value = await bcrypt.hash(user_content.value, 10); } + if (setting_name == "profile_image") { + const folder_params = { Bucket: process.env.S3_BUCKET_NAME, Prefix: `${process.env.ENVIRONMENT}/user/${user.id}` }; + const listed_objects = await s3.send(new ListObjectsCommand(folder_params)); + + const all_media = listed_objects.Contents; + for (let i = 0; all_media.length > i; i++) { + if (all_media[i].Key.includes(user_content.value)) continue; + + // Delete other profile pictures + deleteMedia({ parent_id: user.id, parent_type: "user", file_name: all_media[i].Key.split("/")[3] }); + } + } + let formatted = {}; formatted[setting_name] = user_content.value; - await prisma.user.update({ where: { id: user.id }, data: formatted }) + await prisma.user.update({ where: { id: user.id }, data: formatted }); return _r(true); } async function deleteUser({ user_id }) { @@ -683,4 +696,4 @@ const _r = (s, m) => { return { success: s, message: m }; }; -module.exports = { settings, newUser, getUser, editUser, getPost, newPost, editPost, deletePost, getBiography, updateBiography, uploadMedia, getTags, postSetting, getSetting, installTheme, deleteTheme }; +module.exports = { settings, newUser, getUser, editUser, getPost, newPost, editPost, deletePost, getBiography, updateBiography, uploadMedia, getTags, postSetting, getSetting, installTheme, deleteTheme, getMedia }; diff --git a/backend/core/internal_api.js b/backend/core/internal_api.js index 21b12f3..272365e 100644 --- a/backend/core/internal_api.js +++ b/backend/core/internal_api.js @@ -46,7 +46,8 @@ async function postSetting(request, response) { async function postImage(request, response) { // TODO: Permissions for uploading images // TODO: Verification for image uploading - return response.json(await core.uploadMedia({ parent_id: request.body.post_id, parent_type: request.body.parent_type, file_buffer: request.body.buffer, content_type: request.body.content_type })); + // FIXME: Naming + return response.json(await core.uploadMedia({ parent_id: request.body.post_id || request.body.parent_id, parent_type: request.body.parent_type, file_buffer: request.body.buffer, content_type: request.body.content_type })); } async function deleteImage(req, res) { // TODO: Permissions for deleting image diff --git a/backend/page_scripts.js b/backend/page_scripts.js index b85d8bb..3963ac9 100644 --- a/backend/page_scripts.js +++ b/backend/page_scripts.js @@ -45,14 +45,16 @@ async function login(request, response) { response.render(_getThemePage("login"), await getDefaults(request)); } async function author(req, res) { - const user = await core.getUser({ user_id: req.params.author_id }); + let user = await core.getUser({ user_id: req.params.author_id }); // FIXME: Bandage fix for author get error if (!user.success) return res.redirect("/"); - const profile = await core.getBiography({ author_id: user.data.id }); + user = user.data; + const profile = await core.getBiography({ author_id: user.id }); // TODO: Check for success - const posts = await core.getPost({ requester_id: user.data.id }); + const posts = await core.getPost({ requester_id: user.id }); + const profile_image = await core.getMedia({ parent_id: user.id, parent_type: "user", file_name: user.profile_image }); - res.render(_getThemePage("author"), { ...(await getDefaults(req)), post: { ...profile.data, post_count: posts.data.length } }); + res.render(_getThemePage("author"), { ...(await getDefaults(req)), post: { ...profile.data, post_count: posts.data.length, profile_image: profile_image } }); } async function authorEdit(request, response) { let author = await core.getBiography({ author_id: request.params.author_id }); diff --git a/eslint.config.mjs b/eslint.config.mjs index 57e8920..f2fb3c3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,4 +1,18 @@ import globals from "globals"; import pluginJs from "@eslint/js"; -export default [{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, { languageOptions: { globals: globals.browser } }, { rules: { "no-unused-vars": "error", "no-undef": "error", "indent": ["error", "tab", { tabWidth: 4 }], "semi-style": ["error", "last"], } }, pluginJs.configs.recommended]; +export default [ + { + files: ["**/*.js"], + languageOptions: { sourceType: "commonjs" }, + }, + { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + { + rules: { + "no-unused-vars": "error", + "semi-style": ["error", "last"], + indent: ["error", "tab"], + }, + }, +]; diff --git a/frontend/views/themes/default/css/author.css b/frontend/views/themes/default/css/author.css index dafda4b..b1a1a44 100644 --- a/frontend/views/themes/default/css/author.css +++ b/frontend/views/themes/default/css/author.css @@ -42,8 +42,8 @@ } .page .page-center .about .profile-picture img { margin: auto; - max-height: 200px; - max-width: 200px; + height: 200px; + width: 200px; } .page .page-center .about .displayname { font-size: 1.4rem; diff --git a/frontend/views/themes/default/css/author.scss b/frontend/views/themes/default/css/author.scss index a66b16f..e6dec3b 100644 --- a/frontend/views/themes/default/css/author.scss +++ b/frontend/views/themes/default/css/author.scss @@ -43,8 +43,8 @@ img { margin: auto; - max-height: 200px; - max-width: 200px; + height: 200px; + width: 200px; } } diff --git a/frontend/views/themes/default/ejs/author.ejs b/frontend/views/themes/default/ejs/author.ejs index 2c724b9..e383021 100644 --- a/frontend/views/themes/default/ejs/author.ejs +++ b/frontend/views/themes/default/ejs/author.ejs @@ -25,7 +25,7 @@ <%- post.content %>
-
+
" />
<%= post.owner.display_name || post.owner.username %>
Registered <%= post.created_date.toLocaleString('en-US', { dateStyle:'medium' }) || "Null" %>
diff --git a/frontend/views/themes/default/js/editAuthor.js b/frontend/views/themes/default/js/editAuthor.js index f80fec8..8336fa6 100644 --- a/frontend/views/themes/default/js/editAuthor.js +++ b/frontend/views/themes/default/js/editAuthor.js @@ -1,7 +1,7 @@ -async function changeValue(setting_name, element) { +async function changeValue(setting_name, value) { const form = { setting_name: setting_name, - value: element.value, + value: value, id: window.location.href.split("/")[4], }; const response = await request(`/api/web/user`, "PATCH", form); @@ -31,7 +31,7 @@ function changePasswordInputUpdate() { async function sendPasswordUpdate() { const new_password_1 = qs("#cp-new-1"); - const original_password_value = qs("#cp-current").value + const original_password_value = qs("#cp-current").value; const form = { setting_name: "password", @@ -44,4 +44,39 @@ async function sendPasswordUpdate() { if (response.body.success) { alert("Successfully changed password"); } -} \ No newline at end of file +} + +const fileInput = qs("#profile_picture"); +fileInput.addEventListener("change", uploadProfileImage); + +async function uploadProfileImage(event) { + const file = event.target.files[0]; + const image_object = { + data_blob: new Blob([await file.arrayBuffer()]), + content_type: file.type, + }; + + let form_data = { + buffer: await _readFile(image_object.data_blob), + content_type: image_object.content_type, + parent_id: window.location.href.split("/")[4], + parent_type: "user", + }; + + const image_uploading_request = await request("/api/web/image", "POST", form_data); + + if (image_uploading_request.status == 200) { + // Update profile picture link + changeValue("profile_image", image_uploading_request.body); + // alert(image_uploading_request.body); + } +} + +function _readFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 24db92b..233dede 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,6 +15,7 @@ model User { username String @unique password String display_name String? + profile_image String? role Role @default(USER)