Skip to main content

Profile Function & Social

Updated: 2025-10-22

Objectives

  • Create Profile page into a functional social surface for a small, admin‑only org (≈20 users).
  • Support creating posts with rich text and media, YouTube auto‑embeds, likes, comments, share links, and user discovery.
  • Provide org/DSW and Reggie presence, plus a sidebar list of other profiles.
  • Deliver real‑time notifications (bell, badges, mini‑log) for mentions, comments, and likes via the existing logs/notifications/SSE system.

High‑level Architecture

  • Frontend (admin/src/html/profile.html + admin/src/js/pages/profile.js)

    • Tabs: POSTS, PHOTOS, VIDEOS, PROFILES, and a right‑aligned CREATE control.
    • Composer: Summernote editor, Blueimp multi‑file upload; posts created via API.
    • Feed widgets: infinite scroll on POSTS, cover modal open by deep‑link #post=, periodic refresh.
    • Social actions: Like/Unlike, collapsible comments with counts, share copy, hover liker list.
    • User discovery: Left sidebar “Other Profiles” list (DSW, Reggie, others), PROFILES grid view.
  • Backend (backend/routes/admin-profiles.js, profile-photo.js, users.js)

    • Dedicated adminprofiles schema for posts and media linking.
    • Uploads stored under storage_user//posts/YYYY/MM/{uuid}.{ext}.
    • Media streaming via API to avoid SPA fallback/static routing issues.
    • Mentions/comments/likes emit logs routed to bell and badges as per spec.

Data Model (Postgres)

Schema: adminprofiles

  • posts

    • id UUID PK
    • owner_user_id UUID (FK users.id)
    • title text, body text
    • is_pinned bool default false
    • is_deleted bool default false
    • created_at, updated_at timestamptz (trigger updates on change)
    • Indexes: owner_user_id, created_at DESC, is_deleted; optional GIN full‑text on title+body
  • media_files

    • id UUID PK
    • owner_user_id UUID (FK users.id)
    • rel_path text (storage_user relative), mime_type text, bytes int, sha256 text
    • created_at timestamptz, unique(rel_path)
  • post_media

    • post_id UUID, media_id UUID, position int, caption text NULL
    • PK (post_id, position); FK to posts/media_files; cascade on delete
  • post_comments

    • id UUID PK
    • post_id UUID (FK posts)
    • owner_user_id UUID (FK users)
    • body text, is_deleted bool default false
    • created_at, updated_at timestamptz (trigger updates on change)
    • Indexes: post_id, owner_user_id
  • post_likes

    • post_id UUID, user_id UUID, created_at timestamptz default now()
    • PK (post_id, user_id); Indexes on (post_id, created_at DESC) and (user_id, created_at DESC)

Storage layout:

  • storage_user//posts/YYYY/MM/.
    • computed from user first+last fallback to email local part or id (normalized, safe)

Backend API Surface

Base path: /api/v1/admin-profiles

Posts

  • POST /posts

    • Body: { title?, body?, media_ids?: UUID[], owner_user_id? }
    • Owner override allowed for roles: exec, senior management
    • Emits mention notifications for @handles in body
  • GET /posts?user_id=&page=&page_size=

    • Returns each post with:
      • media: [{ id, rel_path, mime_type, bytes }] (ordered by position)
      • comment_count, like_count
      • viewer_has_liked (for current user)
      • likers: up to 8 recent users [{ id, first_name, last_name }]
  • GET /posts/:id

    • Same decorations as list
  • PATCH /posts/:id

    • Owner‑only: { title?, body?, is_pinned?, is_deleted? }
  • DELETE /posts/:id

    • Owner‑only hard delete (cascades via FKs if configured)

Comments

  • POST /posts/:id/comments

    • Body: { body }
    • Notifies post owner (if not self) and any mentioned users
  • GET /posts/:id/comments

    • Lists visible comments ordered by created_at
  • PATCH /posts/:postId/comments/:commentId

    • Owner of comment OR post owner may edit/delete: { body?, is_deleted? }

Likes

  • POST /posts/:id/like — idempotent like; notifies post owner (if not self)
  • DELETE /posts/:id/like — idempotent unlike

