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
Completes time tracking implementation across all media types by
applying the same pattern established in Games component.
Changes Applied to Books, Music, Shows, and Manga:
UI Components:
- Added time tracking state properties (hours/minutes for new and edit)
- Integrated hour/minute input fields in Add forms (after rating)
- Integrated hour/minute input fields in Edit forms (after edit-rating)
- Added time spent display on media cards with appropriate emojis:
* Books: π Reading Time
* Music: π΅ Listening Time
* Shows: πΊ Watch Time
* Manga: π Reading Time
Form Management:
- Updated resetForm() to clear time tracking fields
- Added updateNew[Type]TimeSpent() conversion methods
- Added updateEdit[Type]TimeSpent() conversion methods
- Updated startEdit() to populate time fields from stored data
Helper Methods:
- Added formatTimeSpent() to format minutes as "Xh Ym", "Xh", or "Ym"
- Converts hours/minutes to total minutes for storage
- Splits total minutes back to hours/minutes for editing
Styling:
- Added .form-row CSS for side-by-side hour/minute inputs
- Added .time-spent CSS with coloured display:
* Books: Green (#10b981)
* Music: Purple (#8b5cf6)
* Shows: Purple (#8b5cf6)
* Manga: Green (#10b981)
All implementations follow the exact same pattern for consistency
and maintainability across the application.
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
- Add series name and order display to book cards (books-list.component.ts:598-602)
- Add series name and order display to game cards (games-list.component.ts:560-564)
- Series shows as "π Series Name #Order" format
- Add brown-themed styling (#8b6f47) for series info
- Pre-populate series fields when editing books/games
- Series fields added to startEdit() methods for both books and games
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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>
Implements comprehensive About page (#51) with the following sections:
- Purpose: Explains that this is Naomi's curated collection
- Features: Overview of all 6 media types (Books, Games, Manga, Shows, Music, Art)
- How to Use: 5-step guide for browsing, liking, commenting, suggesting, and earning achievements
- Technology Stack: Details Angular, Fastify, MongoDB, Prisma, and other technologies
- Credits: Acknowledges Hikari as the developer and Naomi as the visionary
- Contact & Support: Links to Gitea, email, website, and Discord community
- Version Information: Current version and licence details
Technical implementation:
- New AboutComponent with responsive design
- Added /about route to app routes
- Added "βΉοΈ About" link to user dropdown menu
- Beautiful purple gradient styling matching the witchy aesthetic
- FontAwesome icons for visual appeal
- Fully responsive for all device sizes
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>