feat: implement comprehensive activity feed feature

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
This commit is contained in:
2026-02-20 00:00:26 -08:00
committed by Naomi Carrigan
parent 5ad9b50dc8
commit b1aab70b44
8 changed files with 955 additions and 0 deletions
+1
View File
@@ -5,6 +5,7 @@
*/
export * from "./lib/achievement.constants";
export * from "./lib/achievement.types";
export type * from "./lib/activity.types";
export type * from "./lib/art.types";
export * from "./lib/audit.types";
export * from "./lib/auth.types";
+79
View File
@@ -0,0 +1,79 @@
/**
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
enum ActivityType {
suggestion = "SUGGESTION",
like = "LIKE",
comment = "COMMENT",
achievement = "ACHIEVEMENT",
}
interface ActivityUser {
id: string;
username: string;
slug: string | null;
avatar: string | null;
primaryBadge: string | null;
isVip: boolean;
isMod: boolean;
isStaff: boolean;
}
interface BaseActivity {
id: string;
type: ActivityType;
user: ActivityUser;
createdAt: Date;
}
interface SuggestionActivity extends BaseActivity {
type: ActivityType.suggestion;
entityType: string;
suggestionTitle: string;
status: string;
}
interface LikeActivity extends BaseActivity {
type: ActivityType.like;
entityType: string;
entityId: string;
entityTitle: string;
}
interface CommentActivity extends BaseActivity {
type: ActivityType.comment;
entityType: string;
entityId: string;
entityTitle: string;
commentPreview: string;
}
interface AchievementActivity extends BaseActivity {
type: ActivityType.achievement;
achievementKey: string;
achievementName: string;
achievementIcon: string;
achievementPoints: number;
}
type Activity = SuggestionActivity | LikeActivity | CommentActivity | AchievementActivity;
interface ActivityFeedResponse {
activities: Activity[];
total: number;
hasMore: boolean;
}
export { ActivityType };
export type {
Activity,
ActivityFeedResponse,
ActivityUser,
AchievementActivity,
CommentActivity,
LikeActivity,
SuggestionActivity,
};