User Media

  • POST /user-media (multipart form‑data; field: file)
    • Stores to storage_user, registers media_files row; returns { id, rel_path, mime_type, bytes }
  • GET /user-media/:id — streams file with correct Content‑Type
  • GET /user-media/:id/thumb — generates 480px JPEG thumbnail for images; 204 for non‑images
  • DELETE /user-media/:id — owner‑only remove (attempts to unlink file and row)

Profile Photos (separate route)

  • GET /api/v1/profile-photo/serve/:kind?user_id=UUID&token=...
    • kind: resolved|ai|original
    • Resolution preference: AI → generated.png → gender generic → robot generic

Users (for discovery)

  • GET /api/users (existing) used for left sidebar and profiles grid

Mentions and Handle Rules

  • A mention is @handle, where handle is:
    • Lowercase alphanumeric concatenation of first_name + last_name (e.g., Brett Watson → @brettwatson), or
    • The email local part (before @) for backward compatibility.
  • On post/comment create, backend parses mentions and resolves user ids; for each resolved user, a notification is emitted.

Notifications, Badges, and SSE

All notifications use emitLog() and follow the System Logs & Notifications spec.

  • Context kinds used:

    • profile_mention — “You were mentioned in a post/comment”
    • profile_comment — “New comment on your post”
    • profile_like — “Your post was liked”
  • Types: ["profile", "profile_mention" | "profile_comment" | "profile_like"]

  • Platform: "Backend"

  • Targets: ["user:"]

  • Deliver: "normal" (matrix applies; tile routing to profile enables badges)

  • Tile routing: via Settings rule for tile "profile" by context.kind in [profile_comment, profile_mention, profile_share, profile_like]

  • SSE events emitted accordingly: event: log (mini‑log), event: badge (counters)

Frontend Behavior

  • Composer: hidden until CREATE is activated (only on own profile). Uses Summernote for HTML and Blueimp for uploads.
  • Posts List: paginated; infinite scroll; each card includes:
    • Rich body with YouTube URL auto‑embed
    • Media gallery (thumbs) via /user-media
    • Actions: Like (toggles, count, hover liker list), Comments (collapsible, count badge), Share (copy deep link)
  • Comments: inline add/edit/delete; comment count badge increments on add.
  • Share: copies profile.html?user=<owner_id>#post=<post_id> to clipboard.
  • Profiles Sidebar: shows DSW (org/exec fallback), Reggie, then others alphabetically, each with VIEW button linking by id.
  • PROFILES Grid: cards for users; each shows profile pic, name, and post count.
  • THE FEED: right column card header renamed; list shows recent posts; click opens cover modal; refreshes periodically.

Security & Access

  • Admin‑only surface; backend uses authenticate middleware; routes assume internal admin usage.
  • Owner checks:
    • Posts: only owner can PATCH/DELETE (exec/senior mgmt may override owner_user_id when creating DSW posts).
    • Comments: comment owner OR post owner may edit/delete.
  • Media delete: owner‑only; attempts to remove from disk; resilient if missing.

Error Handling & Resilience

  • Upload size limits (50MB per file) and type checks for thumbs.
  • All streams set correct Content‑Type.
  • Frontend guards for missing panes; safe defaults if API pages are empty.

Known Enhancements (Backlog)

  • Real‑time UI updates for likes/comments via SSE subscription on profile page (currently relies on refresh/poll).
  • Richer @mention autocomplete, user hovercards, and deep link to comment anchors.
  • Full‑text search across posts/comments (GIN TSVECTOR index present in earlier drafts).
  • Media types: animated thumbnails for video; PDF/image viewer modal.
  • Moderation tools for exec/senior management (pin/hide posts).

Plugin WIdget and Code Examples

  • card widget
<link href="assets/plugins/lity/dist/lity.min.css" rel="stylesheet">
<script src="assets/plugins/lity/dist/lity.min.js"></script>

