refactor: standardize string quotes and improve code readability across multiple files

- Updated string quotes from single to double in useRecommendationProfile.ts, client.ts, recommendationService.ts, and recommendations.ts for consistency.
- Enhanced error handling and logging in API client and recommendation service.
- Improved code structure and formatting for better readability and maintainability.
- Ensured consistent use of semicolons and spacing throughout the codebase.
This commit is contained in:
ersaayan
2025-11-22 16:21:15 +03:00
parent 73f9248e98
commit bcf8652728
10 changed files with 1129 additions and 1054 deletions
+120 -110
View File
@@ -1,16 +1,16 @@
import { PackageService } from './packageService'
import { Package } from '@/models/Package'
import {
RecommendationRequest,
RecommendedPackage,
import { PackageService } from "./packageService";
import { Package } from "@/models/Package";
import {
RecommendationRequest,
RecommendedPackage,
UserCategory,
ExperienceLevel
} from '@/types/recommendations'
import {
getPresetPackageNames,
getPresetPriority,
getRecommendationReason
} from '@/data/recommendationPresets'
ExperienceLevel,
} from "@/types/recommendations";
import {
getPresetPackageNames,
getPresetPriority,
getRecommendationReason,
} from "@/data/recommendationPresets";
/**
* Recommendation scoring weights
@@ -19,8 +19,8 @@ const SCORING_WEIGHTS = {
CATEGORY_MATCH: 0.4,
POPULARITY: 0.3,
OS_COMPATIBILITY: 0.2,
PRESET_BOOST: 0.1
}
PRESET_BOOST: 0.1,
};
export class RecommendationService {
/**
@@ -29,40 +29,48 @@ export class RecommendationService {
static async generateRecommendations(
request: RecommendationRequest
): Promise<RecommendedPackage[]> {
const { platform_id, categories, experienceLevel, limit = 20 } = request
const { platform_id, categories, experienceLevel, limit = 20 } = request;
// Step 1: Get preset package names for the user's categories and platform
const presetPackageNames = getPresetPackageNames(categories, platform_id)
const presetPackageNames = getPresetPackageNames(categories, platform_id);
// Step 2: Fetch packages from database
// First, get preset packages
const presetPackages = await this.fetchPresetPackages(
presetPackageNames,
presetPackageNames,
platform_id
)
);
// Then, get additional packages from categories
const categoryPackages = await this.fetchCategoryPackages(
categories,
platform_id,
categories,
platform_id,
limit * 2 // Fetch more to ensure we have enough after filtering
)
);
// Step 3: Combine and deduplicate
const allPackages = this.deduplicatePackages([
...presetPackages,
...categoryPackages
])
...categoryPackages,
]);
// Step 4: Score and rank packages
const scoredPackages = allPackages.map(pkg =>
this.scorePackage(pkg, categories, platform_id, presetPackageNames, experienceLevel)
)
const scoredPackages = allPackages.map((pkg) =>
this.scorePackage(
pkg,
categories,
platform_id,
presetPackageNames,
experienceLevel
)
);
// Step 5: Sort by score and limit results
scoredPackages.sort((a, b) => b.recommendationScore - a.recommendationScore)
scoredPackages.sort(
(a, b) => b.recommendationScore - a.recommendationScore
);
return scoredPackages.slice(0, limit)
return scoredPackages.slice(0, limit);
}
/**
@@ -73,32 +81,32 @@ export class RecommendationService {
platformId: string
): Promise<Package[]> {
if (packageNames.length === 0) {
return []
return [];
}
try {
// Fetch packages by exact name match
const packages: Package[] = []
const packages: Package[] = [];
for (const name of packageNames) {
const result = await PackageService.getMany({
platform_id: platformId,
search: name,
limit: 1,
sort_by: 'popularity_score',
sort_order: 'desc'
})
sort_by: "popularity_score",
sort_order: "desc",
});
// Only add if exact match
if (result.packages.length > 0 && result.packages[0].name === name) {
packages.push(result.packages[0])
packages.push(result.packages[0]);
}
}
return packages
return packages;
} catch (error) {
console.error('Error fetching preset packages:', error)
return []
console.error("Error fetching preset packages:", error);
return [];
}
}
@@ -113,38 +121,38 @@ export class RecommendationService {
try {
// Map user categories to database categories
const categoryMap: Record<UserCategory, string[]> = {
'development': ['Development', 'Internet'],
'design': ['Graphics', 'Multimedia'],
'multimedia': ['Multimedia', 'Graphics'],
'system-tools': ['System', 'Utilities'],
'gaming': ['Games'],
'productivity': ['Office', 'Utilities'],
'education': ['Science', 'Education']
}
development: ["Development", "Internet"],
design: ["Graphics", "Multimedia"],
multimedia: ["Multimedia", "Graphics"],
"system-tools": ["System", "Utilities"],
gaming: ["Games"],
productivity: ["Office", "Utilities"],
education: ["Science", "Education"],
};
// Get all matching packages
const packages: Package[] = []
const packages: Package[] = [];
for (const category of categories) {
const dbCategories = categoryMap[category] || []
const dbCategories = categoryMap[category] || [];
// Note: Since we don't have category filtering in current API,
// we'll fetch by popularity and filter client-side
// This is a limitation of current schema - categories are not well-utilized
const result = await PackageService.getMany({
platform_id: platformId,
limit: Math.ceil(limit / categories.length),
sort_by: 'popularity_score',
sort_order: 'desc'
})
packages.push(...result.packages)
sort_by: "popularity_score",
sort_order: "desc",
});
packages.push(...result.packages);
}
return packages
return packages;
} catch (error) {
console.error('Error fetching category packages:', error)
return []
console.error("Error fetching category packages:", error);
return [];
}
}
@@ -152,14 +160,14 @@ export class RecommendationService {
* Remove duplicate packages (by ID)
*/
private static deduplicatePackages(packages: Package[]): Package[] {
const seen = new Set<string>()
return packages.filter(pkg => {
const seen = new Set<string>();
return packages.filter((pkg) => {
if (seen.has(pkg.id)) {
return false
return false;
}
seen.add(pkg.id)
return true
})
seen.add(pkg.id);
return true;
});
}
/**
@@ -172,65 +180,67 @@ export class RecommendationService {
presetPackageNames: string[],
experienceLevel?: ExperienceLevel
): RecommendedPackage {
let score = 0
let reason = ''
const isPresetMatch = presetPackageNames.includes(pkg.name)
let score = 0;
let reason = "";
const isPresetMatch = presetPackageNames.includes(pkg.name);
// 1. Category Match Score (40%)
// For preset packages, this is always high
const categoryScore = isPresetMatch ? 1.0 : 0.5
score += categoryScore * SCORING_WEIGHTS.CATEGORY_MATCH
const categoryScore = isPresetMatch ? 1.0 : 0.5;
score += categoryScore * SCORING_WEIGHTS.CATEGORY_MATCH;
// 2. Popularity Score (30%)
// Normalize popularity_score (0-100) to 0-1
const popularityScore = (pkg.popularity_score || 0) / 100
score += popularityScore * SCORING_WEIGHTS.POPULARITY
const popularityScore = (pkg.popularity_score || 0) / 100;
score += popularityScore * SCORING_WEIGHTS.POPULARITY;
// 3. OS Compatibility Score (20%)
// All packages from DB should be compatible, so this is always 1.0
const osScore = 1.0
score += osScore * SCORING_WEIGHTS.OS_COMPATIBILITY
const osScore = 1.0;
score += osScore * SCORING_WEIGHTS.OS_COMPATIBILITY;
// 4. Preset Boost (10%)
// Extra boost for preset packages based on priority
let presetBoost = 0
let presetBoost = 0;
if (isPresetMatch) {
const priority = getPresetPriority(pkg.name, categories, platformId)
const priority = getPresetPriority(pkg.name, categories, platformId);
if (priority !== null) {
presetBoost = priority / 10 // Normalize 1-10 to 0.1-1.0
presetBoost = priority / 10; // Normalize 1-10 to 0.1-1.0
// Get recommendation reason from preset
const presetReason = getRecommendationReason(pkg.name, categories)
const presetReason = getRecommendationReason(pkg.name, categories);
if (presetReason) {
reason = presetReason
reason = presetReason;
}
}
}
score += presetBoost * SCORING_WEIGHTS.PRESET_BOOST
score += presetBoost * SCORING_WEIGHTS.PRESET_BOOST;
// Default reason if not from preset
if (!reason) {
if (pkg.popularity_score && pkg.popularity_score > 70) {
reason = 'Popular choice in the community'
reason = "Popular choice in the community";
} else {
reason = 'Recommended for your selected categories'
reason = "Recommended for your selected categories";
}
}
// Normalize final score to 0-100
const finalScore = Math.round(score * 100)
const finalScore = Math.round(score * 100);
return {
id: pkg.id,
name: pkg.name,
description: pkg.description || 'No description available',
version: pkg.version || 'latest',
category: typeof pkg.category === 'string' ? pkg.category : pkg.category?.name,
license: typeof pkg.license === 'string' ? pkg.license : pkg.license?.name,
type: pkg.type || 'cli',
description: pkg.description || "No description available",
version: pkg.version || "latest",
category:
typeof pkg.category === "string" ? pkg.category : pkg.category?.name,
license:
typeof pkg.license === "string" ? pkg.license : pkg.license?.name,
type: pkg.type || "cli",
platform: pkg.platform,
platform_id: pkg.platform_id,
repository: pkg.repository || 'official',
repository: pkg.repository || "official",
download_url: pkg.download_url,
lastUpdated: pkg.last_updated ? pkg.last_updated.toString() : undefined,
downloads: pkg.downloads_count,
@@ -239,8 +249,8 @@ export class RecommendationService {
tags: pkg.tags,
recommendationScore: finalScore,
recommendationReason: reason,
presetMatch: isPresetMatch
}
presetMatch: isPresetMatch,
};
}
/**
@@ -253,8 +263,8 @@ export class RecommendationService {
return this.generateRecommendations({
platform_id: platformId,
categories: [primaryCategory],
limit: 5
})
limit: 5,
});
}
/**
@@ -265,30 +275,30 @@ export class RecommendationService {
categories: UserCategory[],
totalLimit: number = 20
): Promise<RecommendedPackage[]> {
const perCategory = Math.ceil(totalLimit / categories.length)
const allRecommendations: RecommendedPackage[] = []
const perCategory = Math.ceil(totalLimit / categories.length);
const allRecommendations: RecommendedPackage[] = [];
for (const category of categories) {
const recommendations = await this.generateRecommendations({
platform_id: platformId,
categories: [category],
limit: perCategory
})
allRecommendations.push(...recommendations)
limit: perCategory,
});
allRecommendations.push(...recommendations);
}
// Deduplicate by ID and re-sort
const seen = new Set<string>()
const deduplicated = allRecommendations.filter(pkg => {
const seen = new Set<string>();
const deduplicated = allRecommendations.filter((pkg) => {
if (seen.has(pkg.id)) {
return false
return false;
}
seen.add(pkg.id)
return true
})
deduplicated.sort((a, b) => b.recommendationScore - a.recommendationScore)
seen.add(pkg.id);
return true;
});
return deduplicated.slice(0, totalLimit)
deduplicated.sort((a, b) => b.recommendationScore - a.recommendationScore);
return deduplicated.slice(0, totalLimit);
}
}