aisdk5-news-package
Version:
A comprehensive RSS news aggregation package for chat applications
92 lines (85 loc) • 3.7 kB
text/typescript
import {
pgTable,
uuid,
varchar,
text,
timestamp,
json,
boolean,
integer,
index,
primaryKey,
foreignKey
} from 'drizzle-orm/pg-core';
import { InferSelectModel } from 'drizzle-orm';
// RSS Feed table
export const newsRssFeed = pgTable('news_rss_feeds', {
id: uuid('id').primaryKey().notNull().defaultRandom(),
name: varchar('name', { length: 255 }).notNull(),
url: text('url').notNull().unique(),
category: varchar('category', { length: 100 }).notNull(),
isActive: boolean('is_active').notNull().default(true),
lastFetchedAt: timestamp('last_fetched_at'),
fetchInterval: integer('fetch_interval').notNull().default(30), // minutes
errorCount: integer('error_count').notNull().default(0),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
}, (table) => ({
isActiveIdx: index('idx_feeds_is_active').on(table.isActive),
lastFetchedIdx: index('idx_feeds_last_fetched').on(table.lastFetchedAt),
}));
export type NewsRssFeed = InferSelectModel<typeof newsRssFeed>;
// Article table
export const newsArticle = pgTable('news_articles', {
id: uuid('id').primaryKey().notNull().defaultRandom(),
title: text('title').notNull(),
description: text('description'),
content: text('content'),
url: text('url').notNull().unique(),
imageUrl: text('image_url'),
publishedAt: timestamp('published_at').notNull(),
fetchedAt: timestamp('fetched_at').notNull().defaultNow(),
feedId: uuid('feed_id')
.notNull()
.references(() => newsRssFeed.id, { onDelete: 'cascade' }),
category: varchar('category', { length: 100 }).notNull(),
tags: json('tags').$type<string[]>().notNull().default([]),
isActive: boolean('is_active').notNull().default(true),
// Media extraction fields
mediaElements: json('media_elements').$type<any[]>().notNull().default([]),
hasAudio: boolean('has_audio').notNull().default(false),
hasVideo: boolean('has_video').notNull().default(false),
mediaExtractedAt: timestamp('media_extracted_at'),
mediaExtractionError: text('media_extraction_error'),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
}, (table) => ({
publishedAtIdx: index('idx_articles_published_at').on(table.publishedAt),
categoryIdx: index('idx_articles_category').on(table.category),
feedIdIdx: index('idx_articles_feed_id').on(table.feedId),
isActiveIdx: index('idx_articles_is_active').on(table.isActive),
categoryActiveIdx: index('idx_articles_category_active').on(table.category, table.isActive),
hasAudioIdx: index('idx_articles_has_audio').on(table.hasAudio),
hasVideoIdx: index('idx_articles_has_video').on(table.hasVideo),
mediaExtractedAtIdx: index('idx_articles_media_extracted_at').on(table.mediaExtractedAt),
}));
export type NewsArticle = InferSelectModel<typeof newsArticle>;
// Saved articles table (generic - can be customized for different user table setups)
export const newsSavedArticle = pgTable('news_saved_articles', {
userId: uuid('user_id').notNull(),
articleId: uuid('article_id')
.notNull()
.references(() => newsArticle.id, { onDelete: 'cascade' }),
savedAt: timestamp('saved_at').notNull().defaultNow(),
}, (table) => ({
pk: primaryKey({ columns: [table.userId, table.articleId] }),
userIdx: index('idx_saved_articles_user').on(table.userId),
savedAtIdx: index('idx_saved_articles_saved_at').on(table.savedAt),
}));
export type NewsSavedArticle = InferSelectModel<typeof newsSavedArticle>;
// Type exports for easy importing
export type {
NewsRssFeed as RSSFeed,
NewsArticle as Article,
NewsSavedArticle as SavedArticle
};