fix: resolve CSP and accessibility issues #60

Merged
naomi merged 1 commits from fix/wee-bit into main 2026-02-20 02:12:12 -08:00
12 changed files with 463 additions and 376 deletions
Showing only changes of commit d17721aa91 - Show all commits
+2 -3
View File
@@ -13,9 +13,8 @@ const helmetPlugin: FastifyPluginAsync = async (app) => {
contentSecurityPolicy: { contentSecurityPolicy: {
directives: { directives: {
defaultSrc: ["'self'"], defaultSrc: ["'self'"],
// Remove unsafe-inline for better security // Angular uses inline styles for component encapsulation, so we need to allow them
// Angular uses inline styles in development, but production builds should use external CSS styleSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", process.env.NODE_ENV === "production" ? "'self'" : "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"], imgSrc: ["'self'", "data:", "https:"],
scriptSrc: ["'self'"], scriptSrc: ["'self'"],
connectSrc: ["'self'", process.env.FRONTEND_URL ?? "http://localhost:4200"], connectSrc: ["'self'", process.env.FRONTEND_URL ?? "http://localhost:4200"],
+1 -1
View File
@@ -1,7 +1,7 @@
/** /**
* @copyright 2026 NHCarrigan
* @copyright 2026 NHCarrigan * @copyright 2026 NHCarrigan
* @license Naomi's Public License * @license Naomi's Public License
* @author Naomi Carrigan
*/ */
import { getGreeting } from "../support/app.po"; import { getGreeting } from "../support/app.po";
+3 -2
View File
@@ -1,11 +1,12 @@
/** /**
* @copyright 2026 NHCarrigan
* @copyright 2026 NHCarrigan * @copyright 2026 NHCarrigan
* @license Naomi's Public License * @license Naomi's Public License
* @author Naomi Carrigan
*/ */
/** /**
* * Gets the greeting element from the page.
* @returns A Cypress chainable to the h1 element.
*/ */
export const getGreeting = (): Cypress.Chainable => { export const getGreeting = (): Cypress.Chainable => {
return cy.get("h1"); return cy.get("h1");
+1 -1
View File
@@ -1,7 +1,7 @@
/** /**
* @copyright 2026 NHCarrigan
* @copyright 2026 NHCarrigan * @copyright 2026 NHCarrigan
* @license Naomi's Public License * @license Naomi's Public License
* @author Naomi Carrigan
*/ */
/// <reference types="cypress" /> /// <reference types="cypress" />
+1
View File
@@ -3,6 +3,7 @@
* @license Naomi's Public License * @license Naomi's Public License
* @author Naomi Carrigan * @author Naomi Carrigan
*/ */
/* eslint-disable unicorn/prevent-abbreviations -- e2e is a standard Cypress filename */
/** /**
* This example support/e2e.ts is processed and * This example support/e2e.ts is processed and
+1 -1
View File
@@ -1,4 +1,4 @@
<a href="#main-content" class="skip-link">Skip to main content</a> <a href="#main-content" class="skip-link" (click)="skipToMainContent($event)">Skip to main content</a>
<app-header></app-header> <app-header></app-header>
<main id="main-content" class="main-content" tabindex="-1"> <main id="main-content" class="main-content" tabindex="-1">
<router-outlet></router-outlet> <router-outlet></router-outlet>
+32
View File
@@ -1,3 +1,35 @@
// Skip to main content link - visually hidden but accessible to screen readers
.skip-link {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
.skip-link:focus {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: auto !important;
height: auto !important;
padding: 0.75rem 1.5rem !important;
margin: 0 !important;
overflow: visible !important;
clip: auto !important;
white-space: normal !important;
background: var(--witch-rose) !important;
color: var(--witch-moon) !important;
text-decoration: none !important;
font-weight: 600 !important;
border-radius: 0 0 4px 0 !important;
z-index: 10000 !important;
}
.main-content { .main-content {
min-height: calc(100vh - 60px); // Assuming header is ~60px min-height: calc(100vh - 60px); // Assuming header is ~60px
background-color: transparent; // Let the body background show through background-color: transparent; // Let the body background show through
+9
View File
@@ -18,4 +18,13 @@ export class App implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.analytics.initialise(); this.analytics.initialise();
} }
skipToMainContent(event: Event): void {
event.preventDefault();
const mainContent = document.getElementById('main-content');
if (mainContent) {
mainContent.focus();
mainContent.scrollIntoView({ behavior: 'smooth' });
}
}
} }
+21 -8
View File
@@ -143,23 +143,36 @@ button {
background: var(--witch-plum); background: var(--witch-plum);
} }
// Skip to main content link // Skip to main content link - visually hidden but accessible to screen readers
.skip-link { .skip-link {
position: absolute; position: absolute;
top: -40px; width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.skip-link:focus {
position: fixed;
top: 0;
left: 0; left: 0;
width: auto;
height: auto;
padding: 0.75rem 1.5rem;
margin: 0;
overflow: visible;
clip: auto;
white-space: normal;
background: var(--witch-rose); background: var(--witch-rose);
color: var(--witch-moon); color: var(--witch-moon);
padding: 0.75rem 1.5rem;
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
border-radius: 0 0 4px 0; border-radius: 0 0 4px 0;
z-index: 10000; z-index: 10000;
transition: top 0.3s;
}
.skip-link:focus {
top: 0;
} }
// Enhanced focus styles for keyboard navigation // Enhanced focus styles for keyboard navigation
+380 -358
View File
@@ -3,6 +3,11 @@
* @license Naomi's Public License * @license Naomi's Public License
* @author Naomi Carrigan * @author Naomi Carrigan
*/ */
/* eslint-disable @typescript-eslint/naming-convention -- Achievement keys use snake_case for database identifiers */
/* eslint-disable sort-keys -- Achievements organized by category, not alphabetically */
/* eslint-disable sort-keys-fix/sort-keys-fix -- Achievements organized by category, not alphabetically */
/* eslint-disable stylistic/key-spacing -- Aligned for readability */
/* eslint-disable import/group-exports -- Single export at end of file */
import { import {
AchievementCategory, AchievementCategory,
type AchievementDefinition, type AchievementDefinition,
@@ -11,6 +16,61 @@ import {
export const ACHIEVEMENTS: Record<string, AchievementDefinition> = { export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
like_enthusiast: {
category: AchievementCategory.Like,
description: "Like 25 items in the library",
icon: "💕",
key: "like_enthusiast",
points: 50,
title: "Enthusiast",
requirements: { count: 25 },
tier: AchievementTier.Bronze,
},
like_fan: {
category: AchievementCategory.Like,
description: "Like 100 items in the library",
icon: "💖",
key: "like_fan",
points: 100,
title: "Fan",
requirements: { count: 100 },
tier: AchievementTier.Silver,
},
suggestion_acceptance_100: {
category: AchievementCategory.Suggestion,
description: "Achieve 100% acceptance rate (minimum 10 suggestions)",
icon: "✨",
key: "suggestion_acceptance_100",
points: 500,
requirements: { count: 10, rate: 1 },
tier: AchievementTier.Platinum,
title: "Perfect Record",
},
like_book_lover: {
description: "Like 50 books",
key: "like_book_lover",
category: AchievementCategory.Like,
title: "Book Lover",
icon: "📚",
points: 100,
tier: AchievementTier.Silver,
requirements: { count: 50 },
},
suggestion_acceptance_75: {
category: AchievementCategory.Suggestion,
description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)",
icon: "🎯",
key: "suggestion_acceptance_75",
points: 300,
requirements: { count: 20, rate: 0.75 },
title: "Quality Over Quantity",
tier: AchievementTier.Gold,
},
suggestion_approved: { suggestion_approved: {
category: AchievementCategory.Suggestion, category: AchievementCategory.Suggestion,
description: "Have your first suggestion accepted", description: "Have your first suggestion accepted",
@@ -18,8 +78,19 @@ export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
key: "suggestion_approved", key: "suggestion_approved",
points: 50, points: 50,
requirements: { count: 1 }, requirements: { count: 1 },
title: "Approved!",
tier: AchievementTier.Bronze, tier: AchievementTier.Bronze,
title: "Approved!",
},
like_cinephile: {
description: "Like 50 shows/films",
category: AchievementCategory.Like,
key: "like_cinephile",
icon: "🎬",
title: "Cinephile",
points: 100,
requirements: { count: 50 },
tier: AchievementTier.Silver,
}, },
suggestion_contributor: { suggestion_contributor: {
@@ -33,6 +104,17 @@ export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
title: "Contributor", title: "Contributor",
}, },
like_diverse: {
description: "Like items from all 6 media types",
category: AchievementCategory.Like,
key: "like_diverse",
icon: "🌍",
title: "Diverse Taste",
points: 200,
requirements: { diversity: true },
tier: AchievementTier.Gold,
},
suggestion_dedicated: { suggestion_dedicated: {
category: AchievementCategory.Suggestion, category: AchievementCategory.Suggestion,
description: "Submit 100 suggestions to the library", description: "Submit 100 suggestions to the library",
@@ -44,18 +126,42 @@ export const ACHIEVEMENTS: Record<string, AchievementDefinition> = {
title: "Dedicated", title: "Dedicated",
}, },
suggestion_acceptance_100: { // ========== COMMENT ACHIEVEMENTS (12) ==========
key: "suggestion_acceptance_100", comment_first: {
description: "Achieve 100% acceptance rate (minimum 10 suggestions)", description: "Leave your first comment in the library",
title: "Perfect Record", category: AchievementCategory.Comment,
category: AchievementCategory.Suggestion, key: "comment_first",
icon: "", icon: "💬",
tier: AchievementTier.Platinum, title: "First Thoughts",
points: 500, points: 25,
requirements: { count: 10, rate: 1.0 }, requirements: { count: 1 },
tier: AchievementTier.Bronze,
}, },
suggestion_enthusiast: {
category: AchievementCategory.Suggestion,
description: "Submit 5 suggestions in one day",
icon: "🔥",
key: "suggestion_enthusiast",
points: 150,
requirements: { count: 5, dayRange: 1 },
title: "Suggestion Enthusiast",
tier: AchievementTier.Silver,
},
comment_critic: {
description: "Leave 50 comments in the library",
category: AchievementCategory.Comment,
key: "comment_critic",
icon: "🗣️",
title: "Critic",
points: 100,
requirements: { count: 50 },
tier: AchievementTier.Silver,
},
// ========== SUGGESTION ACHIEVEMENTS (15) ========== // ========== SUGGESTION ACHIEVEMENTS (15) ==========
suggestion_first_steps: { suggestion_first_steps: {
category: AchievementCategory.Suggestion, category: AchievementCategory.Suggestion,
description: "Submit your first 10 suggestions to the library", description: "Submit your first 10 suggestions to the library",
icon: "🌱", icon: "🌱",
@@ -63,18 +169,20 @@ suggestion_first_steps: {
points: 50, points: 50,
requirements: { count: 10 }, requirements: { count: 10 },
tier: AchievementTier.Bronze, tier: AchievementTier.Bronze,
title: "First Steps", title: "First Steps",
}, },
suggestion_acceptance_75: {
description: "Maintain a 75%+ acceptance rate (minimum 20 suggestions)", comment_expert: {
key: "suggestion_acceptance_75", category: AchievementCategory.Comment,
category: AchievementCategory.Suggestion, description: "Leave 100 comments in the library",
title: "Quality Over Quantity", icon: "🎭",
icon: "🎯", key: "comment_expert",
points: 300, points: 250,
tier: AchievementTier.Gold, title: "Expert Critic",
requirements: { count: 20, rate: 0.75 }, requirements: { count: 100 },
tier: AchievementTier.Gold,
}, },
suggestion_legend: { suggestion_legend: {
category: AchievementCategory.Suggestion, category: AchievementCategory.Suggestion,
description: "Submit 500 suggestions to the library", description: "Submit 500 suggestions to the library",
@@ -83,18 +191,20 @@ suggestion_first_steps: {
points: 1000, points: 1000,
requirements: { count: 500 }, requirements: { count: 500 },
tier: AchievementTier.Diamond, tier: AchievementTier.Diamond,
title: "Legend", title: "Legend",
}, },
like_enthusiast: {
key: "like_enthusiast", comment_legend: {
description: "Like 25 items in the library", category: AchievementCategory.Comment,
title: "Enthusiast", description: "Leave 500 comments in the library",
category: AchievementCategory.Like, icon: "🏅",
icon: "💕", key: "comment_legend",
tier: AchievementTier.Bronze, points: 1000,
points: 50, title: "Review Legend",
requirements: { count: 25 }, requirements: { count: 500 },
tier: AchievementTier.Diamond,
}, },
suggestion_master: { suggestion_master: {
category: AchievementCategory.Suggestion, category: AchievementCategory.Suggestion,
description: "Submit 250 suggestions to the library", description: "Submit 250 suggestions to the library",
@@ -102,19 +212,21 @@ suggestion_first_steps: {
key: "suggestion_master", key: "suggestion_master",
points: 500, points: 500,
requirements: { count: 250 }, requirements: { count: 250 },
title: "Master Curator",
tier: AchievementTier.Platinum, tier: AchievementTier.Platinum,
title: "Master Curator",
}, },
like_fan: {
description: "Like 100 items in the library", comment_detailed: {
key: "like_fan", category: AchievementCategory.Comment,
category: AchievementCategory.Like, description: "Write 10 comments with 500+ characters",
title: "Fan", icon: "📝",
icon: "💖", key: "comment_detailed",
points: 100, points: 150,
tier: AchievementTier.Silver, requirements: { count: 10 },
requirements: { count: 100 }, title: "Detailed Critic",
tier: AchievementTier.Silver,
}, },
suggestion_quality_10: { suggestion_quality_10: {
category: AchievementCategory.Suggestion, category: AchievementCategory.Suggestion,
description: "Have 10 suggestions accepted", description: "Have 10 suggestions accepted",
@@ -122,59 +234,74 @@ suggestion_first_steps: {
key: "suggestion_quality_10", key: "suggestion_quality_10",
points: 200, points: 200,
requirements: { count: 10 }, requirements: { count: 10 },
title: "Elite Curator",
tier: AchievementTier.Gold, tier: AchievementTier.Gold,
}, title: "Elite Curator",
suggestion_enthusiast: {
description: "Submit 5 suggestions in one day",
category: AchievementCategory.Suggestion,
key: "suggestion_enthusiast",
icon: "🔥",
title: "Suggestion Enthusiast",
points: 150,
requirements: { count: 5, dayRange: 1 },
tier: AchievementTier.Silver,
},
suggestion_quality_100: {
description: "Have 100 suggestions accepted",
key: "suggestion_quality_100",
category: AchievementCategory.Suggestion,
title: "Ultimate Curator",
icon: "👸",
points: 1500,
tier: AchievementTier.Diamond,
requirements: { count: 100 },
},
like_book_lover: {
description: "Like 50 books",
key: "like_book_lover",
category: AchievementCategory.Like,
title: "Book Lover",
icon: "📚",
points: 100,
tier: AchievementTier.Silver,
requirements: { count: 50 },
},
suggestion_quality_25: {
description: "Have 25 suggestions accepted",
key: "suggestion_quality_25",
category: AchievementCategory.Suggestion,
title: "Master Curator",
icon: "💫",
points: 400,
tier: AchievementTier.Platinum,
requirements: { count: 25 },
}, },
like_cinephile: { comment_essay: {
description: "Like 50 shows/films", category: AchievementCategory.Comment,
key: "like_cinephile", description: "Write 5 comments with 1000+ characters",
category: AchievementCategory.Like, icon: "📄",
title: "Cinephile", key: "comment_essay",
icon: "🎬", points: 300,
points: 100, requirements: { count: 5 },
tier: AchievementTier.Silver, tier: AchievementTier.Gold,
requirements: { count: 50 }, title: "Essay Writer",
},
suggestion_quality_100: {
description: "Have 100 suggestions accepted",
category: AchievementCategory.Suggestion,
key: "suggestion_quality_100",
icon: "👸",
title: "Ultimate Curator",
points: 1500,
requirements: { count: 100 },
tier: AchievementTier.Diamond,
},
comment_diverse: {
category: AchievementCategory.Comment,
description: "Comment on all 6 media types",
icon: "🎨",
key: "comment_diverse",
points: 250,
requirements: { diversity: true },
title: "Well-Rounded Critic",
tier: AchievementTier.Gold,
},
suggestion_quality_25: {
description: "Have 25 suggestions accepted",
category: AchievementCategory.Suggestion,
key: "suggestion_quality_25",
icon: "💫",
title: "Master Curator",
points: 400,
requirements: { count: 25 },
tier: AchievementTier.Platinum,
},
comment_first_to_comment: {
category: AchievementCategory.Comment,
description: "Be the first to comment on 10 items",
icon: "🗨️",
key: "comment_first_to_comment",
points: 200,
requirements: { count: 10 },
tier: AchievementTier.Silver,
title: "Discussion Starter",
},
comment_master: {
category: AchievementCategory.Comment,
description: "Leave 250 comments in the library",
icon: "📖",
key: "comment_master",
points: 500,
title: "Master Reviewer",
requirements: { count: 250 },
tier: AchievementTier.Platinum,
}, },
suggestion_quality_5: { suggestion_quality_5: {
@@ -188,201 +315,6 @@ suggestion_first_steps: {
requirements: { count: 5 }, requirements: { count: 5 },
}, },
like_diverse: {
description: "Like items from all 6 media types",
key: "like_diverse",
category: AchievementCategory.Like,
title: "Diverse Taste",
icon: "🌍",
points: 200,
tier: AchievementTier.Gold,
requirements: { diversity: true },
},
// ========== COMMENT ACHIEVEMENTS (12) ==========
comment_first: {
key: "comment_first",
description: "Leave your first comment in the library",
title: "First Thoughts",
category: AchievementCategory.Comment,
icon: "💬",
tier: AchievementTier.Bronze,
points: 25,
requirements: { count: 1 },
},
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 },
},
comment_critic: {
key: "comment_critic",
description: "Leave 50 comments in the library",
title: "Critic",
category: AchievementCategory.Comment,
icon: "🗣️",
tier: AchievementTier.Silver,
points: 100,
requirements: { count: 50 },
},
suggestion_renaissance: {
description: "Submit accepted suggestions across all 6 media types",
key: "suggestion_renaissance",
category: AchievementCategory.Suggestion,
title: "Renaissance Person",
icon: "🌈",
points: 400,
tier: AchievementTier.Gold,
requirements: { diversity: true },
},
comment_expert: {
description: "Leave 100 comments in the library",
key: "comment_expert",
category: AchievementCategory.Comment,
title: "Expert Critic",
icon: "🎭",
points: 250,
tier: AchievementTier.Gold,
requirements: { count: 100 },
},
// ========== LIKE ACHIEVEMENTS (12) ==========
like_first: {
description: "Like your first item in the library",
key: "like_first",
category: AchievementCategory.Like,
title: "First Like",
icon: "❤️",
points: 25,
tier: AchievementTier.Bronze,
requirements: { count: 1 },
},
comment_legend: {
description: "Leave 500 comments in the library",
key: "comment_legend",
category: AchievementCategory.Comment,
title: "Review Legend",
icon: "🏅",
points: 1000,
tier: AchievementTier.Diamond,
requirements: { count: 500 },
},
like_mega_fan: {
description: "Like 500 items in the library",
key: "like_mega_fan",
category: AchievementCategory.Like,
title: "Mega Fan",
icon: "💗",
points: 500,
tier: AchievementTier.Platinum,
requirements: { count: 500 },
},
comment_detailed: {
description: "Write 10 comments with 500+ characters",
category: AchievementCategory.Comment,
key: "comment_detailed",
icon: "📝",
title: "Detailed Critic",
points: 150,
requirements: { count: 10 },
tier: AchievementTier.Silver,
},
like_super_fan: {
key: "like_super_fan",
description: "Like 250 items in the library",
title: "Super Fan",
category: AchievementCategory.Like,
icon: "💝",
tier: AchievementTier.Gold,
points: 250,
requirements: { count: 250 },
},
comment_essay: {
category: AchievementCategory.Comment,
description: "Write 5 comments with 1000+ characters",
icon: "📄",
key: "comment_essay",
points: 300,
title: "Essay Writer",
requirements: { count: 5 },
tier: AchievementTier.Gold,
},
like_legendary: {
key: "like_legendary",
description: "Like 1000 items in the library",
title: "Legendary Fan",
category: AchievementCategory.Like,
icon: "💞",
tier: AchievementTier.Diamond,
points: 1000,
requirements: { count: 1000 },
},
comment_diverse: {
description: "Comment on all 6 media types",
category: AchievementCategory.Comment,
key: "comment_diverse",
icon: "🎨",
title: "Well-Rounded Critic",
points: 250,
requirements: { diversity: true },
tier: AchievementTier.Gold,
},
like_gamer: {
key: "like_gamer",
description: "Like 50 games",
title: "Gamer",
category: AchievementCategory.Like,
icon: "🎮",
tier: AchievementTier.Silver,
points: 100,
requirements: { count: 50 },
},
comment_first_to_comment: {
category: AchievementCategory.Comment,
description: "Be the first to comment on 10 items",
icon: "🗨️",
key: "comment_first_to_comment",
points: 200,
title: "Discussion Starter",
requirements: { count: 10 },
tier: AchievementTier.Silver,
},
like_music: {
key: "like_music",
description: "Like 50 music albums",
title: "Music Enthusiast",
category: AchievementCategory.Like,
icon: "🎵",
tier: AchievementTier.Silver,
points: 100,
requirements: { count: 50 },
},
comment_master: {
description: "Leave 250 comments in the library",
category: AchievementCategory.Comment,
key: "comment_master",
icon: "📖",
title: "Master Reviewer",
points: 500,
requirements: { count: 250 },
tier: AchievementTier.Platinum,
},
comment_novel: { comment_novel: {
category: AchievementCategory.Comment, category: AchievementCategory.Comment,
description: "Write 3 comments with 2000+ characters", description: "Write 3 comments with 2000+ characters",
@@ -391,28 +323,42 @@ like_first: {
points: 500, points: 500,
requirements: { count: 3 }, requirements: { count: 3 },
tier: AchievementTier.Platinum, tier: AchievementTier.Platinum,
title: "Novel Writer", title: "Novel Writer",
}, },
like_binge: {
key: "like_binge", suggestion_quality_50: {
title: "Binge Liker", key: "suggestion_quality_50",
description: "Like 20+ items in one day", description: "Have 50 suggestions accepted",
category: AchievementCategory.Like, title: "Grand Master",
tier: AchievementTier.Silver, category: AchievementCategory.Suggestion,
icon: "", icon: "🎖️",
points: 150, tier: AchievementTier.Platinum,
requirements: { count: 20, dayRange: 1 }, points: 700,
requirements: { count: 50 },
}, },
comment_reviewer: { comment_reviewer: {
key: "comment_reviewer", description: "Leave 10 comments in the library",
description: "Leave 10 comments in the library",
title: "Reviewer",
category: AchievementCategory.Comment, category: AchievementCategory.Comment,
icon: "✍️", key: "comment_reviewer",
tier: AchievementTier.Bronze, icon: "✍️",
title: "Reviewer",
points: 50, points: 50,
requirements: { count: 10 }, requirements: { count: 10 },
tier: AchievementTier.Bronze,
}, },
suggestion_renaissance: {
description: "Submit accepted suggestions across all 6 media types",
category: AchievementCategory.Suggestion,
key: "suggestion_renaissance",
icon: "🌈",
title: "Renaissance Person",
points: 400,
requirements: { diversity: true },
tier: AchievementTier.Gold,
},
comment_thoughtful: { comment_thoughtful: {
category: AchievementCategory.Comment, category: AchievementCategory.Comment,
description: "Comment on 50 different items", description: "Comment on 50 different items",
@@ -425,36 +371,86 @@ like_first: {
}, },
engagement_early_adopter: { engagement_early_adopter: {
description: "Be among the first 10 users to join",
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
key: "engagement_early_adopter", description: "Be among the first 10 users to join",
icon: "🌟", icon: "🌟",
title: "Early Adopter", key: "engagement_early_adopter",
points: 1000, points: 1000,
requirements: {}, requirements: {},
tier: AchievementTier.Diamond, tier: AchievementTier.Diamond,
title: "Early Adopter",
}, },
// ========== LIKE ACHIEVEMENTS (12) ==========
engagement_power_user: { like_first: {
description: "Achieve Triple Threat 10 times", description: "Like your first item in the library",
category: AchievementCategory.Engagement, key: "like_first",
key: "engagement_power_user", category: AchievementCategory.Like,
icon: "💫", title: "First Like",
title: "Power User", icon: "❤️",
points: 750, points: 25,
requirements: { count: 10 }, tier: AchievementTier.Bronze,
tier: AchievementTier.Platinum, requirements: { count: 1 },
}, },
engagement_founding_100: { engagement_founding_100: {
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
description: "Be among the first 100 users to join", description: "Be among the first 100 users to join",
icon: "🎖️", icon: "🎖️",
key: "engagement_founding_100", key: "engagement_founding_100",
points: 500, points: 500,
title: "Founding Member",
requirements: {}, requirements: {},
tier: AchievementTier.Gold, tier: AchievementTier.Gold,
title: "Founding Member",
},
like_mega_fan: {
description: "Like 500 items in the library",
key: "like_mega_fan",
category: AchievementCategory.Like,
title: "Mega Fan",
icon: "💗",
points: 500,
tier: AchievementTier.Platinum,
requirements: { count: 500 },
},
engagement_founding_1000: {
category: AchievementCategory.Engagement,
description: "Be among the first 1000 users to join",
icon: "🚀",
key: "engagement_founding_1000",
points: 200,
requirements: {},
tier: AchievementTier.Silver,
title: "Pioneer",
},
like_super_fan: {
key: "like_super_fan",
description: "Like 250 items in the library",
title: "Super Fan",
category: AchievementCategory.Like,
icon: "💝",
tier: AchievementTier.Gold,
points: 250,
requirements: { count: 250 },
},
engagement_power_user: {
category: AchievementCategory.Engagement,
description: "Achieve Triple Threat 10 times",
icon: "💫",
key: "engagement_power_user",
points: 750,
requirements: { count: 10 },
tier: AchievementTier.Platinum,
title: "Power User",
},
like_legendary: {
key: "like_legendary",
description: "Like 1000 items in the library",
title: "Legendary Fan",
category: AchievementCategory.Like,
icon: "💞",
tier: AchievementTier.Diamond,
points: 1000,
requirements: { count: 1000 },
}, },
engagement_streak_100: { engagement_streak_100: {
@@ -463,31 +459,42 @@ like_first: {
icon: "🏆", icon: "🏆",
key: "engagement_streak_100", key: "engagement_streak_100",
points: 500, points: 500,
title: "Committed",
requirements: { streak: 100 }, requirements: { streak: 100 },
tier: AchievementTier.Gold, tier: AchievementTier.Gold,
title: "Committed",
}, },
engagement_founding_1000: { like_gamer: {
category: AchievementCategory.Engagement, key: "like_gamer",
description: "Be among the first 1000 users to join", description: "Like 50 games",
icon: "🚀", title: "Gamer",
key: "engagement_founding_1000", category: AchievementCategory.Like,
points: 200, icon: "🎮",
requirements: {}, tier: AchievementTier.Silver,
title: "Pioneer", points: 100,
tier: AchievementTier.Silver, requirements: { count: 50 },
}, },
engagement_streak_30: { engagement_streak_30: {
description: "Login for 30 days in a row",
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
key: "engagement_streak_30", description: "Login for 30 days in a row",
icon: "💪", icon: "💪",
title: "Dedicated", key: "engagement_streak_30",
points: 250, points: 250,
requirements: { streak: 30 }, requirements: { streak: 30 },
tier: AchievementTier.Silver,
title: "Dedicated",
},
like_music: {
key: "like_music",
description: "Like 50 music albums",
title: "Music Enthusiast",
category: AchievementCategory.Like,
icon: "🎵",
tier: AchievementTier.Silver, tier: AchievementTier.Silver,
points: 100,
requirements: { count: 50 },
}, },
engagement_streak_365: { engagement_streak_365: {
@@ -496,20 +503,31 @@ like_first: {
icon: "⚡", icon: "⚡",
key: "engagement_streak_365", key: "engagement_streak_365",
points: 2000, points: 2000,
title: "Unstoppable",
requirements: { streak: 365 }, requirements: { streak: 365 },
tier: AchievementTier.Diamond, tier: AchievementTier.Diamond,
title: "Unstoppable",
}, },
engagement_streak_7: { engagement_streak_7: {
description: "Login for 7 days in a row",
key: "engagement_streak_7",
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
title: "Daily Visitor", description: "Login for 7 days in a row",
icon: "🔥", icon: "🔥",
key: "engagement_streak_7",
points: 100, points: 100,
tier: AchievementTier.Bronze,
requirements: { streak: 7 }, requirements: { streak: 7 },
tier: AchievementTier.Bronze,
title: "Daily Visitor",
},
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 },
}, },
engagement_triple_threat: { engagement_triple_threat: {
@@ -519,11 +537,11 @@ like_first: {
key: "engagement_triple_threat", key: "engagement_triple_threat",
points: 200, points: 200,
requirements: { dayRange: 1 }, requirements: { dayRange: 1 },
title: "Triple Threat",
tier: AchievementTier.Silver, tier: AchievementTier.Silver,
title: "Triple Threat",
}, },
engagement_veteran_180: { engagement_veteran_180: {
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
description: "Have an account for 6 months", description: "Have an account for 6 months",
icon: "📅", icon: "📅",
@@ -533,17 +551,7 @@ engagement_veteran_180: {
tier: AchievementTier.Silver, tier: AchievementTier.Silver,
title: "Seasoned", title: "Seasoned",
}, },
// ========== 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_veteran_1825: { engagement_veteran_1825: {
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
description: "Have an account for 5 years", description: "Have an account for 5 years",
@@ -554,6 +562,7 @@ engagement_welcome: {
tier: AchievementTier.Diamond, tier: AchievementTier.Diamond,
title: "Legend", title: "Legend",
}, },
engagement_veteran_30: { engagement_veteran_30: {
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
description: "Have an account for 30 days", description: "Have an account for 30 days",
@@ -564,6 +573,7 @@ engagement_welcome: {
tier: AchievementTier.Bronze, tier: AchievementTier.Bronze,
title: "Veteran", title: "Veteran",
}, },
engagement_veteran_365: { engagement_veteran_365: {
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
description: "Have an account for 1 year", description: "Have an account for 1 year",
@@ -574,6 +584,7 @@ engagement_welcome: {
tier: AchievementTier.Gold, tier: AchievementTier.Gold,
title: "Longstanding", title: "Longstanding",
}, },
engagement_veteran_730: { engagement_veteran_730: {
category: AchievementCategory.Engagement, category: AchievementCategory.Engagement,
description: "Have an account for 2 years", description: "Have an account for 2 years",
@@ -584,6 +595,17 @@ engagement_welcome: {
tier: AchievementTier.Platinum, tier: AchievementTier.Platinum,
title: "Elder", title: "Elder",
}, },
// ========== ENGAGEMENT ACHIEVEMENTS (15) ==========
engagement_welcome: {
category: AchievementCategory.Engagement,
description: "Complete your profile setup",
icon: "👋",
key: "engagement_welcome",
points: 25,
requirements: {},
tier: AchievementTier.Bronze,
title: "Welcome!",
},
report_accuracy_80: { report_accuracy_80: {
category: AchievementCategory.Report, category: AchievementCategory.Report,
@@ -593,7 +615,7 @@ engagement_welcome: {
points: 300, points: 300,
requirements: { count: 10, rate: 0.8 }, requirements: { count: 10, rate: 0.8 },
tier: AchievementTier.Gold, tier: AchievementTier.Gold,
title: "Sharp Eye", title: "Sharp Eye",
}, },
report_accuracy_90: { report_accuracy_90: {
@@ -604,7 +626,7 @@ engagement_welcome: {
points: 500, points: 500,
requirements: { count: 10, rate: 0.9 }, requirements: { count: 10, rate: 0.9 },
tier: AchievementTier.Platinum, tier: AchievementTier.Platinum,
title: "Eagle Eye", title: "Eagle Eye",
}, },
report_consistent: { report_consistent: {
@@ -615,7 +637,7 @@ engagement_welcome: {
points: 200, points: 200,
requirements: { count: 4 }, requirements: { count: 4 },
tier: AchievementTier.Silver, tier: AchievementTier.Silver,
title: "Consistent Guardian", title: "Consistent Guardian",
}, },
report_guardian: { report_guardian: {
@@ -626,7 +648,7 @@ engagement_welcome: {
points: 100, points: 100,
requirements: { count: 5 }, requirements: { count: 5 },
tier: AchievementTier.Silver, tier: AchievementTier.Silver,
title: "Guardian", title: "Guardian",
}, },
report_protector: { report_protector: {
@@ -637,7 +659,7 @@ engagement_welcome: {
points: 250, points: 250,
requirements: { count: 10 }, requirements: { count: 10 },
tier: AchievementTier.Gold, tier: AchievementTier.Gold,
title: "Protector", title: "Protector",
}, },
report_vigilant: { report_vigilant: {
@@ -648,7 +670,7 @@ engagement_welcome: {
points: 500, points: 500,
requirements: { count: 25 }, requirements: { count: 25 },
tier: AchievementTier.Platinum, tier: AchievementTier.Platinum,
title: "Vigilant Protector", title: "Vigilant Protector",
}, },
report_volume: { report_volume: {
@@ -663,14 +685,14 @@ engagement_welcome: {
}, },
// ========== REPORT ACHIEVEMENTS (8) ========== // ========== REPORT ACHIEVEMENTS (8) ==========
report_watchful: { report_watchful: {
description: "Submit your first valid report (ACTION_TAKEN)",
category: AchievementCategory.Report, category: AchievementCategory.Report,
key: "report_watchful", description: "Submit your first valid report (ACTION_TAKEN)",
icon: "👀", icon: "👀",
title: "Watchful Eye", key: "report_watchful",
points: 50, points: 50,
requirements: { count: 1 }, requirements: { count: 1 },
tier: AchievementTier.Bronze, tier: AchievementTier.Bronze,
title: "Watchful Eye",
}, },
}; };
@@ -3,6 +3,8 @@
* @license Naomi's Public License * @license Naomi's Public License
* @author Naomi Carrigan * @author Naomi Carrigan
*/ */
/* eslint-disable @typescript-eslint/naming-convention -- Enum members use PascalCase for display values */
/* eslint-disable import/group-exports -- Multiple exports for better organization */
export enum AchievementCategory { export enum AchievementCategory {
Suggestion = "SUGGESTION", Suggestion = "SUGGESTION",
+8
View File
@@ -1,3 +1,11 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable @typescript-eslint/naming-convention -- Enum members use UPPER_CASE for database values */
/* eslint-disable import/group-exports -- Multiple exports for better organization */
export enum ReportReason { export enum ReportReason {
INAPPROPRIATE_CONTENT = "INAPPROPRIATE_CONTENT", INAPPROPRIATE_CONTENT = "INAPPROPRIATE_CONTENT",
HARASSMENT = "HARASSMENT", HARASSMENT = "HARASSMENT",