article.repository.ts•6.91 kB
/**
* Article repository implementation
*/
import {
Article,
ArticleCreateInput,
ArticleOrderByWithRelationInput
} from '../models/prisma.types';
import { prisma } from '../utils/db';
import {
ISearchableRepository,
SearchOptions,
SearchResult
} from './base.repository';
import { logger } from '../utils/logger';
/**
* Repository for article data access
*/
export class ArticleRepository implements ISearchableRepository<Article, number> {
/**
* Find article by ID
* @param id Article ID
* @returns Article or null if not found
*/
async findById(id: number): Promise<Article | null> {
try {
return await prisma.article.findUnique({
where: { id }
});
} catch (error) {
logger.error('Error finding article by ID:', error);
throw error;
}
}
/**
* Find all articles
* @returns Array of articles
*/
async findAll(): Promise<Article[]> {
try {
return await prisma.article.findMany();
} catch (error) {
logger.error('Error finding all articles:', error);
throw error;
}
}
/**
* Create a new article
* @param data Article data
* @returns Created article
*/
async create(data: Omit<Article, 'id' | 'createdAt' | 'updatedAt'>): Promise<Article> {
try {
return await prisma.article.create({
data: data as ArticleCreateInput
});
} catch (error) {
logger.error('Error creating article:', error);
throw error;
}
}
/**
* Update an article
* @param id Article ID
* @param data Updated article data
* @returns Updated article
*/
async update(id: number, data: Partial<Article>): Promise<Article> {
try {
return await prisma.article.update({
where: { id },
data
});
} catch (error) {
logger.error('Error updating article:', error);
throw error;
}
}
/**
* Delete an article
* @param id Article ID
* @returns True if deleted, false otherwise
*/
async delete(id: number): Promise<boolean> {
try {
await prisma.article.delete({
where: { id }
});
return true;
} catch (error) {
logger.error('Error deleting article:', error);
return false;
}
}
/**
* Search articles with filtering, sorting, and pagination
* @param options Search options
* @returns Search results with pagination metadata
*/
async search(options: SearchOptions): Promise<SearchResult<Article>> {
const { skip = 0, take = 10, orderBy = { publishedAt: 'desc' }, where = {} } = options;
const page = Math.floor(skip / take) + 1;
try {
// Get total count for pagination
const total = await prisma.article.count({ where });
// Get results with pagination
const items = await prisma.article.findMany({
skip,
take,
where,
orderBy: orderBy as ArticleOrderByWithRelationInput
});
const totalPages = Math.ceil(total / take);
return {
items,
total,
page,
limit: take,
totalPages,
hasNext: page < totalPages,
hasPrevious: page > 1
};
} catch (error) {
logger.error('Error searching articles:', error);
throw error;
}
}
/**
* Find article by UUID
* @param uuid Article UUID
* @returns Article or null if not found
*/
async findByUuid(uuid: string): Promise<Article | null> {
try {
return await prisma.article.findUnique({
where: { uuid }
});
} catch (error) {
logger.error('Error finding article by UUID:', error);
throw error;
}
}
/**
* Get article with relationships (topics, entities)
* @param id Article ID
* @returns Article with relationships or null if not found
*/
async getArticleWithRelations(id: number): Promise<any | null> {
try {
return await prisma.article.findUnique({
where: { id },
include: {
topics: {
include: {
topic: true
}
},
entities: {
include: {
entity: true
}
}
}
});
} catch (error) {
logger.error('Error getting article with relations:', error);
throw error;
}
}
/**
* Update article read count
* @param id Article ID
* @returns Updated article
*/
async incrementReadCount(id: number): Promise<Article> {
try {
return await prisma.article.update({
where: { id },
data: {
readCount: {
increment: 1
}
}
});
} catch (error) {
logger.error('Error incrementing article read count:', error);
throw error;
}
}
/**
* Find articles by category
* @param category Category to search for
* @param limit Maximum number of articles to return
* @returns Array of articles
*/
async findByCategory(category: string, limit: number = 10): Promise<Article[]> {
try {
return await prisma.article.findMany({
where: {
categories: {
contains: category,
}
},
take: limit,
orderBy: {
publishedAt: 'desc'
}
});
} catch (error) {
logger.error(`Error finding articles by category ${category}:`, error);
throw error;
}
}
/**
* Find similar articles based on topics and entities
* @param articleId Base article ID
* @param limit Maximum number of similar articles to return
* @returns Array of similar articles
*/
async findSimilarArticles(articleId: number, limit: number = 5): Promise<Article[]> {
try {
// Get the article with its topics and entities
const article = await this.getArticleWithRelations(articleId);
if (!article) {
return [];
}
// Extract topics and entities IDs
const topicIds = article.topics.map((t: any) => t.topicId);
const entityIds = article.entities.map((e: any) => e.entityId);
// Find articles with similar topics or entities
return await prisma.article.findMany({
where: {
id: {
not: articleId
},
OR: [
{
topics: {
some: {
topicId: {
in: topicIds
}
}
}
},
{
entities: {
some: {
entityId: {
in: entityIds
}
}
}
}
]
},
take: limit,
orderBy: {
publishedAt: 'desc'
}
});
} catch (error) {
logger.error(`Error finding similar articles for article ${articleId}:`, error);
throw error;
}
}
}