<div class="card p-1">
<div style="max-height: 250px" class="overflow-hidden z-1">
<img src="assets/img/gallery/widget-cover-1.jpg" alt="" class="card-img" />
</div>
<div class="card-img-overlay d-flex flex-column bg-body bg-opacity-50 m-1 z-2">
<div class="flex-fill">
<div class="d-flex align-items-center">
<h6>YOUTUBE</h6>
<div class="dropdown dropdown-icon ms-auto">
<a href="#" class="text-white" data-bs-toggle="dropdown"><i class="fa fs-14px fa-ellipsis-h"></i></a>
<div class="dropdown-menu dropdown-menu-end" data-bs-theme="light">
<a href="//www.youtube.com/watch?v=_AS5nu4u1ss" class="dropdown-item">View</a>
</div>
</div>
</div>
</div>
<div>
<a href="//www.youtube.com/watch?v=_AS5nu4u1ss" data-lity class="text-white text-decoration-none d-flex align-items-center">
<div class="bg-white bg-opacity-35 text-white w-35px h-35px d-flex align-items-center justify-content-center">
<i class="fa fa-play fa-lg"></i>
</div>
<div class="ms-3 flex-1">
<div class="fw-bold">NEW VIDEOS - BEHIND THE WARS</div>
<div class="text-body text-opacity-75">
<i class="far fa-eye"></i> 892 views
<i class="far fa-clock ms-3"></i> 39min ago
</div>
</div>
</a>
</div>
</div>
</div>
  • User List widget (for hover over post likes etc)
<div class="widget-user-list">
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-1.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-2.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-3.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-4.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-5.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-6.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link"><img src="assets/img/user/user-7.jpg" alt="" /></a></div>
<div class="widget-user-list-item"><a href="#" class="widget-user-list-link bg-body text-body fw-bold">+26</a></div>
</div>

- Profile Widget (for the last tab in the top navigation of profiles)
```<div class="card">
<div class="m-1">
<div class="position-relative overflow-hidden" style="height: 150px">
<img src="assets/img/gallery/widget-cover-2.jpg" class="card-img" alt="" />
<div class="card-img-overlay text-white text-center bg-body bg-opacity-75">
<div class="my-2">
<img src="assets/img/user/user.jpg" alt="" width="80" class="rounded-circle" />
</div>
<div>
<div class="fw-bold">MAURICE PATTERSON</div>
<div class="text-body text-opacity-75">Never give up</div>
</div>
</div>
</div>
<div class="card-body py-2 mt-1">
<div class="row text-center">
<div class="col-4">
<div class="fw-bold fs-5">415</div>
<div class="text-body text-opacity-50 fw-semibold small">POSTS</div>
</div>
<div class="col-4">
<div class="fw-bold fs-5">140k</div>
<div class="text-body text-opacity-50 fw-semibold small">FOLLOWERS</div>
</div>
<div class="col-4">
<div class="fw-bold fs-5">697</div>
<div class="text-body text-opacity-50 fw-semibold small">FOLLOWING</div>
</div>
</div>
</div>
</div>
</div>```

- Modal with options
```<div class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">MODAL TITLE</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-theme">Save changes</button>
</div>
</div>
</div>
</div>
  • Modal Cover (amazing view for full screen in your face odels like whenyou need to submit so many forms but oransing tham i dont have tine fr)

Modal Cover Modal cover is an extended feature from Bootstrap modal. It provide the same background color as the modal content. Place a .modal-cover css class on .modal for full cover modal.

<button type="button" class="btn btn-outline-theme me-2" data-bs-toggle="modal" data-bs-target="#modalCoverExample">Modal Cover</button>

<div class="modal modal-cover fade" id="modalCoverExample">
<div class="modal-dialog">
<div class="modal-content">
...
</div>
</div>
</div>

-Toast

<div class="toast" data-autohide="false">
<div class="toast-header text-theme gap-2">
<i class="fa fa-fw fa-bell opacity-5"></i>
<strong class="flex-1">BOOTSTRAP</strong>
<small class="text-muted">11 mins ago</small>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
Hello, world! This is a toast message.
</div>
</div>

<script>
$('.toast').toast('show');
</script>
  • Toasts Container - Toasts container is an extended ui from Bootstrap toasts. Wrap the toasts with .toasts-container will allow toast to float within the right top position.
...
...