mirror of
https://github.com/yusufipk/RepoHub.git
synced 2026-09-11 18:46:07 +00:00
docs: Add comprehensive package contribution guide and validation tooling
- Add bilingual README with English/Turkish language switcher - Create detailed package contribution guidelines with platform-specific naming conventions - Add validation script for verifying package presets against database - Include best practices and example PR format for contributors - Add tsx dependency for running TypeScript validation scripts - Document validation workflow with manual and automated verification options
This commit is contained in:
@@ -65,14 +65,14 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Validate and set limit with better error message
|
||||
if (body.limit !== undefined && (body.limit < 1 || body.limit > 50)) {
|
||||
if (body.limit !== undefined && (body.limit < 1 || body.limit > 1000)) {
|
||||
return NextResponse.json(
|
||||
{ error: "Limit must be between 1 and 50" },
|
||||
{ error: "Limit must be between 1 and 1000" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const limit =
|
||||
body.limit && body.limit > 0 && body.limit <= 50 ? body.limit : 20;
|
||||
body.limit && body.limit > 0 && body.limit <= 1000 ? body.limit : 50;
|
||||
|
||||
// Generate recommendations
|
||||
const recommendations = await RecommendationService.generateRecommendations(
|
||||
|
||||
@@ -174,12 +174,6 @@ export function OnboardingModal({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{selectedCategories.length > 0 && (
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{t('onboarding.step1.selected', { count: selectedCategories.length })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -30,12 +30,6 @@ export function RecommendationCard({ pkg, isSelected, onToggle }: Recommendation
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground line-clamp-2 mb-3">
|
||||
{pkg.description}
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<Button
|
||||
variant={isSelected ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
|
||||
@@ -26,7 +26,6 @@ export function RecommendationListItem({ pkg, isSelected, onToggle }: Recommenda
|
||||
<h4 className="font-semibold text-sm truncate">{pkg.name}</h4>
|
||||
<span className="text-xs text-muted-foreground">{pkg.version}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground truncate">{pkg.description}</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export function RecommendationsSection({
|
||||
platform_id: getEffectiveOS(),
|
||||
categories: effectiveProfile.categories,
|
||||
experienceLevel: effectiveProfile.experienceLevel,
|
||||
limit: 12
|
||||
limit: 1000
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+470
-124
@@ -10,227 +10,374 @@ import { UserCategory } from "@/types/recommendations";
|
||||
type PlatformId = "windows" | "macos" | "ubuntu" | "debian" | "arch" | "fedora";
|
||||
|
||||
export const PACKAGE_PRESETS: Record<PlatformId, Record<UserCategory, string[]>> = {
|
||||
windows: {
|
||||
development: [
|
||||
"git",
|
||||
"code", // Visual Studio Code
|
||||
"nodejs",
|
||||
"python",
|
||||
"docker-desktop",
|
||||
"postman",
|
||||
"windows": {
|
||||
"development": [
|
||||
"Git.Git",
|
||||
"Microsoft.VisualStudioCode",
|
||||
"Docker.DockerDesktop",
|
||||
"Postman.Postman",
|
||||
"Microsoft.WindowsTerminal",
|
||||
"Microsoft.PowerShell",
|
||||
"Notepad++.Notepad++",
|
||||
"WinSCP.WinSCP",
|
||||
"PuTTY.PuTTY",
|
||||
"WinMerge.WinMerge",
|
||||
"EclipseFoundation.Eclipse",
|
||||
"Anysphere.Cursor"
|
||||
],
|
||||
design: [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
"design": [
|
||||
"GIMP.GIMP",
|
||||
"Inkscape.Inkscape",
|
||||
"BlenderFoundation.Blender",
|
||||
"KDE.Krita",
|
||||
"IrfanSkiljan.IrfanView",
|
||||
"XnSoft.XnViewMP",
|
||||
"FastStone.Viewer",
|
||||
"Greenshot.Greenshot",
|
||||
"ShareX.ShareX"
|
||||
],
|
||||
multimedia: [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs-studio",
|
||||
"multimedia": [
|
||||
"VideoLAN.VLC",
|
||||
"Audacity.Audacity",
|
||||
"OBSProject.OBSStudio",
|
||||
"Apple.iTunes",
|
||||
"AIMP.AIMP",
|
||||
"PeterPawlowski.foobar2000",
|
||||
"Winamp.Winamp",
|
||||
"GOMLab.GOMPlayer",
|
||||
"Spotify.Spotify",
|
||||
"VentisMedia.MediaMonkey",
|
||||
"HandBrake.HandBrake"
|
||||
],
|
||||
"system-tools": [
|
||||
"7zip",
|
||||
"powertoys",
|
||||
"everything",
|
||||
"7zip.7zip",
|
||||
"Microsoft.PowerToys",
|
||||
"voidtools.Everything",
|
||||
"RARLab.WinRAR",
|
||||
"DominikReichl.KeePass",
|
||||
"TeamViewer.TeamViewer",
|
||||
"RealVNC.VNCViewer",
|
||||
"CodeSector.TeraCopy",
|
||||
"LIGHTNINGUK.ImgBurn",
|
||||
"WinDirStat.WinDirStat",
|
||||
"AntibodySoftware.WizTree",
|
||||
"Glarysoft.GlaryUtilities",
|
||||
"ChristianKindahl.InfraRecorder",
|
||||
"Open-Shell.Open-Shell-Menu",
|
||||
"Piriform.CCleaner",
|
||||
"Rufus.Rufus",
|
||||
"BleachBit.BleachBit",
|
||||
"NVAccess.NVDA",
|
||||
"Malwarebytes.Malwarebytes",
|
||||
"SUPERAntiSpyware.SUPERAntiSpyware",
|
||||
"qBittorrent.qBittorrent"
|
||||
],
|
||||
gaming: [
|
||||
"steam",
|
||||
"discord",
|
||||
"gaming": [
|
||||
"Valve.Steam",
|
||||
"Discord.Discord",
|
||||
"EpicGames.EpicGamesLauncher",
|
||||
"GOG.Galaxy"
|
||||
],
|
||||
productivity: [
|
||||
"notion",
|
||||
"obsidian",
|
||||
"slack",
|
||||
],
|
||||
education: [
|
||||
"anki",
|
||||
"productivity": [
|
||||
"Notion.Notion",
|
||||
"Obsidian.Obsidian",
|
||||
"SlackTechnologies.Slack",
|
||||
"Google.Chrome",
|
||||
"Mozilla.Firefox",
|
||||
"Microsoft.Edge",
|
||||
"Brave.Brave",
|
||||
"Opera.Opera",
|
||||
"Zoom.Zoom",
|
||||
"Microsoft.Teams",
|
||||
"Pidgin.Pidgin",
|
||||
"Mozilla.Thunderbird",
|
||||
"Foxit.FoxitReader",
|
||||
"TheDocumentFoundation.LibreOffice",
|
||||
"SumatraPDF.SumatraPDF",
|
||||
"AcroSoftware.CutePDFWriter",
|
||||
"Apache.OpenOffice",
|
||||
"Dropbox.Dropbox",
|
||||
"Microsoft.OneDrive",
|
||||
"Google.EarthPro",
|
||||
"Evernote.Evernote"
|
||||
],
|
||||
"education": [
|
||||
"Anki.Anki"
|
||||
]
|
||||
},
|
||||
|
||||
macos: {
|
||||
development: [
|
||||
"macos": {
|
||||
"development": [
|
||||
"git",
|
||||
"code", // Visual Studio Code
|
||||
"nodejs",
|
||||
"python",
|
||||
"docker",
|
||||
"visual-studio-code",
|
||||
"cursor",
|
||||
"node",
|
||||
"postman",
|
||||
"iterm2",
|
||||
"warp",
|
||||
"sublime-text",
|
||||
"cyberduck",
|
||||
"meld",
|
||||
"dotnet-sdk",
|
||||
"temurin"
|
||||
],
|
||||
design: [
|
||||
"design": [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
"krita",
|
||||
"xnviewmp"
|
||||
],
|
||||
multimedia: [
|
||||
"multimedia": [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs",
|
||||
"spotify",
|
||||
"handbrake",
|
||||
"iina",
|
||||
"foobar2000"
|
||||
],
|
||||
"system-tools": [
|
||||
"rectangle",
|
||||
"the-unarchiver",
|
||||
"keka",
|
||||
"appcleaner",
|
||||
"keepassxc",
|
||||
"teamviewer",
|
||||
"anydesk",
|
||||
"malwarebytes",
|
||||
"raycast",
|
||||
"alfred",
|
||||
"qbittorrent"
|
||||
],
|
||||
gaming: [
|
||||
"gaming": [
|
||||
"steam",
|
||||
"discord",
|
||||
"epic-games"
|
||||
],
|
||||
productivity: [
|
||||
"productivity": [
|
||||
"notion",
|
||||
"obsidian",
|
||||
"slack",
|
||||
"zoom",
|
||||
"microsoft-teams",
|
||||
"thunderbird",
|
||||
"google-chrome",
|
||||
"firefox",
|
||||
"microsoft-edge",
|
||||
"brave-browser",
|
||||
"opera",
|
||||
"libreoffice",
|
||||
"foxitreader",
|
||||
"adobe-acrobat-reader",
|
||||
"dropbox",
|
||||
"google-drive",
|
||||
"onedrive"
|
||||
],
|
||||
education: [
|
||||
"education": [
|
||||
"anki",
|
||||
],
|
||||
"zotero"
|
||||
]
|
||||
},
|
||||
|
||||
ubuntu: {
|
||||
development: [
|
||||
"ubuntu": {
|
||||
"development": [
|
||||
"git",
|
||||
"code", // Visual Studio Code
|
||||
"nodejs",
|
||||
"python3",
|
||||
"docker.io",
|
||||
"curl",
|
||||
"wget",
|
||||
"nodejs",
|
||||
"npm",
|
||||
"python3-pip",
|
||||
"docker.io",
|
||||
"dotnet-sdk-8.0"
|
||||
],
|
||||
design: [
|
||||
"design": [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
"krita",
|
||||
"darktable"
|
||||
],
|
||||
multimedia: [
|
||||
"multimedia": [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs-studio",
|
||||
"ffmpeg",
|
||||
"mpv",
|
||||
"handbrake",
|
||||
"kdenlive"
|
||||
],
|
||||
"system-tools": [
|
||||
"neofetch",
|
||||
"timeshift",
|
||||
"stacer",
|
||||
"keepassxc",
|
||||
"synaptic"
|
||||
],
|
||||
"gaming": [
|
||||
"steam",
|
||||
"lutris",
|
||||
"mangohud"
|
||||
],
|
||||
"productivity": [
|
||||
"libreoffice",
|
||||
"chromium-browser",
|
||||
"evolution",
|
||||
"focuswriter"
|
||||
],
|
||||
"education": [
|
||||
"anki"
|
||||
]
|
||||
},
|
||||
"debian": {
|
||||
"development": [
|
||||
"git",
|
||||
"build-essential",
|
||||
"curl",
|
||||
"wget",
|
||||
"nodejs",
|
||||
"npm",
|
||||
"python3",
|
||||
"python3-pip",
|
||||
"docker.io"
|
||||
],
|
||||
"design": [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
"krita"
|
||||
],
|
||||
"multimedia": [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs-studio",
|
||||
"ffmpeg",
|
||||
"handbrake"
|
||||
],
|
||||
"system-tools": [
|
||||
"htop",
|
||||
"neofetch",
|
||||
"tldr",
|
||||
"fastfetch",
|
||||
"tmux",
|
||||
"zsh",
|
||||
"gparted",
|
||||
"timeshift",
|
||||
"keepassxc"
|
||||
],
|
||||
gaming: [
|
||||
"gaming": [
|
||||
"steam",
|
||||
"discord",
|
||||
"lutris",
|
||||
"gamemode",
|
||||
"mangohud"
|
||||
],
|
||||
productivity: [
|
||||
"productivity": [
|
||||
"libreoffice",
|
||||
"thunderbird",
|
||||
"firefox-esr",
|
||||
"chromium"
|
||||
],
|
||||
education: [
|
||||
"anki",
|
||||
],
|
||||
"education": [
|
||||
]
|
||||
},
|
||||
|
||||
debian: {
|
||||
development: [
|
||||
"arch": {
|
||||
"development": [
|
||||
"git",
|
||||
"base-devel",
|
||||
"code",
|
||||
"nodejs",
|
||||
"python3",
|
||||
"docker.io",
|
||||
"curl",
|
||||
"npm",
|
||||
"python-pip",
|
||||
"jdk17-openjdk"
|
||||
],
|
||||
design: [
|
||||
"design": [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
"krita"
|
||||
],
|
||||
multimedia: [
|
||||
"multimedia": [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs-studio",
|
||||
"ffmpeg",
|
||||
"mpv",
|
||||
"handbrake"
|
||||
],
|
||||
"system-tools": [
|
||||
"htop",
|
||||
"neofetch",
|
||||
"fastfetch",
|
||||
"tldr",
|
||||
"tmux",
|
||||
"zsh",
|
||||
"gparted",
|
||||
"timeshift",
|
||||
"keepassxc",
|
||||
"reflector",
|
||||
"pacman-contrib"
|
||||
],
|
||||
gaming: [
|
||||
"gaming": [
|
||||
"steam",
|
||||
"lutris",
|
||||
"gamemode",
|
||||
"mangohud",
|
||||
"discord",
|
||||
"wine",
|
||||
"winetricks"
|
||||
],
|
||||
productivity: [
|
||||
"libreoffice",
|
||||
"thunderbird",
|
||||
],
|
||||
education: [
|
||||
"anki",
|
||||
],
|
||||
},
|
||||
|
||||
arch: {
|
||||
development: [
|
||||
"git",
|
||||
"visual-studio-code-bin",
|
||||
"nodejs",
|
||||
"python",
|
||||
"docker",
|
||||
"postman-bin",
|
||||
],
|
||||
design: [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
],
|
||||
multimedia: [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs-studio",
|
||||
],
|
||||
"system-tools": [
|
||||
"htop",
|
||||
"neofetch",
|
||||
"tldr",
|
||||
],
|
||||
gaming: [
|
||||
"steam",
|
||||
"discord",
|
||||
],
|
||||
productivity: [
|
||||
"productivity": [
|
||||
"libreoffice-fresh",
|
||||
"thunderbird",
|
||||
"firefox",
|
||||
"chromium",
|
||||
"obsidian"
|
||||
],
|
||||
education: [
|
||||
"anki",
|
||||
],
|
||||
"education": [
|
||||
"anki"
|
||||
]
|
||||
},
|
||||
|
||||
fedora: {
|
||||
development: [
|
||||
"fedora": {
|
||||
"development": [
|
||||
"git",
|
||||
"code",
|
||||
"curl",
|
||||
"nodejs",
|
||||
"python3",
|
||||
"docker",
|
||||
"curl",
|
||||
"python3-pip",
|
||||
"java-17-openjdk-devel",
|
||||
"dotnet-sdk-8.0"
|
||||
],
|
||||
design: [
|
||||
"design": [
|
||||
"gimp",
|
||||
"inkscape",
|
||||
"blender",
|
||||
"krita"
|
||||
],
|
||||
multimedia: [
|
||||
"multimedia": [
|
||||
"vlc",
|
||||
"audacity",
|
||||
"obs-studio",
|
||||
"mpv"
|
||||
],
|
||||
"system-tools": [
|
||||
"htop",
|
||||
"neofetch",
|
||||
"fastfetch",
|
||||
"tldr",
|
||||
"tmux",
|
||||
"zsh",
|
||||
"gparted",
|
||||
"keepassxc",
|
||||
"dnf-plugins-core"
|
||||
],
|
||||
gaming: [
|
||||
"steam",
|
||||
"discord",
|
||||
"gaming": [
|
||||
"lutris",
|
||||
"gamemode",
|
||||
"mangohud"
|
||||
],
|
||||
productivity: [
|
||||
"productivity": [
|
||||
"libreoffice",
|
||||
"thunderbird",
|
||||
"firefox",
|
||||
"chromium"
|
||||
],
|
||||
education: [
|
||||
"anki",
|
||||
],
|
||||
"education": []
|
||||
},
|
||||
};
|
||||
|
||||
@@ -252,3 +399,202 @@ export function getPackagesForPlatform(
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package names with their categories for a specific platform
|
||||
*/
|
||||
export function getPackagesWithCategories(
|
||||
platformId: string,
|
||||
categories: UserCategory[]
|
||||
): { name: string; category: UserCategory }[] {
|
||||
const platform = PACKAGE_PRESETS[platformId as PlatformId];
|
||||
if (!platform) return [];
|
||||
|
||||
const packages: { name: string; category: UserCategory }[] = [];
|
||||
categories.forEach((category) => {
|
||||
const categoryPackages = platform[category] || [];
|
||||
categoryPackages.forEach((name) => {
|
||||
packages.push({ name, category });
|
||||
});
|
||||
});
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
export const PRESET_DESCRIPTIONS: Record<string, string> = {
|
||||
// Development
|
||||
"git": "Distributed version control system",
|
||||
"Git.Git": "Distributed version control system",
|
||||
"curl": "Command line tool for transferring data with URLs",
|
||||
"wget": "Network utility to retrieve files from the Web",
|
||||
"nodejs": "JavaScript runtime built on Chrome's V8 JavaScript engine",
|
||||
"npm": "Package manager for the Node.js JavaScript platform",
|
||||
"python3": "Interpreted, interactive, object-oriented programming language",
|
||||
"python3-pip": "Python package installer",
|
||||
"python-pip": "Python package installer",
|
||||
"docker.io": "Linux container runtime",
|
||||
"Docker.DockerDesktop": "Build, Share, and Run container applications",
|
||||
"dotnet-sdk-8.0": ".NET 8.0 Software Development Kit",
|
||||
"dotnet-sdk": ".NET Software Development Kit",
|
||||
"Microsoft.VisualStudioCode": "Code editing. Redefined.",
|
||||
"visual-studio-code": "Code editing. Redefined.",
|
||||
"code": "The Open Source build of Visual Studio Code",
|
||||
"Postman.Postman": "Platform for building and using APIs",
|
||||
"postman": "Platform for building and using APIs",
|
||||
"Microsoft.WindowsTerminal": "Modern terminal application for Windows",
|
||||
"iterm2": "Terminal emulator for macOS",
|
||||
"warp": "AI-powered terminal",
|
||||
"sublime-text": "Sophisticated text editor for code, markup and prose",
|
||||
"build-essential": "Informational list of build-essential packages",
|
||||
"base-devel": "Basic tools to build Arch Linux packages",
|
||||
"java-17-openjdk-devel": "OpenJDK 17 Development Kit",
|
||||
"jdk17-openjdk": "OpenJDK 17 Development Kit",
|
||||
"temurin": "Eclipse Temurin Java SE binaries",
|
||||
|
||||
// Design
|
||||
"gimp": "GNU Image Manipulation Program",
|
||||
"GIMP.GIMP": "GNU Image Manipulation Program",
|
||||
"inkscape": "Vector-based drawing program",
|
||||
"Inkscape.Inkscape": "Vector-based drawing program",
|
||||
"blender": "Very fast and versatile 3D modeller/renderer",
|
||||
"BlenderFoundation.Blender": "Very fast and versatile 3D modeller/renderer",
|
||||
"krita": "Digital painting and sketching application",
|
||||
"KDE.Krita": "Digital painting and sketching application",
|
||||
"darktable": "Virtual lighttable and darkroom for photographers",
|
||||
"xnviewmp": "Image viewer, browser and converter",
|
||||
"XnSoft.XnViewMP": "Image viewer, browser and converter",
|
||||
"IrfanSkiljan.IrfanView": "Fast and compact image viewer",
|
||||
"FastStone.Viewer": "Image viewer, converter and editor",
|
||||
"ShareX.ShareX": "Screen capture, file sharing and productivity tool",
|
||||
"Greenshot.Greenshot": "Lightweight screenshot software tool",
|
||||
|
||||
// Multimedia
|
||||
"vlc": "Multimedia player and streamer",
|
||||
"VideoLAN.VLC": "Multimedia player and streamer",
|
||||
"audacity": "Multi-track audio editor and recorder",
|
||||
"Audacity.Audacity": "Multi-track audio editor and recorder",
|
||||
"obs-studio": "Software for live streaming and screen recording",
|
||||
"obs": "Software for live streaming and screen recording",
|
||||
"OBSProject.OBSStudio": "Software for live streaming and screen recording",
|
||||
"ffmpeg": "Tools for transcoding, streaming and playing of multimedia files",
|
||||
"mpv": "Video player based on MPlayer/mplayer2",
|
||||
"handbrake": "Open Source Video Transcoder",
|
||||
"HandBrake.HandBrake": "Open Source Video Transcoder",
|
||||
"kdenlive": "Non-linear video editor",
|
||||
"spotify": "Music streaming service",
|
||||
"Spotify.Spotify": "Music streaming service",
|
||||
"Apple.iTunes": "Media player, media library, and mobile device management utility",
|
||||
"foobar2000": "Advanced audio player",
|
||||
"PeterPawlowski.foobar2000": "Advanced audio player",
|
||||
"Winamp.Winamp": "Media player for Windows",
|
||||
"AIMP.AIMP": "Free audio player",
|
||||
"iina": "The modern video player for macOS",
|
||||
|
||||
// System Tools
|
||||
"htop": "Interactive process viewer",
|
||||
"fastfetch": "Like neofetch, but much faster",
|
||||
"neofetch": "Command-line system information tool",
|
||||
"tmux": "Terminal multiplexer",
|
||||
"zsh": "Shell with lots of features",
|
||||
"gparted": "GNOME Partition Editor",
|
||||
"timeshift": "System restore utility",
|
||||
"stacer": "Linux System Optimizer and Monitoring",
|
||||
"keepassxc": "Cross Platform Password Manager",
|
||||
"DominikReichl.KeePass": "Password manager",
|
||||
"synaptic": "Graphical package manager",
|
||||
"7zip.7zip": "File archiver with a high compression ratio",
|
||||
"Microsoft.PowerToys": "Set of system utilities for power users",
|
||||
"voidtools.Everything": "Locate files and folders by name instantly",
|
||||
"RARLab.WinRAR": "Powerful archiver and archive manager",
|
||||
"TeamViewer.TeamViewer": "Remote control and meeting software",
|
||||
"teamviewer": "Remote control and meeting software",
|
||||
"RealVNC.VNCViewer": "Remote control software",
|
||||
"rufus": "Create bootable USB drives the easy way",
|
||||
"Rufus.Rufus": "Create bootable USB drives the easy way",
|
||||
"bleachbit": "Delete unnecessary files from the system",
|
||||
"BleachBit.BleachBit": "Delete unnecessary files from the system",
|
||||
"rectangle": "Move and resize windows in macOS using keyboard shortcuts",
|
||||
"the-unarchiver": "Unpack any archive file",
|
||||
"keka": "The macOS file archiver",
|
||||
"appcleaner": "Uninstall unwanted apps",
|
||||
"raycast": "Productivity tool that replaces Spotlight",
|
||||
"alfred": "Productivity app for macOS",
|
||||
"qbittorrent": "BitTorrent client",
|
||||
"qBittorrent.qBittorrent": "BitTorrent client",
|
||||
|
||||
// Gaming
|
||||
"steam": "Digital distribution platform for video games",
|
||||
"Valve.Steam": "Digital distribution platform for video games",
|
||||
"lutris": "Open Source gaming platform for Linux",
|
||||
"gamemode": "Optimize Linux system performance for gaming",
|
||||
"mangohud": "Vulkan and OpenGL overlay for monitoring FPS, temperatures, CPU/GPU load",
|
||||
"discord": "All-in-one voice and text chat for gamers",
|
||||
"Discord.Discord": "All-in-one voice and text chat for gamers",
|
||||
"wine": "Run Windows applications on Linux",
|
||||
"winetricks": "Workarounds for problems in Wine",
|
||||
"EpicGames.EpicGamesLauncher": "Epic Games Store",
|
||||
"epic-games": "Epic Games Store",
|
||||
"GOG.Galaxy": "GOG Galaxy Client",
|
||||
|
||||
// Productivity
|
||||
"libreoffice": "Office productivity suite",
|
||||
"TheDocumentFoundation.LibreOffice": "Office productivity suite",
|
||||
"libreoffice-fresh": "Office productivity suite (fresh version)",
|
||||
"thunderbird": "Email, newsgroup and chat client",
|
||||
"Mozilla.Thunderbird": "Email, newsgroup and chat client",
|
||||
"firefox": "Mozilla Firefox web browser",
|
||||
"Mozilla.Firefox": "Mozilla Firefox web browser",
|
||||
"chromium": "Web browser",
|
||||
"chromium-browser": "Web browser",
|
||||
"Google.Chrome": "Web browser",
|
||||
"google-chrome": "Web browser",
|
||||
"Microsoft.Edge": "Web browser",
|
||||
"microsoft-edge": "Web browser",
|
||||
"Brave.Brave": "Secure, fast, and private web browser",
|
||||
"brave-browser": "Secure, fast, and private web browser",
|
||||
"Opera.Opera": "Web browser",
|
||||
"opera": "Web browser",
|
||||
"zoom": "Video conferencing",
|
||||
"Zoom.Zoom": "Video conferencing",
|
||||
"microsoft-teams": "Communication and collaboration platform",
|
||||
"Microsoft.Teams": "Communication and collaboration platform",
|
||||
"slack": "Collaboration hub for work",
|
||||
"SlackTechnologies.Slack": "Collaboration hub for work",
|
||||
"notion": "All-in-one workspace",
|
||||
"Notion.Notion": "All-in-one workspace",
|
||||
"obsidian": "Knowledge base that works on local Markdown files",
|
||||
"Obsidian.Obsidian": "Knowledge base that works on local Markdown files",
|
||||
"foxitreader": "PDF Reader",
|
||||
"Foxit.FoxitReader": "PDF Reader",
|
||||
"adobe-acrobat-reader": "PDF Reader",
|
||||
"dropbox": "File hosting service",
|
||||
"Dropbox.Dropbox": "File hosting service",
|
||||
"onedrive": "File hosting service",
|
||||
"Microsoft.OneDrive": "File hosting service",
|
||||
"google-drive": "File hosting service",
|
||||
"evernote": "Note taking app",
|
||||
"Evernote.Evernote": "Note taking app",
|
||||
"evolution": "Groupware suite",
|
||||
"focuswriter": "Distraction-free word processor",
|
||||
|
||||
// Education
|
||||
"anki": "Powerful, intelligent flash cards",
|
||||
"Anki.Anki": "Powerful, intelligent flash cards",
|
||||
"zotero": "Your personal research assistant"
|
||||
};
|
||||
|
||||
export function getPresetDetails(name: string): { description: string } {
|
||||
// Try exact match
|
||||
if (PRESET_DESCRIPTIONS[name]) {
|
||||
return { description: PRESET_DESCRIPTIONS[name] };
|
||||
}
|
||||
|
||||
// Try case insensitive
|
||||
const lowerName = name.toLowerCase();
|
||||
const key = Object.keys(PRESET_DESCRIPTIONS).find(k => k.toLowerCase() === lowerName);
|
||||
if (key) {
|
||||
return { description: PRESET_DESCRIPTIONS[key] };
|
||||
}
|
||||
|
||||
return { description: "Recommended package" };
|
||||
}
|
||||
|
||||
+10
-2
@@ -16,8 +16,16 @@ export async function middleware(request: NextRequest) {
|
||||
request.ip ||
|
||||
'CACHE_TOKEN'
|
||||
|
||||
// 50 requests per minute per IP
|
||||
await limiter.check(null, 50, ip)
|
||||
// Exempt localhost from rate limiting
|
||||
const isLocalhost = ip === '127.0.0.1' ||
|
||||
ip === '::1' ||
|
||||
ip === 'localhost' ||
|
||||
ip === 'CACHE_TOKEN'
|
||||
|
||||
if (!isLocalhost) {
|
||||
// 50 requests per minute per IP
|
||||
await limiter.check(null, 50, ip)
|
||||
}
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: 'Too Many Requests' },
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { PackageService } from "./packageService";
|
||||
import { Package } from "@/models/Package";
|
||||
import {
|
||||
RecommendationRequest,
|
||||
RecommendedPackage,
|
||||
UserCategory,
|
||||
ExperienceLevel,
|
||||
} from "@/types/recommendations";
|
||||
import { getPackagesForPlatform } from "@/data/recommendationPresets";
|
||||
|
||||
|
||||
import { getPackagesWithCategories, getPresetDetails } from "@/data/recommendationPresets";
|
||||
|
||||
export class RecommendationService {
|
||||
/**
|
||||
@@ -17,19 +13,19 @@ export class RecommendationService {
|
||||
static async generateRecommendations(
|
||||
request: RecommendationRequest
|
||||
): Promise<RecommendedPackage[]> {
|
||||
const { platform_id, categories, experienceLevel, limit = 20 } = request;
|
||||
const { platform_id, categories, limit = 20 } = request;
|
||||
|
||||
// Step 1: Get preset package names for the user's categories and platform
|
||||
const presetPackageNames = getPackagesForPlatform(platform_id, categories);
|
||||
const presetPackagesInfo = getPackagesWithCategories(platform_id, categories);
|
||||
const presetPackageNames = presetPackagesInfo.map(p => p.name);
|
||||
|
||||
// Step 2: Fetch packages from database
|
||||
// Step 2: Generate packages from presets (no DB query)
|
||||
const packageCategoryMap = new Map<string, UserCategory>();
|
||||
|
||||
// Fetch preset packages
|
||||
const presetPackages = await this.fetchPresetPackages(
|
||||
presetPackageNames,
|
||||
presetPackagesInfo,
|
||||
platform_id,
|
||||
categories,
|
||||
packageCategoryMap
|
||||
);
|
||||
|
||||
@@ -55,54 +51,49 @@ export class RecommendationService {
|
||||
|
||||
/**
|
||||
* Fetch packages that match preset names
|
||||
* optimized to use static data instead of DB queries
|
||||
*/
|
||||
private static async fetchPresetPackages(
|
||||
packageNames: string[],
|
||||
packagesInfo: { name: string; category: UserCategory }[],
|
||||
platformId: string,
|
||||
categories: UserCategory[],
|
||||
categoryMap: Map<string, UserCategory>
|
||||
): Promise<Package[]> {
|
||||
if (packageNames.length === 0) {
|
||||
if (packagesInfo.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const packages: Package[] = [];
|
||||
let categoryIndex = 0;
|
||||
const packages: Package[] = [];
|
||||
|
||||
// Search for each package name (case-insensitive)
|
||||
for (const name of packageNames) {
|
||||
const result = await PackageService.getMany({
|
||||
platform_id: platformId,
|
||||
search: name,
|
||||
limit: 5, // Get top 5 matches to handle variations
|
||||
sort_by: "popularity_score",
|
||||
sort_order: "desc",
|
||||
});
|
||||
|
||||
// Find best match (case-insensitive, exact name preferred)
|
||||
const exactMatch = result.packages.find(
|
||||
(pkg) => pkg.name.toLowerCase() === name.toLowerCase()
|
||||
);
|
||||
|
||||
if (exactMatch) {
|
||||
packages.push(exactMatch);
|
||||
// Distribute categories evenly
|
||||
categoryMap.set(exactMatch.id, categories[categoryIndex % categories.length]);
|
||||
categoryIndex++;
|
||||
} else if (result.packages.length > 0) {
|
||||
// If no exact match, take the first result (most popular match)
|
||||
packages.push(result.packages[0]);
|
||||
categoryMap.set(result.packages[0].id, categories[categoryIndex % categories.length]);
|
||||
categoryIndex++;
|
||||
for (const { name, category } of packagesInfo) {
|
||||
const { description } = getPresetDetails(name);
|
||||
|
||||
// Create a mock package object to avoid database queries
|
||||
// This ensures instant loading for recommendations
|
||||
const mockPackage: Package = {
|
||||
id: `${platformId}:${name.toLowerCase()}`,
|
||||
name: name,
|
||||
description: description,
|
||||
version: "latest",
|
||||
platform_id: platformId,
|
||||
type: "cli",
|
||||
repository: "official",
|
||||
popularity_score: 100,
|
||||
is_active: true,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
downloads_count: 10000,
|
||||
platform: {
|
||||
id: platformId,
|
||||
name: platformId.charAt(0).toUpperCase() + platformId.slice(1),
|
||||
package_manager: "unknown"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return packages;
|
||||
} catch (error) {
|
||||
console.error("Error fetching preset packages:", error);
|
||||
return [];
|
||||
packages.push(mockPackage);
|
||||
categoryMap.set(mockPackage.id, category);
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user