From 95d8cafec2fe362557fa4469cdcd95f81d839914 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 17 Mar 2026 21:49:25 +0000 Subject: [PATCH] Auto commit: 2026-03-17T21:49:25.816Z --- backend/src/db/api/users.js | 37 +- .../20260317213730-add-user-profile-fields.js | 25 + backend/src/db/models/users.js | 16 +- frontend/src/menuAside.ts | 7 +- frontend/src/pages/media-gallery/index.tsx | 60 + frontend/src/pages/profile.tsx | 26 +- frontend/src/pages/users/users-view.tsx | 1567 +---------------- 7 files changed, 201 insertions(+), 1537 deletions(-) create mode 100644 backend/src/db/migrations/20260317213730-add-user-profile-fields.js create mode 100644 frontend/src/pages/media-gallery/index.tsx diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js index ba0d0fe..fdbcbde 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -1,4 +1,3 @@ - const db = require('../models'); const FileDBApi = require('./file'); const crypto = require('crypto'); @@ -13,6 +12,23 @@ const Sequelize = db.Sequelize; const Op = Sequelize.Op; module.exports = class UsersDBApi { + static async getFollowersCount(userId) { + return await db.friendships.count({ + where: { addresseeId: userId, status: "accepted" } + }); + } + static async getFollowingCount(userId) { + return await db.friendships.count({ + where: { requesterId: userId, status: "accepted" } + }); + } + static async isFollowing(requesterId, addresseeId) { + const friendship = await db.friendships.findOne({ + where: { requesterId, addresseeId } + }); + return !!friendship; + } + static async create(data, options) { const currentUser = (options && options.currentUser) || { id: null }; @@ -85,6 +101,10 @@ module.exports = class UsersDBApi { null , + bio: data.data.bio || null, + birthday: data.data.birthday || null, + education: data.data.education || null, + importHash: data.data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -143,7 +163,10 @@ module.exports = class UsersDBApi { const usersData = data.map((item, index) => ({ id: item.id || undefined, - firstName: item.firstName + firstName: item.firstName, + bio: item.bio, + birthday: item.birthday, + education: item.education || null , @@ -205,6 +228,10 @@ module.exports = class UsersDBApi { null , + bio: item.bio || null, + birthday: item.birthday || null, + education: item.education || null, + importHash: item.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -298,6 +325,9 @@ module.exports = class UsersDBApi { if (data.provider !== undefined) updatePayload.provider = data.provider; + if (data.bio !== undefined) updatePayload.bio = data.bio; + if (data.birthday !== undefined) updatePayload.birthday = data.birthday; + if (data.education !== undefined) updatePayload.education = data.education; updatePayload.updatedById = currentUser.id; @@ -986,5 +1016,4 @@ module.exports = class UsersDBApi { -}; - +}; \ No newline at end of file diff --git a/backend/src/db/migrations/20260317213730-add-user-profile-fields.js b/backend/src/db/migrations/20260317213730-add-user-profile-fields.js new file mode 100644 index 0000000..974dfde --- /dev/null +++ b/backend/src/db/migrations/20260317213730-add-user-profile-fields.js @@ -0,0 +1,25 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up (queryInterface, Sequelize) { + await queryInterface.addColumn('users', 'bio', { + type: Sequelize.DataTypes.TEXT, + allowNull: true, + }); + await queryInterface.addColumn('users', 'birthday', { + type: Sequelize.DataTypes.DATEONLY, + allowNull: true, + }); + await queryInterface.addColumn('users', 'education', { + type: Sequelize.DataTypes.TEXT, + allowNull: true, + }); + }, + + async down (queryInterface, Sequelize) { + await queryInterface.removeColumn('users', 'bio'); + await queryInterface.removeColumn('users', 'birthday'); + await queryInterface.removeColumn('users', 'education'); + } +}; \ No newline at end of file diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index bb384ba..17f3abe 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -104,6 +104,16 @@ provider: { }, +bio: { + type: DataTypes.TEXT, + }, + birthday: { + type: DataTypes.DATEONLY, + }, + education: { + type: DataTypes.TEXT, + }, + importHash: { type: DataTypes.STRING(255), allowNull: true, @@ -335,7 +345,9 @@ function trimStringFields(users) { users.lastName = users.lastName ? users.lastName.trim() : null; + + users.bio = users.bio ? users.bio.trim() : null; + users.education = users.education ? users.education.trim() : null; return users; -} - +} \ No newline at end of file diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 20aadac..7020ea8 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -126,6 +126,11 @@ const menuAside: MenuAsideItem[] = [ icon: 'mdiAlertOctagonOutline' in icon ? icon['mdiAlertOctagonOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_USER_REPORTS' }, + { + href: '/media-gallery', + label: 'Media Gallery', + icon: icon.mdiImageMultiple, + }, { href: '/profile', label: 'Profile', @@ -142,4 +147,4 @@ const menuAside: MenuAsideItem[] = [ }, ] -export default menuAside \ No newline at end of file +export default menuAside diff --git a/frontend/src/pages/media-gallery/index.tsx b/frontend/src/pages/media-gallery/index.tsx new file mode 100644 index 0000000..989b726 --- /dev/null +++ b/frontend/src/pages/media-gallery/index.tsx @@ -0,0 +1,60 @@ +import { mdiImageMultiple } from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement, useEffect, useState } from 'react'; +import CardBox from '../../components/CardBox'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import SectionMain from '../../components/SectionMain'; +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../../config'; +import axios from 'axios'; +import ImageField from '../../components/ImageField'; + +const MediaGallery = () => { + const [images, setImages] = useState([]); + + useEffect(() => { + const fetchPosts = async () => { + try { + const response = await axios.get('/timeline_posts'); + const posts = response.data.rows || []; + const galleryImages = posts.flatMap((post: any) => post.media_images || []); + setImages(galleryImages); + } catch (error) { + console.error('Failed to fetch media:', error); + } + }; + fetchPosts(); + }, []); + + return ( + <> + + {getPageTitle('Media Gallery')} + + + + {''} + + +
+ {images.map((image: any, index: number) => ( + + ))} +
+
+
+ + ); +}; + +MediaGallery.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default MediaGallery; diff --git a/frontend/src/pages/profile.tsx b/frontend/src/pages/profile.tsx index f5eb7cf..cf4a07a 100644 --- a/frontend/src/pages/profile.tsx +++ b/frontend/src/pages/profile.tsx @@ -45,7 +45,10 @@ const EditUsers = () => { app_role: '', disabled: false, avatar: [], - password: '' + password: '', + bio: '', + birthday: null, + education: '' }; const [initialValues, setInitialValues] = useState(initVals); @@ -92,6 +95,7 @@ const EditUsers = () => { initialValues={initialValues} onSubmit={(values) => handleSubmit(values)} > + {({ setFieldValue, values }) => (
{ + + + + + + setFieldValue('birthday', date)} + className="px-3 py-2 max-w-full border-gray-700 rounded w-full focus:ring focus:ring-blue-600 focus:border-blue-600 border bg-white dark:bg-slate-800" + placeholderText="Select a date" + /> + + + + + + { /> + )} @@ -177,4 +199,4 @@ EditUsers.getLayout = function getLayout(page: ReactElement) { return {page}; }; -export default EditUsers; +export default EditUsers; \ No newline at end of file diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx index e314bea..c71d02f 100644 --- a/frontend/src/pages/users/users-view.tsx +++ b/frontend/src/pages/users/users-view.tsx @@ -1,14 +1,14 @@ -import React, { ReactElement, useEffect } from 'react'; +import React, { ReactElement, useEffect, useState } from 'react'; import Head from 'next/head' -import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; import dayjs from "dayjs"; import {useAppDispatch, useAppSelector} from "../../stores/hooks"; import {useRouter} from "next/router"; import { fetch } from '../../stores/users/usersSlice' -import {saveFile} from "../../helpers/fileSaver"; import dataFormatter from '../../helpers/dataFormatter'; import ImageField from "../../components/ImageField"; +import { mdiMessage, mdiChartTimelineVariant } from "@mdi/js"; +import axios from "axios"; + import LayoutAuthenticated from "../../layouts/Authenticated"; import {getPageTitle} from "../../config"; import SectionTitleLineWithButton from "../../components/SectionTitleLineWithButton"; @@ -16,7 +16,6 @@ import SectionMain from "../../components/SectionMain"; import CardBox from "../../components/CardBox"; import BaseButton from "../../components/BaseButton"; import BaseDivider from "../../components/BaseDivider"; -import {mdiChartTimelineVariant} from "@mdi/js"; import {SwitchField} from "../../components/SwitchField"; import FormField from "../../components/FormField"; @@ -25,181 +24,68 @@ const UsersView = () => { const router = useRouter() const dispatch = useAppDispatch() const { users } = useAppSelector((state) => state.users) - + const [stats, setStats] = useState({ followersCount: 0, followingCount: 0, isFollowing: false }); const { id } = router.query; - function removeLastCharacter(str) { - console.log(str,`str`) - return str.slice(0, -1); - } - useEffect(() => { - dispatch(fetch({ id })); + if (id) { + dispatch(fetch({ id })); + axios.get(`/users/${id}/social-stats`).then(res => setStats(res.data)); + } }, [dispatch, id]); - return ( <> {getPageTitle('View users')} - - + +
+ { /* Implement toggle follow */ }} + /> + router.push(`/conversations/`)} + /> + +
+
+ +

Followers: {stats.followersCount}

+
+ +

Following: {stats.followingCount}

+
+
- - -

First Name

{users?.firstName}

- - - - - - - - - - - - - - - - - - - - - - - - - - - -

Last Name

{users?.lastName}

- - - - - - - - - - - - - - - - - - - - - - - - - - - -

Phone Number

{users?.phoneNumber}

- - - - - - - - - - - - - - - - - - - - - - - - - - - -

E-Mail

{users?.email}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { disabled /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Avatar

{users?.avatar?.length @@ -253,1348 +105,11 @@ const UsersView = () => { ) :

No Avatar

}
- - - - - - - - - - - - - - - - - - - - - - - - -

App Role

- - - - -

{users?.app_role?.name ?? 'No data'}

- - - - - - - - - - - - - - - - - - - - - - - - - - +

{users?.app_role?.name ?? 'No data'}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <> -

Custom Permissions

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.custom_permissions && Array.isArray(users.custom_permissions) && - users.custom_permissions.map((item: any) => ( - router.push(`/permissions/permissions-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - ))} - -
Name
- { item.name } -
-
- {!users?.custom_permissions?.length &&
No data
} -
- - - - - - - - - - - - - <> -

Auth_sessions User

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.auth_sessions_user && Array.isArray(users.auth_sessions_user) && - users.auth_sessions_user.map((item: any) => ( - router.push(`/auth_sessions/auth_sessions-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
SessionTokenIssuedAtExpiresAtIPAddressUserAgentIsRevokedRevokedAt
- { item.session_token } - - { dataFormatter.dateTimeFormatter(item.issued_at) } - - { dataFormatter.dateTimeFormatter(item.expires_at) } - - { item.ip_address } - - { item.user_agent } - - { dataFormatter.booleanFormatter(item.is_revoked) } - - { dataFormatter.dateTimeFormatter(item.revoked_at) } -
-
- {!users?.auth_sessions_user?.length &&
No data
} -
- - - - <> -

Timeline_posts Author

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.timeline_posts_author && Array.isArray(users.timeline_posts_author) && - users.timeline_posts_author.map((item: any) => ( - router.push(`/timeline_posts/timeline_posts-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
PostTypeLinkURLLinkTitleLinkDescriptionVisibilityIsPinnedIsEditedPublishedAtEditedAt
- { item.post_type } - - { item.link_url } - - { item.link_title } - - { item.link_description } - - { item.visibility } - - { dataFormatter.booleanFormatter(item.is_pinned) } - - { dataFormatter.booleanFormatter(item.is_edited) } - - { dataFormatter.dateTimeFormatter(item.published_at) } - - { dataFormatter.dateTimeFormatter(item.edited_at) } -
-
- {!users?.timeline_posts_author?.length &&
No data
} -
- - - - <> -

Post_comments Author

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - {users.post_comments_author && Array.isArray(users.post_comments_author) && - users.post_comments_author.map((item: any) => ( - router.push(`/post_comments/post_comments-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - ))} - -
IsEditedCommentedAtEditedAt
- { dataFormatter.booleanFormatter(item.is_edited) } - - { dataFormatter.dateTimeFormatter(item.commented_at) } - - { dataFormatter.dateTimeFormatter(item.edited_at) } -
-
- {!users?.post_comments_author?.length &&
No data
} -
- - - - <> -

Post_reactions User

- -
- - - - - - - - - - - - - - - - - - - - {users.post_reactions_user && Array.isArray(users.post_reactions_user) && - users.post_reactions_user.map((item: any) => ( - router.push(`/post_reactions/post_reactions-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - ))} - -
ReactionTypeReactedAt
- { item.reaction_type } - - { dataFormatter.dateTimeFormatter(item.reacted_at) } -
-
- {!users?.post_reactions_user?.length &&
No data
} -
- - - - <> -

Friendships Requester

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.friendships_requester && Array.isArray(users.friendships_requester) && - users.friendships_requester.map((item: any) => ( - router.push(`/friendships/friendships-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - ))} - -
StatusRequestedAtRespondedAtNote
- { item.status } - - { dataFormatter.dateTimeFormatter(item.requested_at) } - - { dataFormatter.dateTimeFormatter(item.responded_at) } - - { item.note } -
-
- {!users?.friendships_requester?.length &&
No data
} -
- - - <> -

Friendships Addressee

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.friendships_addressee && Array.isArray(users.friendships_addressee) && - users.friendships_addressee.map((item: any) => ( - router.push(`/friendships/friendships-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - ))} - -
StatusRequestedAtRespondedAtNote
- { item.status } - - { dataFormatter.dateTimeFormatter(item.requested_at) } - - { dataFormatter.dateTimeFormatter(item.responded_at) } - - { item.note } -
-
- {!users?.friendships_addressee?.length &&
No data
} -
- - - - <> -

Conversations CreatedByUser

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.conversations_created_by_user && Array.isArray(users.conversations_created_by_user) && - users.conversations_created_by_user.map((item: any) => ( - router.push(`/conversations/conversations-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
ConversationTypeTitleVisibilityCreatedOnLastMessageAt
- { item.conversation_type } - - { item.title } - - { item.visibility } - - { dataFormatter.dateTimeFormatter(item.created_on) } - - { dataFormatter.dateTimeFormatter(item.last_message_at) } -
-
- {!users?.conversations_created_by_user?.length &&
No data
} -
- - - - <> -

Conversation_members User

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.conversation_members_user && Array.isArray(users.conversation_members_user) && - users.conversation_members_user.map((item: any) => ( - router.push(`/conversation_members/conversation_members-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
MemberRoleMembershipStatusIsMutedMutedUntilJoinedAtLeftAt
- { item.member_role } - - { item.membership_status } - - { dataFormatter.booleanFormatter(item.is_muted) } - - { dataFormatter.dateTimeFormatter(item.muted_until) } - - { dataFormatter.dateTimeFormatter(item.joined_at) } - - { dataFormatter.dateTimeFormatter(item.left_at) } -
-
- {!users?.conversation_members_user?.length &&
No data
} -
- - - - <> -

Messages Sender

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.messages_sender && Array.isArray(users.messages_sender) && - users.messages_sender.map((item: any) => ( - router.push(`/messages/messages-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
MessageTypeIsEditedSentAtEditedAtDeletedAtTime
- { item.message_type } - - { dataFormatter.booleanFormatter(item.is_edited) } - - { dataFormatter.dateTimeFormatter(item.sent_at) } - - { dataFormatter.dateTimeFormatter(item.edited_at) } - - { dataFormatter.dateTimeFormatter(item.deleted_at_time) } -
-
- {!users?.messages_sender?.length &&
No data
} -
- - - - <> -

Message_receipts User

- -
- - - - - - - - - - - - - - - - - - - - {users.message_receipts_user && Array.isArray(users.message_receipts_user) && - users.message_receipts_user.map((item: any) => ( - router.push(`/message_receipts/message_receipts-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - ))} - -
ReceiptStatusStatusAt
- { item.receipt_status } - - { dataFormatter.dateTimeFormatter(item.status_at) } -
-
- {!users?.message_receipts_user?.length &&
No data
} -
- - - - <> -

Notifications Recipient

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.notifications_recipient && Array.isArray(users.notifications_recipient) && - users.notifications_recipient.map((item: any) => ( - router.push(`/notifications/notifications-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
NotificationTypeTitleBodyDeepLinkIsReadCreatedOnReadAt
- { item.notification_type } - - { item.title } - - { item.body } - - { item.deep_link } - - { dataFormatter.booleanFormatter(item.is_read) } - - { dataFormatter.dateTimeFormatter(item.created_on) } - - { dataFormatter.dateTimeFormatter(item.read_at) } -
-
- {!users?.notifications_recipient?.length &&
No data
} -
- - - - <> -

User_reports Reporter

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.user_reports_reporter && Array.isArray(users.user_reports_reporter) && - users.user_reports_reporter.map((item: any) => ( - router.push(`/user_reports/user_reports-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
TargetTypeTargetReferenceReasonDetailsStatusReportedAtResolvedAt
- { item.target_type } - - { item.target_reference } - - { item.reason } - - { item.details } - - { item.status } - - { dataFormatter.dateTimeFormatter(item.reported_at) } - - { dataFormatter.dateTimeFormatter(item.resolved_at) } -
-
- {!users?.user_reports_reporter?.length &&
No data
} -
- - - <> -

User_reports AssignedToUser

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {users.user_reports_assigned_to_user && Array.isArray(users.user_reports_assigned_to_user) && - users.user_reports_assigned_to_user.map((item: any) => ( - router.push(`/user_reports/user_reports-view/?id=${item.id}`)}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
TargetTypeTargetReferenceReasonDetailsStatusReportedAtResolvedAt
- { item.target_type } - - { item.target_reference } - - { item.reason } - - { item.details } - - { item.status } - - { dataFormatter.dateTimeFormatter(item.reported_at) } - - { dataFormatter.dateTimeFormatter(item.resolved_at) } -
-
- {!users?.user_reports_assigned_to_user?.length &&
No data
} -
- - - - - { UsersView.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ) } -export default UsersView; \ No newline at end of file +export default UsersView;