diff --git a/convex/lib/public.test.ts b/convex/lib/public.test.ts index 7c5fe9050..52b519cdb 100644 --- a/convex/lib/public.test.ts +++ b/convex/lib/public.test.ts @@ -65,4 +65,32 @@ describe('public skill mapping', () => { comments: 0, }) }) + + it('returns skill when moderationStatus is active', () => { + const skill = makeSkill({ moderationStatus: 'active' }) + expect(toPublicSkill(skill)).not.toBeNull() + }) + + it('filters out skill when moderationStatus is hidden', () => { + const skill = makeSkill({ moderationStatus: 'hidden' }) + expect(toPublicSkill(skill)).toBeNull() + }) + + it('returns skill when moderationStatus is undefined (legacy)', () => { + const skill = makeSkill({ moderationStatus: undefined as unknown as string }) + expect(toPublicSkill(skill)).not.toBeNull() + }) + + it('filters out soft-deleted skills', () => { + const skill = makeSkill({ softDeletedAt: Date.now() }) + expect(toPublicSkill(skill)).toBeNull() + }) + + it('filters out skills with blocked.malware flag', () => { + const skill = makeSkill({ + moderationStatus: 'active', + moderationFlags: ['blocked.malware'], + }) + expect(toPublicSkill(skill)).toBeNull() + }) }) diff --git a/convex/vt.ts b/convex/vt.ts index 2838bcb87..5440e2969 100644 --- a/convex/vt.ts +++ b/convex/vt.ts @@ -305,7 +305,21 @@ export const scanWithVirusTotal = internalAction({ handler: async (ctx, args) => { const apiKey = process.env.VT_API_KEY if (!apiKey) { - console.log('VT_API_KEY not configured, skipping scan') + console.log('VT_API_KEY not configured, skipping scan — activating skill') + // Activate the skill so it appears in search despite no VT scan. + const version = await ctx.runQuery(internal.skills.getVersionByIdInternal, { + versionId: args.versionId, + }) + if (version) { + const skill = await ctx.runQuery(internal.skills.getSkillByIdInternal, { + skillId: version.skillId, + }) + if (skill?.moderationReason !== 'quality.low') { + await ctx.runMutation(internal.skills.setSkillModerationStatusActiveInternal, { + skillId: version.skillId, + }) + } + } return } @@ -524,6 +538,14 @@ export const pollPendingScans = internalAction({ versionId, vtAnalysis: { status: 'stale', checkedAt: Date.now() }, }) + // Activate the skill so it appears in search — absence of a VT + // verdict should not permanently hide a published skill. + const skill = await ctx.runQuery(internal.skills.getSkillByIdInternal, { skillId }) + if (skill?.moderationReason !== 'quality.low') { + await ctx.runMutation(internal.skills.setSkillModerationStatusActiveInternal, { + skillId, + }) + } staled++ } continue @@ -549,6 +571,14 @@ export const pollPendingScans = internalAction({ versionId, vtAnalysis: { status: 'stale', checkedAt: Date.now() }, }) + // Activate the skill so it appears in search — absence of a VT + // verdict should not permanently hide a published skill. + const skill = await ctx.runQuery(internal.skills.getSkillByIdInternal, { skillId }) + if (skill?.moderationReason !== 'quality.low') { + await ctx.runMutation(internal.skills.setSkillModerationStatusActiveInternal, { + skillId, + }) + } staled++ } continue