Changed approach from stripping HTML on backend to rendering HTML with
sanitization on frontend, matching the pattern used in comment-display
component. This preserves HTML formatting (bold, italics, etc.) in
comment previews whilst still protecting against XSS attacks.
Backend changes:
- Reverted stripHtml() method (no longer needed)
- Keep full HTML content in commentPreview field
Frontend changes:
- Import and inject SanitizeService
- Changed from text interpolation to [innerHTML] with sanitization
- Changed <p> to <div> for comment preview container
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added stripHtml() helper method to ActivityService to remove HTML tags
from comment content before creating the preview text. This ensures that
comments display as plain text in the activity feed instead of showing
raw HTML like "<p>test</p>".
The regex pattern /<[^>]*>/g removes all HTML tags whilst preserving
the actual text content, which is then trimmed and truncated to 100
characters for the preview.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added explicit return type annotations (SuggestionActivity[], LikeActivity[],
CommentActivity[], AchievementActivity[]) to all private activity service
methods. This ensures TypeScript properly narrows the discriminated union
types based on the ActivityType enum values, resolving compilation errors
where the type system couldn't infer the specific activity subtypes.
Also added type annotation to the async map callback in getLikeActivities
to ensure the returned Promise resolves to the correct LikeActivity type.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed all activity type assignments to use ActivityType enum members
(ActivityType.suggestion, ActivityType.like, etc.) instead of string
literals ('SUGGESTION', 'LIKE', etc.) to ensure proper type discrimination
in the union type.
Frontend:
- Import ActivityType as value (not just type)
- Update switch cases to use enum members
- Expose ActivityType in component for template access
Backend:
- Import ActivityType as value
- Use enum members in all activity mappings
This ensures TypeScript can properly discriminate the union type and
provides type safety throughout the activity feed.
Fixed two type errors:
- Changed ActivityType assertions from 'as ActivityType' to 'as const'
for proper literal type inference
- Changed achievement.name to achievement.title to match the actual
AchievementDefinition structure
This ensures type safety and proper union type discrimination.
Implements issue #56 with a timeline-style activity feed showing recent
user activity across the library.
Activity Types Displayed:
- Suggestions: Shows suggested items with status badges
- Likes: Shows liked content with links to items
- Comments: Shows comments with preview text
- Achievements: Shows earned achievements with icons and points
Database & Backend:
- Created ActivityService to aggregate from existing data sources
- No new database table needed (uses existing suggestions, likes, comments, achievements)
- Efficient querying with parallel data fetching
- Privacy-aware: Only shows activity from users with profilePublic: true
- Filters out banned users automatically
API Endpoints:
- GET /api/activity - Get general activity feed with pagination
- GET /api/activity/:userId - Get specific user's activity
- Query parameters: limit (max 100, default 50), offset (default 0)
- Returns: activities array, total count, hasMore flag
Frontend Activity Feed:
- Beautiful timeline layout with user avatars and badges
- Relative timestamps ("5m ago", "2h ago", "3d ago")
- Activity type icons (💡💬❤️🏆)
- Colour-coded status badges for suggestions
- Comment previews with styled quote blocks
- Achievement displays with icons and points
- Infinite scroll with "Load More" button
- Links to user profiles and content items
UI Components:
- Responsive card-based design
- User avatars with placeholder fallback
- Badge display (STAFF, MOD, VIP, primary badges)
- Entity links that route to appropriate media pages
- Clean typography and spacing
- Loading and empty states
Privacy & Performance:
- Respects profilePublic setting (existing privacy control)
- Only displays activity from non-banned users
- Pagination to prevent loading too much data
- Efficient aggregation with limit multipliers
- Clean separation of concerns (service/routes/component)
Navigation:
- Added /activity route
- Added "📰 Activity Feed" link to user dropdown menu
- Positioned between Leaderboard and About
Implementation Notes:
- Built entirely from existing data (no activity table needed)
- Retroactive: Shows all historical activity automatically
- Type-safe with full TypeScript interfaces
- Activity union type for type discrimination
- Standalone Angular component
- Clean, maintainable code structure