feat(knowledge): add link resolution service

- Add resolveLinksFromContent() to parse wiki links from content and resolve them
- Add getBacklinks() to find all entries that link to a target entry
- Import parseWikiLinks from utils for content parsing
- Export new types: ResolvedLink, Backlink
- Add comprehensive tests for new functionality (27 tests total)
This commit is contained in:
Jason Woltje
2026-01-29 19:34:57 -06:00
parent 1cb54b56b0
commit 24768bd664
3 changed files with 285 additions and 1 deletions

View File

@@ -1,5 +1,6 @@
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import { parseWikiLinks, WikiLink } from "../utils/wiki-link-parser";
/**
* Represents a knowledge entry that matches a link target
@@ -9,6 +10,32 @@ export interface ResolvedEntry {
title: string;
}
/**
* Represents a resolved wiki link with entry information
*/
export interface ResolvedLink {
/** The parsed wiki link */
link: WikiLink;
/** The resolved entry ID, or null if not found */
entryId: string | null;
}
/**
* Represents a backlink - an entry that links to a target entry
*/
export interface Backlink {
/** The source entry ID */
sourceId: string;
/** The source entry title */
sourceTitle: string;
/** The source entry slug */
sourceSlug: string;
/** The link text used to reference the target */
linkText: string;
/** The display text shown for the link */
displayText: string;
}
/**
* Service for resolving wiki-style links to knowledge entries
*
@@ -165,4 +192,72 @@ export class LinkResolutionService {
return matches;
}
/**
* Parse wiki links from content and resolve them to knowledge entries
*
* @param content - The markdown content containing wiki links
* @param workspaceId - The workspace scope for resolution
* @returns Array of resolved links with entry IDs (or null if not found)
*/
async resolveLinksFromContent(
content: string,
workspaceId: string
): Promise<ResolvedLink[]> {
// Parse wiki links from content
const parsedLinks = parseWikiLinks(content);
if (parsedLinks.length === 0) {
return [];
}
// Resolve each link
const resolvedLinks: ResolvedLink[] = [];
for (const link of parsedLinks) {
const entryId = await this.resolveLink(workspaceId, link.target);
resolvedLinks.push({
link,
entryId,
});
}
return resolvedLinks;
}
/**
* Get all entries that link TO a specific entry (backlinks)
*
* @param entryId - The target entry ID
* @returns Array of backlinks with source entry information
*/
async getBacklinks(entryId: string): Promise<Backlink[]> {
// Find all links where this entry is the target
const links = await this.prisma.knowledgeLink.findMany({
where: {
targetId: entryId,
resolved: true,
},
include: {
source: {
select: {
id: true,
title: true,
slug: true,
},
},
},
orderBy: {
createdAt: "desc",
},
});
return links.map((link) => ({
sourceId: link.source.id,
sourceTitle: link.source.title,
sourceSlug: link.source.slug,
linkText: link.linkText,
displayText: link.displayText,
}));
}
}