feat(#82): implement Personality Module
- Add Personality model to Prisma schema with FormalityLevel enum - Create migration and seed with 6 default personalities - Implement CRUD API with TDD approach (97.67% coverage) * PersonalitiesService: findAll, findOne, findDefault, create, update, remove * PersonalitiesController: REST endpoints with auth guards * Comprehensive test coverage (21 passing tests) - Add Personality types to shared package - Create frontend components: * PersonalitySelector: dropdown for choosing personality * PersonalityPreview: preview personality style and system prompt * PersonalityForm: create/edit personalities with validation * Settings page: manage personalities with CRUD operations - Integrate with Ollama API: * Support personalityId in chat endpoint * Auto-inject system prompt from personality * Fall back to default personality if not specified - API client for frontend personality management All tests passing with 97.67% backend coverage (exceeds 85% requirement)
This commit is contained in:
201
apps/api/src/knowledge/services/link-sync.service.ts
Normal file
201
apps/api/src/knowledge/services/link-sync.service.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "../../prisma/prisma.service";
|
||||
import { LinkResolutionService } from "./link-resolution.service";
|
||||
import { parseWikiLinks, WikiLink } from "../utils/wiki-link-parser";
|
||||
|
||||
/**
|
||||
* Represents a backlink to a knowledge entry
|
||||
*/
|
||||
export interface Backlink {
|
||||
id: string;
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
linkText: string;
|
||||
displayText: string;
|
||||
positionStart: number;
|
||||
positionEnd: number;
|
||||
resolved: boolean;
|
||||
context: string | null;
|
||||
createdAt: Date;
|
||||
source: {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an unresolved wiki link
|
||||
*/
|
||||
export interface UnresolvedLink {
|
||||
id: string;
|
||||
sourceId: string;
|
||||
targetId: string | null;
|
||||
linkText: string;
|
||||
displayText: string;
|
||||
positionStart: number;
|
||||
positionEnd: number;
|
||||
resolved: boolean;
|
||||
context: string | null;
|
||||
createdAt: Date;
|
||||
source: {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for synchronizing wiki-style links in knowledge entries
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Parse content for wiki links
|
||||
* - Resolve links to knowledge entries
|
||||
* - Store/update link records
|
||||
* - Handle orphaned links
|
||||
*/
|
||||
@Injectable()
|
||||
export class LinkSyncService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly linkResolver: LinkResolutionService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sync links for a knowledge entry
|
||||
* Parses content, resolves links, and updates the database
|
||||
*
|
||||
* @param workspaceId - The workspace scope
|
||||
* @param entryId - The entry being updated
|
||||
* @param content - The markdown content to parse
|
||||
*/
|
||||
async syncLinks(
|
||||
workspaceId: string,
|
||||
entryId: string,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
// Parse wiki links from content
|
||||
const parsedLinks = parseWikiLinks(content);
|
||||
|
||||
// Get existing links for this entry
|
||||
const existingLinks = await this.prisma.knowledgeLink.findMany({
|
||||
where: {
|
||||
sourceId: entryId,
|
||||
},
|
||||
});
|
||||
|
||||
// Resolve all parsed links
|
||||
const linkCreations: Array<{
|
||||
sourceId: string;
|
||||
targetId: string | null;
|
||||
linkText: string;
|
||||
displayText: string;
|
||||
positionStart: number;
|
||||
positionEnd: number;
|
||||
resolved: boolean;
|
||||
}> = [];
|
||||
|
||||
for (const link of parsedLinks) {
|
||||
const targetId = await this.linkResolver.resolveLink(
|
||||
workspaceId,
|
||||
link.target
|
||||
);
|
||||
|
||||
linkCreations.push({
|
||||
sourceId: entryId,
|
||||
targetId: targetId,
|
||||
linkText: link.target,
|
||||
displayText: link.displayText,
|
||||
positionStart: link.start,
|
||||
positionEnd: link.end,
|
||||
resolved: targetId !== null,
|
||||
});
|
||||
}
|
||||
|
||||
// Determine which existing links to keep/delete
|
||||
// We'll use a simple strategy: delete all existing and recreate
|
||||
// (In production, you might want to diff and only update changed links)
|
||||
const existingLinkIds = existingLinks.map((link) => link.id);
|
||||
|
||||
// Delete all existing links and create new ones in a transaction
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// Delete all existing links
|
||||
if (existingLinkIds.length > 0) {
|
||||
await tx.knowledgeLink.deleteMany({
|
||||
where: {
|
||||
sourceId: entryId,
|
||||
id: {
|
||||
in: existingLinkIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Create new links
|
||||
for (const linkData of linkCreations) {
|
||||
await tx.knowledgeLink.create({
|
||||
data: linkData,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all backlinks for an entry
|
||||
* Returns entries that link TO this entry
|
||||
*
|
||||
* @param entryId - The target entry
|
||||
* @returns Array of backlinks with source entry information
|
||||
*/
|
||||
async getBacklinks(entryId: string): Promise<Backlink[]> {
|
||||
const backlinks = await this.prisma.knowledgeLink.findMany({
|
||||
where: {
|
||||
targetId: entryId,
|
||||
resolved: true,
|
||||
},
|
||||
include: {
|
||||
source: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return backlinks as Backlink[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all unresolved links for a workspace
|
||||
* Useful for finding broken links or pages that need to be created
|
||||
*
|
||||
* @param workspaceId - The workspace scope
|
||||
* @returns Array of unresolved links
|
||||
*/
|
||||
async getUnresolvedLinks(workspaceId: string): Promise<UnresolvedLink[]> {
|
||||
const unresolvedLinks = await this.prisma.knowledgeLink.findMany({
|
||||
where: {
|
||||
source: {
|
||||
workspaceId,
|
||||
},
|
||||
resolved: false,
|
||||
},
|
||||
include: {
|
||||
source: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return unresolvedLinks as UnresolvedLink[];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user