sync: claim removal fix, auto-crawl email, progress bar, search optimization
- Fix claim removal request flow - Add email notification on auto-crawl skill discovery - Add progress bar component - Optimize Meilisearch sync and search indexing - Improve skill parser and queries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,8 @@ export default async function ClaimPage({
|
||||
success: {
|
||||
title: t('success.title'),
|
||||
description: t('success.description'),
|
||||
pendingTitle: t('success.pendingTitle'),
|
||||
pendingDescription: t('success.pendingDescription'),
|
||||
viewRequests: t('success.viewRequests'),
|
||||
},
|
||||
addSuccess: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { locales, localeDirection, type Locale } from '@/i18n';
|
||||
import { Providers } from '../providers';
|
||||
import { Suspense } from 'react';
|
||||
import { QueryNotification } from '@/components/QueryNotification';
|
||||
import { ProgressBar } from '@/components/ProgressBar';
|
||||
import '../globals.css';
|
||||
|
||||
export async function generateMetadata({
|
||||
@@ -70,6 +71,7 @@ export default async function LocaleLayout({
|
||||
<Suspense fallback={null}>
|
||||
<QueryNotification />
|
||||
</Suspense>
|
||||
<ProgressBar />
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</Providers>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { createDb, addRequestQueries, userQueries } from '@skillhub/db';
|
||||
import { createDb, addRequestQueries, discoveredRepoQueries, userQueries } from '@skillhub/db';
|
||||
import { sanitizeReason } from '@/lib/sanitize';
|
||||
import { sendClaimSubmittedEmail } from '@/lib/email';
|
||||
|
||||
@@ -389,6 +389,26 @@ export async function POST(request: NextRequest) {
|
||||
hasSkillMd: validation.hasSkillMd,
|
||||
});
|
||||
|
||||
// Auto-approve and queue for crawling if SKILL.md found
|
||||
if (validation.hasSkillMd && validation.skillPaths.length > 0) {
|
||||
await addRequestQueries.updateStatus(db, requestId, {
|
||||
status: 'approved',
|
||||
});
|
||||
|
||||
// Queue repo for indexer crawling via discovered_repos
|
||||
try {
|
||||
await discoveredRepoQueries.upsert(db, {
|
||||
id: `${parsed.owner}/${parsed.repo}`,
|
||||
owner: parsed.owner,
|
||||
repo: parsed.repo,
|
||||
discoveredVia: 'add-request',
|
||||
githubStars: 0,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[Claim] Failed to queue repo for crawling:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Build appropriate response message
|
||||
let message: string;
|
||||
if (validation.skillPaths.length > 1) {
|
||||
|
||||
@@ -124,6 +124,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Check repository ownership using public API (no token needed for public repos)
|
||||
let isOwner = false;
|
||||
let repoDeleted = false;
|
||||
let githubError: string | null = null;
|
||||
|
||||
try {
|
||||
@@ -144,7 +145,7 @@ export async function POST(request: NextRequest) {
|
||||
isOwner = true;
|
||||
}
|
||||
} else if (repoResponse.status === 404) {
|
||||
githubError = 'Repository not found on GitHub';
|
||||
repoDeleted = true;
|
||||
} else if (repoResponse.status === 403) {
|
||||
githubError = 'GitHub API rate limit exceeded';
|
||||
}
|
||||
@@ -160,23 +161,34 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!isOwner) {
|
||||
if (!isOwner && !repoDeleted) {
|
||||
return NextResponse.json(
|
||||
{ error: 'You are not the owner of this repository', code: 'NOT_OWNER' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Create the removal request (auto-approved since owner is verified)
|
||||
// Create the removal request
|
||||
const sanitizedReason = reason ? sanitizeReason(reason) : 'No reason provided';
|
||||
const requestId = await removalRequestQueries.create(db, {
|
||||
userId: dbUser.id,
|
||||
skillId,
|
||||
reason: sanitizedReason,
|
||||
verifiedOwner: true,
|
||||
verifiedOwner: !repoDeleted,
|
||||
});
|
||||
|
||||
// Auto-approve: Block the skill from being re-indexed
|
||||
if (repoDeleted) {
|
||||
// Repo not found — submit for admin review instead of auto-approving
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
requestId,
|
||||
pending: true,
|
||||
blocked: false,
|
||||
message: 'Repository not found on GitHub. Your removal request has been submitted for admin review.',
|
||||
});
|
||||
}
|
||||
|
||||
// Owner verified — auto-approve: Block the skill from being re-indexed
|
||||
await skillQueries.block(db, skillId);
|
||||
|
||||
// Update the request status to approved
|
||||
|
||||
@@ -55,6 +55,8 @@ interface ClaimFormProps {
|
||||
success: {
|
||||
title: string;
|
||||
description: string;
|
||||
pendingTitle: string;
|
||||
pendingDescription: string;
|
||||
viewRequests: string;
|
||||
};
|
||||
addSuccess: {
|
||||
@@ -161,6 +163,7 @@ export function ClaimForm({ translations }: ClaimFormProps) {
|
||||
const [isSubmittingRemove, setIsSubmittingRemove] = useState(false);
|
||||
const [removeError, setRemoveError] = useState('');
|
||||
const [removeSuccess, setRemoveSuccess] = useState(false);
|
||||
const [removePending, setRemovePending] = useState(false);
|
||||
const [removalRequests, setRemovalRequests] = useState<RemovalRequest[]>([]);
|
||||
const [loadingRemovalRequests, setLoadingRemovalRequests] = useState(false);
|
||||
|
||||
@@ -267,6 +270,7 @@ export function ClaimForm({ translations }: ClaimFormProps) {
|
||||
}
|
||||
|
||||
setRemoveSuccess(true);
|
||||
setRemovePending(data.pending || false);
|
||||
setSkillId('');
|
||||
setRemoveReason('');
|
||||
fetchRemovalRequests();
|
||||
@@ -438,11 +442,13 @@ export function ClaimForm({ translations }: ClaimFormProps) {
|
||||
<div className="card p-8 text-center">
|
||||
<CheckCircle className="w-12 h-12 mx-auto mb-4 text-success" />
|
||||
<h2 className="text-xl font-semibold text-text-primary mb-2">
|
||||
{translations.success.title}
|
||||
{removePending ? translations.success.pendingTitle : translations.success.title}
|
||||
</h2>
|
||||
<p className="text-text-secondary mb-6">{translations.success.description}</p>
|
||||
<p className="text-text-secondary mb-6">
|
||||
{removePending ? translations.success.pendingDescription : translations.success.description}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setRemoveSuccess(false)}
|
||||
onClick={() => { setRemoveSuccess(false); setRemovePending(false); }}
|
||||
className="btn-secondary"
|
||||
>
|
||||
{translations.success.viewRequests}
|
||||
|
||||
14
apps/web/components/ProgressBar.tsx
Normal file
14
apps/web/components/ProgressBar.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { AppProgressBar } from 'next-nprogress-bar';
|
||||
|
||||
export function ProgressBar() {
|
||||
return (
|
||||
<AppProgressBar
|
||||
height="3px"
|
||||
color="#0284c7"
|
||||
options={{ showSpinner: false }}
|
||||
shallowRouting
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -719,6 +719,8 @@
|
||||
"success": {
|
||||
"title": "Skill Blocked",
|
||||
"description": "Your skill has been blocked from indexing. It will no longer appear in SkillHub.",
|
||||
"pendingTitle": "Request Submitted",
|
||||
"pendingDescription": "The repository was not found on GitHub. Your removal request has been submitted for admin review.",
|
||||
"viewRequests": "View My Requests"
|
||||
},
|
||||
"addSuccess": {
|
||||
|
||||
@@ -719,6 +719,8 @@
|
||||
"success": {
|
||||
"title": "مهارت مسدود شد",
|
||||
"description": "مهارت شما از ایندکس شدن مسدود شد. دیگر در SkillHub نمایش داده نخواهد شد.",
|
||||
"pendingTitle": "درخواست ثبت شد",
|
||||
"pendingDescription": "مخزن در GitHub یافت نشد. درخواست حذف شما برای بررسی ادمین ثبت شد.",
|
||||
"viewRequests": "مشاهده درخواستهای من"
|
||||
},
|
||||
"addSuccess": {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"next": "^15.0.0",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"next-intl": "^3.4.0",
|
||||
"next-nprogress-bar": "^2.4.7",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
Reference in New Issue
Block a user