feat: implement user profiles with achievements and primary badge system (#58)
Node.js CI / CI (push) Successful in 1m21s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m22s

## 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 #45
Closes #48
Closes #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>
This commit was merged in pull request #58.
This commit is contained in:
2026-02-19 22:21:17 -08:00
committed by Naomi Carrigan
parent 7579f1ec97
commit 86404497f0
58 changed files with 8325 additions and 449 deletions
+4 -1
View File
@@ -3,9 +3,11 @@
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export * from "./lib/achievement.constants";
export * from "./lib/achievement.types";
export type * from "./lib/art.types";
export * from "./lib/audit.types";
export type * from "./lib/auth.types";
export * from "./lib/auth.types";
export * from "./lib/book.types";
export type * from "./lib/comment.types";
export type * from "./lib/common.types";
@@ -13,5 +15,6 @@ export * from "./lib/game.types";
export type * from "./lib/like.types";
export * from "./lib/manga.types";
export * from "./lib/music.types";
export * from "./lib/report.types";
export * from "./lib/show.types";
export * from "./lib/suggestion.types";
@@ -0,0 +1,644 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
AchievementCategory,
AchievementDefinition,
AchievementTier,
} from "./achievement.types";
export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
// ========== SUGGESTION ACHIEVEMENTS (15) ==========
suggestion_first_steps: {
key: "suggestion_first_steps",
title: "First Steps",
description: "Submit your first 10 suggestions to the library",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Bronze,
icon: "🌱",
points: 50,
requirements: { count: 10 },
},
suggestion_contributor: {
key: "suggestion_contributor",
title: "Contributor",
description: "Submit 50 suggestions to the library",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Silver,
icon: "📝",
points: 100,
requirements: { count: 50 },
},
suggestion_dedicated: {
key: "suggestion_dedicated",
title: "Dedicated",
description: "Submit 100 suggestions to the library",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Gold,
icon: "⭐",
points: 250,
requirements: { count: 100 },
},
suggestion_master: {
key: "suggestion_master",
title: "Master Curator",
description: "Submit 250 suggestions to the library",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Platinum,
icon: "💎",
points: 500,
requirements: { count: 250 },
},
suggestion_legend: {
key: "suggestion_legend",
title: "Legend",
description: "Submit 500 suggestions to the library",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Diamond,
icon: "👑",
points: 1000,
requirements: { count: 500 },
},
suggestion_approved: {
key: "suggestion_approved",
title: "Approved!",
description: "Have your first suggestion accepted",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Bronze,
icon: "✅",
points: 50,
requirements: { count: 1 },
},
suggestion_quality_5: {
key: "suggestion_quality_5",
title: "Quality Curator",
description: "Have 5 suggestions accepted",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Silver,
icon: "🌟",
points: 100,
requirements: { count: 5 },
},
suggestion_quality_10: {
key: "suggestion_quality_10",
title: "Elite Curator",
description: "Have 10 suggestions accepted",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Gold,
icon: "🏆",
points: 200,
requirements: { count: 10 },
},
suggestion_quality_25: {
key: "suggestion_quality_25",
title: "Master Curator",
description: "Have 25 suggestions accepted",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Platinum,
icon: "💫",
points: 400,
requirements: { count: 25 },
},
suggestion_quality_50: {
key: "suggestion_quality_50",
title: "Grand Master",
description: "Have 50 suggestions accepted",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Platinum,
icon: "🎖️",
points: 700,
requirements: { count: 50 },
},
suggestion_quality_100: {
key: "suggestion_quality_100",
title: "Ultimate Curator",
description: "Have 100 suggestions accepted",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Diamond,
icon: "👸",
points: 1500,
requirements: { count: 100 },
},
suggestion_acceptance_75: {
key: "suggestion_acceptance_75",
title: "Quality Over Quantity",
description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Gold,
icon: "🎯",
points: 300,
requirements: { rate: 0.75, count: 20 },
},
suggestion_acceptance_100: {
key: "suggestion_acceptance_100",
title: "Perfect Record",
description: "Achieve 100% acceptance rate (minimum 10 suggestions)",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Platinum,
icon: "✨",
points: 500,
requirements: { rate: 1.0, count: 10 },
},
suggestion_renaissance: {
key: "suggestion_renaissance",
title: "Renaissance Person",
description: "Submit accepted suggestions across all 6 media types",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Gold,
icon: "🌈",
points: 400,
requirements: { diversity: true },
},
suggestion_enthusiast: {
key: "suggestion_enthusiast",
title: "Suggestion Enthusiast",
description: "Submit 5 suggestions in one day",
category: AchievementCategory.Suggestion,
tier: AchievementTier.Silver,
icon: "🔥",
points: 150,
requirements: { count: 5, dayRange: 1 },
},
// ========== LIKE ACHIEVEMENTS (12) ==========
like_first: {
key: "like_first",
title: "First Like",
description: "Like your first item in the library",
category: AchievementCategory.Like,
tier: AchievementTier.Bronze,
icon: "❤️",
points: 25,
requirements: { count: 1 },
},
like_enthusiast: {
key: "like_enthusiast",
title: "Enthusiast",
description: "Like 25 items in the library",
category: AchievementCategory.Like,
tier: AchievementTier.Bronze,
icon: "💕",
points: 50,
requirements: { count: 25 },
},
like_fan: {
key: "like_fan",
title: "Fan",
description: "Like 100 items in the library",
category: AchievementCategory.Like,
tier: AchievementTier.Silver,
icon: "💖",
points: 100,
requirements: { count: 100 },
},
like_super_fan: {
key: "like_super_fan",
title: "Super Fan",
description: "Like 250 items in the library",
category: AchievementCategory.Like,
tier: AchievementTier.Gold,
icon: "💝",
points: 250,
requirements: { count: 250 },
},
like_mega_fan: {
key: "like_mega_fan",
title: "Mega Fan",
description: "Like 500 items in the library",
category: AchievementCategory.Like,
tier: AchievementTier.Platinum,
icon: "💗",
points: 500,
requirements: { count: 500 },
},
like_legendary: {
key: "like_legendary",
title: "Legendary Fan",
description: "Like 1000 items in the library",
category: AchievementCategory.Like,
tier: AchievementTier.Diamond,
icon: "💞",
points: 1000,
requirements: { count: 1000 },
},
like_book_lover: {
key: "like_book_lover",
title: "Book Lover",
description: "Like 50 books",
category: AchievementCategory.Like,
tier: AchievementTier.Silver,
icon: "📚",
points: 100,
requirements: { count: 50 },
},
like_gamer: {
key: "like_gamer",
title: "Gamer",
description: "Like 50 games",
category: AchievementCategory.Like,
tier: AchievementTier.Silver,
icon: "🎮",
points: 100,
requirements: { count: 50 },
},
like_cinephile: {
key: "like_cinephile",
title: "Cinephile",
description: "Like 50 shows/films",
category: AchievementCategory.Like,
tier: AchievementTier.Silver,
icon: "🎬",
points: 100,
requirements: { count: 50 },
},
like_music: {
key: "like_music",
title: "Music Enthusiast",
description: "Like 50 music albums",
category: AchievementCategory.Like,
tier: AchievementTier.Silver,
icon: "🎵",
points: 100,
requirements: { count: 50 },
},
like_diverse: {
key: "like_diverse",
title: "Diverse Taste",
description: "Like items from all 6 media types",
category: AchievementCategory.Like,
tier: AchievementTier.Gold,
icon: "🌍",
points: 200,
requirements: { diversity: true },
},
like_binge: {
key: "like_binge",
title: "Binge Liker",
description: "Like 20+ items in one day",
category: AchievementCategory.Like,
tier: AchievementTier.Silver,
icon: "⚡",
points: 150,
requirements: { count: 20, dayRange: 1 },
},
// ========== COMMENT ACHIEVEMENTS (12) ==========
comment_first: {
key: "comment_first",
title: "First Thoughts",
description: "Leave your first comment in the library",
category: AchievementCategory.Comment,
tier: AchievementTier.Bronze,
icon: "💬",
points: 25,
requirements: { count: 1 },
},
comment_reviewer: {
key: "comment_reviewer",
title: "Reviewer",
description: "Leave 10 comments in the library",
category: AchievementCategory.Comment,
tier: AchievementTier.Bronze,
icon: "✍️",
points: 50,
requirements: { count: 10 },
},
comment_critic: {
key: "comment_critic",
title: "Critic",
description: "Leave 50 comments in the library",
category: AchievementCategory.Comment,
tier: AchievementTier.Silver,
icon: "🗣️",
points: 100,
requirements: { count: 50 },
},
comment_expert: {
key: "comment_expert",
title: "Expert Critic",
description: "Leave 100 comments in the library",
category: AchievementCategory.Comment,
tier: AchievementTier.Gold,
icon: "🎭",
points: 250,
requirements: { count: 100 },
},
comment_master: {
key: "comment_master",
title: "Master Reviewer",
description: "Leave 250 comments in the library",
category: AchievementCategory.Comment,
tier: AchievementTier.Platinum,
icon: "📖",
points: 500,
requirements: { count: 250 },
},
comment_legend: {
key: "comment_legend",
title: "Review Legend",
description: "Leave 500 comments in the library",
category: AchievementCategory.Comment,
tier: AchievementTier.Diamond,
icon: "🏅",
points: 1000,
requirements: { count: 500 },
},
comment_detailed: {
key: "comment_detailed",
title: "Detailed Critic",
description: "Write 10 comments with 500+ characters",
category: AchievementCategory.Comment,
tier: AchievementTier.Silver,
icon: "📝",
points: 150,
requirements: { count: 10 },
},
comment_essay: {
key: "comment_essay",
title: "Essay Writer",
description: "Write 5 comments with 1000+ characters",
category: AchievementCategory.Comment,
tier: AchievementTier.Gold,
icon: "📄",
points: 300,
requirements: { count: 5 },
},
comment_novel: {
key: "comment_novel",
title: "Novel Writer",
description: "Write 3 comments with 2000+ characters",
category: AchievementCategory.Comment,
tier: AchievementTier.Platinum,
icon: "📚",
points: 500,
requirements: { count: 3 },
},
comment_thoughtful: {
key: "comment_thoughtful",
title: "Thoughtful Reviewer",
description: "Comment on 50 different items",
category: AchievementCategory.Comment,
tier: AchievementTier.Silver,
icon: "💭",
points: 150,
requirements: { uniqueItems: 50 },
},
comment_diverse: {
key: "comment_diverse",
title: "Well-Rounded Critic",
description: "Comment on all 6 media types",
category: AchievementCategory.Comment,
tier: AchievementTier.Gold,
icon: "🎨",
points: 250,
requirements: { diversity: true },
},
comment_first_to_comment: {
key: "comment_first_to_comment",
title: "Discussion Starter",
description: "Be the first to comment on 10 items",
category: AchievementCategory.Comment,
tier: AchievementTier.Silver,
icon: "🗨️",
points: 200,
requirements: { count: 10 },
},
// ========== ENGAGEMENT ACHIEVEMENTS (15) ==========
engagement_welcome: {
key: "engagement_welcome",
title: "Welcome!",
description: "Complete your profile setup",
category: AchievementCategory.Engagement,
tier: AchievementTier.Bronze,
icon: "👋",
points: 25,
requirements: {},
},
engagement_streak_7: {
key: "engagement_streak_7",
title: "Daily Visitor",
description: "Login for 7 days in a row",
category: AchievementCategory.Engagement,
tier: AchievementTier.Bronze,
icon: "🔥",
points: 100,
requirements: { streak: 7 },
},
engagement_streak_30: {
key: "engagement_streak_30",
title: "Dedicated",
description: "Login for 30 days in a row",
category: AchievementCategory.Engagement,
tier: AchievementTier.Silver,
icon: "💪",
points: 250,
requirements: { streak: 30 },
},
engagement_streak_100: {
key: "engagement_streak_100",
title: "Committed",
description: "Login for 100 days in a row",
category: AchievementCategory.Engagement,
tier: AchievementTier.Gold,
icon: "🏆",
points: 500,
requirements: { streak: 100 },
},
engagement_streak_365: {
key: "engagement_streak_365",
title: "Unstoppable",
description: "Login for 365 days in a row",
category: AchievementCategory.Engagement,
tier: AchievementTier.Diamond,
icon: "⚡",
points: 2000,
requirements: { streak: 365 },
},
engagement_triple_threat: {
key: "engagement_triple_threat",
title: "Triple Threat",
description: "Submit a suggestion, like an item, and leave a comment in the same day",
category: AchievementCategory.Engagement,
tier: AchievementTier.Silver,
icon: "🎯",
points: 200,
requirements: { dayRange: 1 },
},
engagement_power_user: {
key: "engagement_power_user",
title: "Power User",
description: "Achieve Triple Threat 10 times",
category: AchievementCategory.Engagement,
tier: AchievementTier.Platinum,
icon: "💫",
points: 750,
requirements: { count: 10 },
},
engagement_early_adopter: {
key: "engagement_early_adopter",
title: "Early Adopter",
description: "Be among the first 10 users to join",
category: AchievementCategory.Engagement,
tier: AchievementTier.Diamond,
icon: "🌟",
points: 1000,
requirements: {},
},
engagement_founding_100: {
key: "engagement_founding_100",
title: "Founding Member",
description: "Be among the first 100 users to join",
category: AchievementCategory.Engagement,
tier: AchievementTier.Gold,
icon: "🎖️",
points: 500,
requirements: {},
},
engagement_founding_1000: {
key: "engagement_founding_1000",
title: "Pioneer",
description: "Be among the first 1000 users to join",
category: AchievementCategory.Engagement,
tier: AchievementTier.Silver,
icon: "🚀",
points: 200,
requirements: {},
},
engagement_veteran_30: {
key: "engagement_veteran_30",
title: "Veteran",
description: "Have an account for 30 days",
category: AchievementCategory.Engagement,
tier: AchievementTier.Bronze,
icon: "⏰",
points: 50,
requirements: { dayRange: 30 },
},
engagement_veteran_180: {
key: "engagement_veteran_180",
title: "Seasoned",
description: "Have an account for 6 months",
category: AchievementCategory.Engagement,
tier: AchievementTier.Silver,
icon: "📅",
points: 150,
requirements: { dayRange: 180 },
},
engagement_veteran_365: {
key: "engagement_veteran_365",
title: "Longstanding",
description: "Have an account for 1 year",
category: AchievementCategory.Engagement,
tier: AchievementTier.Gold,
icon: "🎂",
points: 300,
requirements: { dayRange: 365 },
},
engagement_veteran_730: {
key: "engagement_veteran_730",
title: "Elder",
description: "Have an account for 2 years",
category: AchievementCategory.Engagement,
tier: AchievementTier.Platinum,
icon: "🗓️",
points: 600,
requirements: { dayRange: 730 },
},
engagement_veteran_1825: {
key: "engagement_veteran_1825",
title: "Legend",
description: "Have an account for 5 years",
category: AchievementCategory.Engagement,
tier: AchievementTier.Diamond,
icon: "🌠",
points: 1500,
requirements: { dayRange: 1825 },
},
// ========== REPORT ACHIEVEMENTS (8) ==========
report_watchful: {
key: "report_watchful",
title: "Watchful Eye",
description: "Submit your first valid report (ACTION_TAKEN)",
category: AchievementCategory.Report,
tier: AchievementTier.Bronze,
icon: "👀",
points: 50,
requirements: { count: 1 },
},
report_guardian: {
key: "report_guardian",
title: "Guardian",
description: "Have 5 reports result in ACTION_TAKEN",
category: AchievementCategory.Report,
tier: AchievementTier.Silver,
icon: "🛡️",
points: 100,
requirements: { count: 5 },
},
report_protector: {
key: "report_protector",
title: "Protector",
description: "Have 10 reports result in ACTION_TAKEN",
category: AchievementCategory.Report,
tier: AchievementTier.Gold,
icon: "⚔️",
points: 250,
requirements: { count: 10 },
},
report_vigilant: {
key: "report_vigilant",
title: "Vigilant Protector",
description: "Have 25 reports result in ACTION_TAKEN",
category: AchievementCategory.Report,
tier: AchievementTier.Platinum,
icon: "🔰",
points: 500,
requirements: { count: 25 },
},
report_accuracy_80: {
key: "report_accuracy_80",
title: "Sharp Eye",
description: "Maintain 80%+ ACTION_TAKEN rate (minimum 10 reports)",
category: AchievementCategory.Report,
tier: AchievementTier.Gold,
icon: "🎯",
points: 300,
requirements: { rate: 0.8, count: 10 },
},
report_accuracy_90: {
key: "report_accuracy_90",
title: "Eagle Eye",
description: "Maintain 90%+ ACTION_TAKEN rate (minimum 10 reports)",
category: AchievementCategory.Report,
tier: AchievementTier.Platinum,
icon: "🦅",
points: 500,
requirements: { rate: 0.9, count: 10 },
},
report_consistent: {
key: "report_consistent",
title: "Consistent Guardian",
description: "Submit at least one report weekly for 4 weeks",
category: AchievementCategory.Report,
tier: AchievementTier.Silver,
icon: "📆",
points: 200,
requirements: { count: 4 },
},
report_volume: {
key: "report_volume",
title: "Dedicated Reporter",
description: "Submit 50 reports (any status)",
category: AchievementCategory.Report,
tier: AchievementTier.Silver,
icon: "📝",
points: 150,
requirements: { count: 50 },
},
};
export const ACHIEVEMENT_LIST = Object.values(ACHIEVEMENTS);
+70
View File
@@ -0,0 +1,70 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export enum AchievementCategory {
Suggestion = "SUGGESTION",
Like = "LIKE",
Comment = "COMMENT",
Engagement = "ENGAGEMENT",
Report = "REPORT",
}
export enum AchievementTier {
Bronze = "BRONZE",
Silver = "SILVER",
Gold = "GOLD",
Platinum = "PLATINUM",
Diamond = "DIAMOND",
}
export interface AchievementRequirements {
count?: number;
rate?: number;
streak?: number;
diversity?: boolean;
uniqueItems?: number;
dayRange?: number;
}
export interface AchievementDefinition {
key: string;
title: string;
description: string;
category: AchievementCategory;
tier: AchievementTier;
icon: string;
points: number;
requirements: AchievementRequirements;
}
export interface UserAchievement {
id: string;
userId: string;
achievementKey: string;
progress: number;
earned: boolean;
earnedAt?: Date;
createdAt: Date;
updatedAt: Date;
}
export interface AchievementProgress {
definition: AchievementDefinition;
progress: number;
earned: boolean;
earnedAt?: Date;
}
export interface UserAchievementSummary {
totalPoints: number;
totalEarned: number;
recentAchievements: AchievementProgress[];
progressByCategory: {
category: AchievementCategory;
earned: number;
total: number;
}[];
}
+1
View File
@@ -21,6 +21,7 @@ enum AuditAction {
rateLimitExceeded = "RATE_LIMIT_EXCEEDED",
csrfValidationFailed = "CSRF_VALIDATION_FAILED",
unauthorizedAccess = "UNAUTHORIZED_ACCESS",
achievementUnlocked = "ACHIEVEMENT_UNLOCKED",
}
enum AuditCategory {
+33 -11
View File
@@ -4,18 +4,39 @@
* @author Naomi Carrigan
*/
/* eslint-disable @typescript-eslint/naming-convention -- Prisma enum values use UPPER_CASE */
enum PrimaryBadge {
STAFF = "STAFF",
MOD = "MOD",
VIP = "VIP",
DISCORD = "DISCORD",
}
/* eslint-enable @typescript-eslint/naming-convention */
interface User {
id: string;
email: string;
username: string;
avatar?: string;
discordId: string;
isAdmin: boolean;
isBanned: boolean;
inDiscord: boolean;
isVip: boolean;
isMod: boolean;
isStaff: boolean;
id: string;
email: string;
username: string;
avatar?: string;
slug?: string;
displayName?: string;
bio?: string;
profilePublic: boolean;
primaryBadge?: PrimaryBadge;
website?: string;
discordServer?: string;
bluesky?: string;
github?: string;
linkedin?: string;
twitch?: string;
youtube?: string;
discordId: string;
isAdmin: boolean;
isBanned: boolean;
inDiscord: boolean;
isVip: boolean;
isMod: boolean;
isStaff: boolean;
}
interface JwtPayload {
@@ -36,4 +57,5 @@ interface AuthResponse {
user: User;
}
export { PrimaryBadge };
export type { AuthResponse, JwtPayload, User };
+24 -20
View File
@@ -4,30 +4,34 @@
* @author Naomi Carrigan
*/
import { PrimaryBadge } from "./auth.types";
interface CommentUser {
id: string;
username: string;
avatar?: string;
inDiscord?: boolean;
isVip?: boolean;
isMod?: boolean;
isStaff?: boolean;
id: string;
username: string;
avatar?: string;
primaryBadge?: PrimaryBadge;
inDiscord?: boolean;
isVip?: boolean;
isMod?: boolean;
isStaff?: boolean;
}
interface Comment {
id: string;
content: string;
rawContent?: string;
userId: string;
user: CommentUser;
gameId?: string;
bookId?: string;
musicId?: string;
artId?: string;
showId?: string;
mangaId?: string;
createdAt: Date;
updatedAt: Date;
id: string;
content: string;
rawContent?: string;
userId: string;
user: CommentUser;
gameId?: string;
bookId?: string;
musicId?: string;
artId?: string;
showId?: string;
mangaId?: string;
hasPendingReports?: boolean;
createdAt: Date;
updatedAt: Date;
}
interface CreateCommentDto {
+8 -8
View File
@@ -32,16 +32,16 @@ interface Game {
}
interface CreateGameDto {
title: string;
platform?: string;
status: GameStatus;
title: string;
platform?: string;
status: GameStatus;
dateStarted?: Date;
dateFinished?: Date;
rating?: number;
notes?: string;
coverImage?: string;
tags?: Array<string>;
links?: Array<Link>;
rating?: number;
notes?: string;
coverImage?: string;
tags?: Array<string>;
links?: Array<Link>;
}
interface UpdateGameDto extends Partial<CreateGameDto> {
+110
View File
@@ -0,0 +1,110 @@
export enum ReportReason {
INAPPROPRIATE_CONTENT = "INAPPROPRIATE_CONTENT",
HARASSMENT = "HARASSMENT",
SPAM = "SPAM",
IMPERSONATION = "IMPERSONATION",
OFFENSIVE_NAME = "OFFENSIVE_NAME",
MALICIOUS_LINKS = "MALICIOUS_LINKS",
OTHER = "OTHER",
}
export enum ReportStatus {
PENDING = "PENDING",
REVIEWED = "REVIEWED",
DISMISSED = "DISMISSED",
ACTION_TAKEN = "ACTION_TAKEN",
}
export interface ProfileReport {
id: string;
reportedUserId: string;
reporterId: string;
reason: ReportReason;
details: string;
status: ReportStatus;
reviewedBy?: string;
reviewNotes?: string;
createdAt: Date;
updatedAt: Date;
}
export interface ProfileReportWithUsers extends ProfileReport {
reportedUser: {
id: string;
username: string;
displayName?: string;
avatar?: string;
};
reporter: {
id: string;
username: string;
displayName?: string;
avatar?: string;
};
reviewer?: {
id: string;
username: string;
displayName?: string;
};
}
export interface CreateReportDto {
reportedUserId: string;
reason: ReportReason;
details: string;
}
export interface UpdateReportDto {
status: ReportStatus;
reviewNotes?: string;
}
export interface CommentReport {
id: string;
reportedCommentId: string;
reporterId: string;
reason: ReportReason;
details: string;
status: ReportStatus;
reviewedBy?: string;
reviewNotes?: string;
createdAt: Date;
updatedAt: Date;
}
export interface CommentReportWithDetails extends CommentReport {
reportedComment: {
id: string;
content: string;
rawContent?: string;
userId: string;
user: {
id: string;
username: string;
displayName?: string;
avatar?: string;
};
};
reporter: {
id: string;
username: string;
displayName?: string;
avatar?: string;
};
reviewer?: {
id: string;
username: string;
displayName?: string;
};
}
export interface CreateCommentReportDto {
reportedCommentId: string;
reason: ReportReason;
details: string;
}
export interface UpdateCommentReportDto {
status: ReportStatus;
reviewNotes?: string;
}