fix: include official owners in skill browse (#2856)

This commit is contained in:
Vincent Koc
2026-06-25 13:24:07 +08:00
committed by GitHub
parent 9797825433
commit 843cf78c74
3 changed files with 98 additions and 11 deletions
+61 -1
View File
@@ -265,6 +265,43 @@ describe("public skill list deterministic cursors", () => {
expect(getPageMock).not.toHaveBeenCalled();
});
it("includes official publisher status on browse owners", async () => {
const digest = makeSearchDigest({
ownerPublisherId: "publishers:openclaw",
ownerHandle: "openclaw",
ownerKind: "org",
ownerName: undefined,
ownerDisplayName: "OpenClaw",
});
getPageMock.mockResolvedValueOnce({
page: [digest],
hasMore: false,
indexKeys: [[undefined, digest.updatedAt, digest._id]],
});
const result = await listPublicPageV4Handler(
{
db: {
query: vi.fn((table: string) => {
if (table !== "officialPublishers") {
throw new Error(`Unexpected table: ${table}`);
}
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue({ publisherId: "publishers:openclaw" }),
})),
};
}),
},
} as never,
{ sort: "updated", numItems: 10 },
);
expect((result.page as Array<{ owner?: { official?: boolean } }>)[0]?.owner?.official).toBe(
true,
);
});
it("uses the topic digest index for topic-filtered recommended browse", async () => {
const digest = makeSearchDigest({
skillId: "skills:calendar",
@@ -1486,6 +1523,13 @@ describe("skills.listRelatedByCategory", () => {
return { order };
});
const query = vi.fn((table: string) => {
if (table === "officialPublishers") {
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue(null),
})),
};
}
if (table !== "skillSearchDigest") throw new Error(`Unexpected query table: ${table}`);
return { withIndex };
});
@@ -1555,6 +1599,13 @@ describe("skills.listRelatedByCategory", () => {
return { order };
});
const query = vi.fn((table: string) => {
if (table === "officialPublishers") {
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue(null),
})),
};
}
if (table !== "skillSearchDigest") throw new Error(`Unexpected query table: ${table}`);
return { withIndex };
});
@@ -1584,7 +1635,16 @@ describe("skills.listRelatedByCategory", () => {
builder({ eq });
return { order };
});
const query = vi.fn(() => ({ withIndex }));
const query = vi.fn((table: string) => {
if (table === "officialPublishers") {
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue(null),
})),
};
}
return { withIndex };
});
const result = await listRelatedByCategoryHandler({ db: { query } } as never, {
skillId: "skills:current",
+30 -10
View File
@@ -79,6 +79,7 @@ import {
summarizeReasonCodes,
verdictFromCodes,
} from "./lib/moderationReasonCodes";
import { hasOfficialPublisherRow, toPublicPublisherWithOfficial } from "./lib/officialPublishers";
import {
type HydratableSkill,
type PublicPublisher,
@@ -2008,14 +2009,15 @@ async function buildPublicSkillEntries(
ownerPublisherId,
ownerUserId,
}).then((ownerDoc) => {
const publicOwner = toPublicPublisher(ownerDoc);
if (!publicOwner) {
return { ownerHandle: null, owner: null };
}
return {
ownerHandle: publicOwner.handle ?? String(publicOwner._id),
owner: publicOwner,
};
return toPublicPublisherWithOfficial(ctx, ownerDoc).then((publicOwner) => {
if (!publicOwner) {
return { ownerHandle: null, owner: null };
}
return {
ownerHandle: publicOwner.handle ?? String(publicOwner._id),
owner: publicOwner,
};
});
});
ownerInfoCache.set(cacheKey, ownerPromise);
return ownerPromise;
@@ -5106,7 +5108,11 @@ export const listPublicPageV3 = query({
if (!hydratable) continue;
const publicSkill = toPublicSkill(hydratable);
if (!publicSkill) continue;
const ownerInfo = digestToOwnerInfo(digest);
const ownerInfo = await addOfficialStatusToOwnerInfo(
ctx,
digestToOwnerInfo(digest),
digest.ownerPublisherId,
);
if (!ownerInfo?.owner) continue;
const latestVersion = await resolveDigestLatestVersionForSkill(ctx, digest);
items.push({
@@ -5937,7 +5943,11 @@ async function buildPublicSkillEntryFromDigest(
const hydratable = digestToHydratableSkill(digest);
const publicSkill = toPublicSkill(hydratable);
if (!publicSkill) return null;
const ownerInfo = digestToOwnerInfo(digest);
const ownerInfo = await addOfficialStatusToOwnerInfo(
ctx,
digestToOwnerInfo(digest),
digest.ownerPublisherId,
);
if (!ownerInfo?.owner) return null;
const latestVersion = await resolveDigestLatestVersionForSkill(ctx, digest);
return {
@@ -5948,6 +5958,16 @@ async function buildPublicSkillEntryFromDigest(
};
}
async function addOfficialStatusToOwnerInfo(
ctx: Pick<QueryCtx, "db">,
ownerInfo: { ownerHandle: string | null; owner: PublicPublisher | null } | null,
publisherId?: Id<"publishers">,
) {
if (!ctx?.db || !ownerInfo?.owner || !publisherId) return ownerInfo;
const official = await hasOfficialPublisherRow(ctx, publisherId);
return official ? { ...ownerInfo, owner: { ...ownerInfo.owner, official: true } } : ownerInfo;
}
async function loadPublicLatestVersionForDigest(
ctx: Pick<QueryCtx, "db">,
digest: Pick<Doc<"skillSearchDigest">, "skillId" | "latestVersionId" | "latestVersionSkillId">,
+7
View File
@@ -738,6 +738,13 @@ describe("public skill version queries", () => {
const ctx = {
db: {
query: vi.fn((table: string) => {
if (table === "officialPublishers") {
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue(null),
})),
};
}
if (table !== "skillBadges") throw new Error(`Unexpected table ${table}`);
return {
withIndex: vi.fn(() => ({