Files
OmniRoute/src/app/api/skills/route.ts
Oğuzhan Sert 8dc93ade6d fix(i18n): Turkish locale-aware search and sorting (#3115)
* feat(i18n): add Turkish locale-aware text helpers (search/sort)

* test(i18n): cover null/whitespace edges + document compareTr/normalize contract

* fix(i18n): route dashboard search through Turkish-safe matchesSearch

* fix(i18n): sort user-visible lists with Turkish collation (compareTr)

* fix(i18n): keep providerId tiebreaker as ASCII sort (technical id)

* docs(i18n): document intentional lang=en in global-error boundary

* chore(lint): guard against locale-unsafe toLowerCase().includes search

* fix(i18n): migrate missed provider-name search + harden lint disable placement

* fix(i18n): downgrade no-restricted-syntax to warn (incremental adoption)

The rule errored on ~19 pre-existing toLowerCase().includes() call-sites in
src/app accumulated since this PR's base. Keep it as a warning so the guard-rail
guides future code without breaking the 0-errors lint gate (project policy:
0 errors, warnings tolerated).

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
2026-06-03 18:58:01 -03:00

63 lines
2.4 KiB
TypeScript

import { NextResponse } from "next/server";
import { skillRegistry } from "@/lib/skills/registry";
import { parsePaginationParams, buildPaginatedResponse } from "@/shared/types/pagination";
import { getSkillsProviderSetting } from "@/lib/skills/providerSettings";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { matchesSearch } from "@/shared/utils/turkishText";
const POPULAR_BY_PROVIDER = {
skillsmp: ["web-search", "file-reader", "sql-assistant", "devops-helper", "docs-assistant"],
skillssh: ["git", "terminal", "postgres", "kubernetes", "playwright"],
} as const;
export async function GET(request?: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
await skillRegistry.loadFromDatabase();
const provider = await getSkillsProviderSetting();
const url = request?.url || "http://localhost/api/skills";
const parsedUrl = new URL(url);
const query = parsedUrl.searchParams.get("q")?.trim() || "";
const modeFilter = parsedUrl.searchParams.get("mode");
const sourceFilter = parsedUrl.searchParams.get("source");
let allSkills = skillRegistry.list();
if (query) {
allSkills = allSkills.filter((skill) => {
const tagsText = Array.isArray(skill.tags) ? skill.tags.join(" ") : "";
return (
matchesSearch(skill.name, query) ||
matchesSearch(skill.description, query) ||
matchesSearch(tagsText, query)
);
});
}
if (modeFilter === "on" || modeFilter === "off" || modeFilter === "auto") {
allSkills = allSkills.filter(
(skill) => (skill.mode || (skill.enabled ? "on" : "off")) === modeFilter
);
}
if (sourceFilter === "skillsmp" || sourceFilter === "skillssh" || sourceFilter === "local") {
allSkills = allSkills.filter((skill) => (skill.sourceProvider || "local") === sourceFilter);
}
const params = parsePaginationParams(parsedUrl.searchParams);
const paged = allSkills.slice((params.page - 1) * params.limit, params.page * params.limit);
const response = buildPaginatedResponse(paged, allSkills.length, params);
return NextResponse.json({
...response,
skills: response.data,
provider,
popularDefaults: POPULAR_BY_PROVIDER[provider],
});
} catch (err: unknown) {
const error = err instanceof Error ? err.message : String(err);
return NextResponse.json({ error }, { status: 500 });
}
}