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
Implements issue #57 with manual time entry functionality:
- Added timeSpent field (stored in minutes) to all media models
- Supports hours and minutes input in forms
- Displays formatted time on media cards
Database Schema:
- Added timeSpent Int? field to Game, Book, Music, Show, and Manga models
- Stored in minutes for consistency and easy calculation
Shared Types:
- Updated all media type interfaces with timeSpent? field
- Added to CreateDto and main interfaces for all media types
Frontend (Games - template for other types):
- Added hour/minute input fields to Add and Edit forms
- Split input with validation (minutes 0-59)
- Auto-calculates total minutes on change
- Formats display as "Xh Ym", "Xh", or "Ym" as appropriate
- Green highlighted time display on cards with ⏱️ icon
- Populates edit form from existing time data
Implementation Notes:
- Backend services require no changes (DTOs handle automatically)
- Time conversion helpers for display and storage
- Form state properly resets time fields
- Edit mode correctly splits minutes back to hours/minutes
Next Steps:
- Apply same UI pattern to Books, Music, Shows, Manga
- Add time statistics to user profiles
- Consider aggregate time tracking views
Implements issue #55 with multiple leaderboard categories:
- Top Suggestions (by count and acceptance rate)
- Top Likes (by total likes given)
- Top Comments (by total comments posted)
- Overall Leaders (weighted by achievement points and engagement diversity)
Features:
- Tabbed UI with reactive state management
- Medal indicators for top 3 positions
- User avatars and badges display
- Current user highlighting
- Privacy controls via profilePublic setting
- Configurable result limits (max 100)
- Detailed statistics per category
Backend:
- Created LeaderboardService with aggregation logic
- Filters for public profiles and non-banned users
- Efficient sorting algorithms for each category
- Parallel data fetching for all leaderboards
Frontend:
- Standalone Angular component with signals
- Responsive card-based layout
- Integration with existing user profile system
- Navigation link in header dropdown
Technical notes:
- Uses Fastify AutoLoad with FastifyPluginAsync pattern
- Shared types across monorepo for type safety
- Leverages existing achievement system data
Implements series grouping functionality for books and games to allow
tracking franchises and related items.
Database changes:
- Add series (optional string) and seriesOrder (optional number) fields to Book model
- Add series (optional string) and seriesOrder (optional number) fields to Game model
Backend changes:
- Add GET /books/series/:seriesName endpoint to fetch all books in a series
- Add GET /games/series/:seriesName endpoint to fetch all games in a series
- Add getBooksBySeries() method to BookService (orders by seriesOrder asc)
- Add getGamesBySeries() method to GameService (orders by seriesOrder asc)
- Create/Update endpoints automatically handle series fields via DTOs
Frontend changes:
- Add series and seriesOrder input fields to "Add New Book" form
- Add series and seriesOrder input fields to "Edit Book" form
- Add series and seriesOrder input fields to "Add New Game" form
- Add series and seriesOrder input fields to "Edit Game" form
Type definitions:
- Update Book and CreateBookDto interfaces with series fields
- Update Game and CreateGameDto interfaces with series fields
Closes#54
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Summary
This PR implements comprehensive user profile enhancements including:
- User profile pages showing stats, badges, social links, and bio
- Achievement system with 62 achievements across 5 categories
- Primary badge selection allowing users to display their preferred badge
- Admin profile editing capabilities
## Changes
### User Profiles (#45)
- **Frontend**: User profile pages with stats display
- Profile cards showing avatar, display name, username, and bio
- Social links section (Website, GitHub, Bluesky, LinkedIn, Twitch, YouTube, Discord)
- Stats display (suggestions, accepted suggestions, likes, comments)
- Recent achievements section
- Badge display
- Report button for other users' profiles
- **Backend**: Profile API endpoints
- Get user profile by username or ID
- Profile includes stats, badges, and achievement points
### Achievement System (#48)
- **Database**: UserAchievement model for tracking progress
- **62 Total Achievements** across 5 categories:
- **Suggestions (15)**: First suggestion through ultimate curator
- **Likes (12)**: First like through legendary fan
- **Comments (12)**: First comment through review legend
- **Engagement (15)**: Login streaks and activity milestones
- **Reports (8)**: Valid reports and accuracy tracking
- **Backend**: AchievementService with real-time checking
- Integrated into all user interaction points
- API endpoints for achievement data
- Progress tracking to avoid recalculation
- **Frontend**: Achievements page and profile integration
- Full achievements page with category filtering
- Tier-based styling (Bronze, Silver, Gold, Platinum, Diamond)
- Progress indicators for in-progress achievements
- Recent achievements on profile pages
### Primary Badge System (#49)
- **Database**: Add primaryBadge field to User model
- **Backend**: Update profile endpoints to include primary badge
- **Frontend**: Primary badge selection in settings
- Only shows badges the user has earned
- Displayed on profile page
- Displayed in comments (next to username)
- Falls back to no badge if selection is invalid
- **Admin Features**: Admin can edit any user's primary badge
### Admin Enhancements
- Comprehensive profile editing modal for admins
- Edit display name, bio, slug, social links
- Set primary badge for users
- Visual feedback for save/error states
- Admin action buttons in report review modals
- Ban user, delete comment, edit profile
- Integrated with report workflow
### Quality Improvements
- Improved dropdown option contrast for readability
- Hide all badges when no primary badge is selected
- "View All" achievements link only shown on own profile
- Improved achievement text readability
## Testing
- ✅ User profiles display correctly with stats and badges
- ✅ Achievement checking works for all interaction types
- ✅ Primary badge selection persists and displays correctly
- ✅ Admin profile editing saves successfully
- ✅ Report workflow integrated with admin actions
- ✅ Achievements page shows all 62 achievements with filtering
- ✅ Text readability improved across components
Closes#45Closes#48Closes#49
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #58
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
## Summary
This PR implements several improvements to the library application:
- Added start and finish date tracking for media items
- Added "Retired" category for abandoned media
- Implemented avatar-based user menu with dropdown navigation
- Added automatic background token refresh to prevent session expiry
- Created centralised logging system with frontend-to-API log forwarding
- Added toast notifications for error handling
## Changes
### Media Tracking (#41)
- Added `dateStarted` and `dateFinished` fields to Books, Games, Manga, Music, and Shows
- Updated TypeScript types, Prisma schema, and API services
- Added manual date input fields to frontend forms
- Properly converts HTML date strings to Date objects before API submission
### Retired Category (#43)
- Added `RETIRED` status to all media type enums
- Updated Prisma schema, frontend dropdowns, and filter buttons
- Added status label handling for retired items
### User Menu (#46)
- Replaced username text with avatar image in header
- Created dropdown menu with navigation items (Users, Audit, Suggestions)
- Added logout button to menu
- Implemented keyboard accessibility (tabindex, role, keyup handlers)
### Token Refresh (#44)
- Implemented automatic token refresh every 13 minutes in background
- Added proactive refresh to prevent token expiry during form filling
- Prevents users from losing form data due to expired sessions
### Centralised Logging (#1)
- Created `/log` endpoint on API to receive frontend logs
- Replaced API console.log calls with @nhcarrigan/logger
- Created ConsoleLoggerService to intercept all console methods on frontend
- Added global error handlers (window.error, unhandledrejection) on frontend
- Added process error handlers (uncaughtException, unhandledRejection, SIGTERM, SIGINT) on API
- All frontend console activity now forwarded to centralised logging
### Error Handling
- Created ToastService and ToastComponent for displaying errors
- Integrated with GlobalErrorHandler and HTTP interceptor
- Added accessibility features (keyboard navigation, ARIA attributes)
- Set toast opacity to 40% for optimal readability
### Testing & Build
- Fixed pre-existing test failure for GET / route (now returns version info)
- Added ESM module mocking (jsdom, marked, dompurify, @nhcarrigan/logger)
- Configured Jest with isolatedModules to handle TypeScript errors
- Excluded test-setup.ts from production build
- All tests passing (123 total)
- Build passing with no errors
## Test Plan
- [x] All tests pass (123 tests)
- [x] Build passes without errors
- [x] Lint passes (only pre-existing warnings)
- [x] Date fields work correctly on all media types
- [x] Retired status displays and filters properly
- [x] Avatar menu opens/closes correctly with keyboard and mouse
- [x] Token refresh prevents session expiry
- [x] Toast notifications appear for errors
- [x] Frontend logs forward to API successfully
- [x] Root route returns version information
Closes#41Closes#43Closes#44Closes#46Closes#1🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Reviewed-on: #50
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>