Files
library/apps/frontend/src/app/services/user.service.ts
T
hikari 3da648544e
Node.js CI / CI (pull_request) Failing after 1m21s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m22s
feat: implement comprehensive achievement system with 62 achievements
Adds a complete achievement system with gamification features across all user interactions.

**Database & Types:**
- Add UserAchievement model to track user progress and earned achievements
- Add achievement-related fields to User model (achievementPoints, currentStreak, lastStreakCheck)
- Add ACHIEVEMENT_UNLOCKED audit action type
- Define 62 achievements as TypeScript constants across 5 categories

**Achievement Categories:**
- Suggestions (15): First suggestion through ultimate curator milestones
- Likes (12): First like through legendary fan milestones
- Comments (12): First comment through review legend milestones
- Engagement (15): Login streaks and total activity tracking
- Reports (8): Valid reports and accuracy tracking

**Backend Implementation:**
- Create AchievementService with comprehensive checking logic
- Add achievement route with 6 API endpoints
- Integrate achievement checking into all user interaction points:
  - Suggestions (create + accept)
  - Likes (toggle)
  - Comments (all 6 media types)
  - Login streaks
  - Reports (profile + comment)
- Update UserService to include achievement points in profiles

**Frontend Implementation:**
- Create AchievementService for API communication
- Create achievements page showing all 62 achievements with:
  - Category filtering (All, Suggestions, Likes, Comments, Engagement, Reports)
  - Tier-based styling (Bronze, Silver, Gold, Platinum, Diamond)
  - Progress indicators for in-progress achievements
  - Earned date display for completed achievements
- Add "Recent Achievements" section to user profiles
- Add "🏆 Achievements" link to header navigation
- Only show "View All" link on own profile

**Technical Features:**
- Real-time achievement checking on user actions
- Progress tracking to avoid recalculation
- Points system for gamification
- Tier-based gradient styling
- Leaderboard-ready architecture

Resolves #48

Co-Authored-By: Hikari <hikari@nhcarrigan.com>
2026-02-19 22:02:10 -08:00

96 lines
2.2 KiB
TypeScript

/**
* @copyright 2026 NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from './api.service';
import { User, PrimaryBadge } from '@library/shared-types';
export interface UserProfileResponse {
id: string;
username: string;
displayName?: string;
avatar?: string;
bio?: string;
slug?: string;
primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
github?: string;
linkedin?: string;
twitch?: string;
youtube?: string;
achievementPoints: number;
badges: {
isStaff: boolean;
isMod: boolean;
isVip: boolean;
inDiscord: boolean;
};
stats: {
suggestionsCount: number;
suggestionsAcceptedCount: number;
likesCount: number;
commentsCount: number;
};
createdAt: Date;
}
export interface UpdateUserSettingsRequest {
slug?: string;
displayName?: string;
bio?: string;
profilePublic?: boolean;
primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
github?: string;
linkedin?: string;
twitch?: string;
youtube?: string;
}
@Injectable({
providedIn: 'root'
})
export class UserService {
private api = inject(ApiService);
getAllUsers(): Observable<User[]> {
return this.api.get<User[]>('/users');
}
banUser(userId: string): Observable<User> {
return this.api.post<User>(`/users/${userId}/ban`, {});
}
unbanUser(userId: string): Observable<User> {
return this.api.post<User>(`/users/${userId}/unban`, {});
}
getMe(): Observable<User> {
return this.api.get<User>('/users/me');
}
updateSettings(settings: UpdateUserSettingsRequest): Observable<User> {
return this.api.put<User>('/users/me', settings);
}
getProfile(identifier: string): Observable<UserProfileResponse> {
return this.api.get<UserProfileResponse>(`/users/profile/${identifier}`);
}
makeProfilePrivate(userId: string): Observable<User> {
return this.api.post<User>(`/users/${userId}/make-private`, {});
}
adminUpdateUser(userId: string, settings: UpdateUserSettingsRequest): Observable<User> {
return this.api.put<User>(`/users/${userId}`, settings);
}
}