From dad5e343d1eecc7e6d4264e2f43938ac0015820e Mon Sep 17 00:00:00 2001 From: bernard-ng Date: Tue, 4 Nov 2025 16:20:39 +0200 Subject: [PATCH] [db] change field case --- basango/packages/db/src/client.ts | 10 +- basango/packages/db/src/queries/articles.ts | 52 ++------ basango/packages/db/src/queries/sources.ts | 44 ++----- basango/packages/db/src/schema.ts | 129 +++++++------------- 4 files changed, 69 insertions(+), 166 deletions(-) diff --git a/basango/packages/db/src/client.ts b/basango/packages/db/src/client.ts index f6b48de..42e491f 100644 --- a/basango/packages/db/src/client.ts +++ b/basango/packages/db/src/client.ts @@ -13,8 +13,7 @@ const connectionConfig = { }; const pool = new Pool({ - connectionString: - process.env.DATABASE_URL ?? process.env.DATABASE_PRIMARY_URL!, + connectionString: process.env.DATABASE_URL ?? process.env.DATABASE_PRIMARY_URL!, ...connectionConfig, }); @@ -31,9 +30,7 @@ export const getConnectionPoolStats = () => { const totalConnections = connectionConfig.max; const utilization = - totalConnections > 0 - ? Math.round((stats.active / totalConnections) * 100) - : 0; + totalConnections > 0 ? Math.round((stats.active / totalConnections) * 100) : 0; return { timestamp: new Date().toISOString(), @@ -44,8 +41,7 @@ export const getConnectionPoolStats = () => { totalConnections, totalActive: stats.active, totalWaiting: stats.waiting, - hasExhaustedPools: - stats.active >= totalConnections || (stats.waiting ?? 0) > 0, + hasExhaustedPools: stats.active >= totalConnections || (stats.waiting ?? 0) > 0, utilizationPercent: utilization, }, }; diff --git a/basango/packages/db/src/queries/articles.ts b/basango/packages/db/src/queries/articles.ts index 308aaca..1d6b864 100644 --- a/basango/packages/db/src/queries/articles.ts +++ b/basango/packages/db/src/queries/articles.ts @@ -2,14 +2,7 @@ import type { AnyColumn, SQL } from "drizzle-orm"; import { and, asc, desc, eq, gt, lt, or, sql } from "drizzle-orm"; import type { Database } from "@/client"; -import { - articles, - bookmarkArticles, - bookmarks, - comments, - sources, - users, -} from "@/schema"; +import { articles, bookmarkArticles, bookmarks, comments, sources, users } from "@/schema"; import { buildPaginationResult, createPageState, @@ -117,8 +110,7 @@ export async function* getArticlesForExport( db: Database, params: ArticleExportParams = {}, ): AsyncGenerator { - const batchSize = - params.batchSize && params.batchSize > 0 ? params.batchSize : 1000; + const batchSize = params.batchSize && params.batchSize > 0 ? params.batchSize : 1000; const filters: SQL[] = []; @@ -183,19 +175,13 @@ export async function* getArticlesForExport( const SOURCE_IMAGE_BASE = "https://devscast.org/images/sources/"; -function normalizeArticleFilters( - filters?: ArticleFilters, -): NormalizedArticleFilters { +function normalizeArticleFilters(filters?: ArticleFilters): NormalizedArticleFilters { const trimmedSearch = filters?.search?.trim(); const trimmedCategory = filters?.category?.trim(); return { - search: - trimmedSearch && trimmedSearch.length > 0 ? trimmedSearch : undefined, - category: - trimmedCategory && trimmedCategory.length > 0 - ? trimmedCategory - : undefined, + search: trimmedSearch && trimmedSearch.length > 0 ? trimmedSearch : undefined, + category: trimmedCategory && trimmedCategory.length > 0 ? trimmedCategory : undefined, dateRange: filters?.dateRange ?? null, sortDirection: filters?.sortDirection ?? "desc", }; @@ -261,8 +247,9 @@ async function fetchArticleOverview( }, ): Promise { const baseConditions = options.baseConditions ?? []; - const { conditions: filterConditions, searchQuery } = - buildArticleFilterConditions(options.filters); + const { conditions: filterConditions, searchQuery } = buildArticleFilterConditions( + options.filters, + ); const whereConditions = [...baseConditions, ...filterConditions]; const bookmarkExpression = buildBookmarkExistsExpression(options.userId); @@ -297,17 +284,11 @@ async function fetchArticleOverview( options.filters.sortDirection === "asc" ? or( gt(articles.publishedAt, cursor.date), - and( - eq(articles.publishedAt, cursor.date), - gt(articles.id, cursor.id), - ), + and(eq(articles.publishedAt, cursor.date), gt(articles.id, cursor.id)), ) : or( lt(articles.publishedAt, cursor.date), - and( - eq(articles.publishedAt, cursor.date), - lt(articles.id, cursor.id), - ), + and(eq(articles.publishedAt, cursor.date), lt(articles.id, cursor.id)), ); whereConditions.push(comparison); } @@ -394,8 +375,7 @@ export async function getBookmarkedArticleList( ): Promise { const page = createPageState(params.page); const filters = normalizeArticleFilters(params.filters); - const { conditions: filterConditions, searchQuery } = - buildArticleFilterConditions(filters); + const { conditions: filterConditions, searchQuery } = buildArticleFilterConditions(filters); const whereConditions: SQL[] = [ eq(bookmarks.id, params.bookmarkId), @@ -435,17 +415,11 @@ export async function getBookmarkedArticleList( filters.sortDirection === "asc" ? or( gt(articles.publishedAt, cursor.date), - and( - eq(articles.publishedAt, cursor.date), - gt(articles.id, cursor.id), - ), + and(eq(articles.publishedAt, cursor.date), gt(articles.id, cursor.id)), ) : or( lt(articles.publishedAt, cursor.date), - and( - eq(articles.publishedAt, cursor.date), - lt(articles.id, cursor.id), - ), + and(eq(articles.publishedAt, cursor.date), lt(articles.id, cursor.id)), ); whereConditions.push(comparison); } diff --git a/basango/packages/db/src/queries/sources.ts b/basango/packages/db/src/queries/sources.ts index cb1f857..a8fa02f 100644 --- a/basango/packages/db/src/queries/sources.ts +++ b/basango/packages/db/src/queries/sources.ts @@ -67,9 +67,7 @@ export interface SourceStatisticsRow { articleMetadataAvailable: number; } -export async function getSourceStatisticsList( - db: Database, -): Promise { +export async function getSourceStatisticsList(db: Database): Promise { const rows = await db .select({ sourceId: sources.id, @@ -111,8 +109,7 @@ async function selectPublicationBoundary( conditions.push(sql`${params.category} = ANY(${articles.categories})`); } - const whereClause = - conditions.length > 1 ? and(...conditions) : conditions[0]; + const whereClause = conditions.length > 1 ? and(...conditions) : conditions[0]; const [result] = await db .select({ @@ -181,9 +178,7 @@ export async function getSourceOverviewList( ); } - const rows = await query - .orderBy(desc(sources.createdAt), desc(sources.id)) - .limit(page.limit + 1); + const rows = await query.orderBy(desc(sources.createdAt), desc(sources.id)).limit(page.limit + 1); return buildPaginationResult(rows, page, { id: "sourceId", @@ -200,10 +195,7 @@ function createBackwardDateRange(days: number): { start: number; end: number } { return { start, end }; } -async function fetchPublicationGraph( - db: Database, - sourceId: string, -): Promise { +async function fetchPublicationGraph(db: Database, sourceId: string): Promise { const range = createBackwardDateRange(PUBLICATION_GRAPH_DAYS); const rows = await db @@ -239,11 +231,7 @@ async function fetchPublicationGraph( const start = new Date(range.start * 1000); const end = new Date(range.end * 1000); - for ( - let date = new Date(start.getTime()); - date < end; - date.setUTCDate(date.getUTCDate() + 1) - ) { + for (let date = new Date(start.getTime()); date < end; date.setUTCDate(date.getUTCDate() + 1)) { const day = date.toISOString().slice(0, 10); entries.push({ day, count: counts.get(day) ?? 0 }); } @@ -251,10 +239,7 @@ async function fetchPublicationGraph( return entries; } -async function fetchCategoryShares( - db: Database, - sourceId: string, -): Promise { +async function fetchCategoryShares(db: Database, sourceId: string): Promise { const rows = await db .select({ categories: sql`array_to_string @@ -273,18 +258,13 @@ async function fetchCategoryShares( } } - const total = Array.from(counts.values()).reduce( - (acc, value) => acc + value, - 0, - ); + const total = Array.from(counts.values()).reduce((acc, value) => acc + value, 0); - const shares: CategoryShare[] = Array.from(counts.entries()).map( - ([category, count]) => ({ - category, - count, - percentage: total > 0 ? Math.round((count / total) * 10000) / 100 : 0, - }), - ); + const shares: CategoryShare[] = Array.from(counts.entries()).map(([category, count]) => ({ + category, + count, + percentage: total > 0 ? Math.round((count / total) * 10000) / 100 : 0, + })); shares.sort((a, b) => b.count - a.count); return shares; diff --git a/basango/packages/db/src/schema.ts b/basango/packages/db/src/schema.ts index 414b8a8..b2f97fc 100644 --- a/basango/packages/db/src/schema.ts +++ b/basango/packages/db/src/schema.ts @@ -53,12 +53,7 @@ export const articleSentimentEnum = pgEnum("article_sentiment", [ "negative", ]); -export const biasEnum = pgEnum("bias", [ - "neutral", - "slightly", - "partisan", - "extreme", -]); +export const biasEnum = pgEnum("bias", ["neutral", "slightly", "partisan", "extreme"]); export const reliabilityEnum = pgEnum("reliability", [ "trusted", @@ -68,16 +63,14 @@ export const reliabilityEnum = pgEnum("reliability", [ "unreliable", ]); -export const transparencyEnum = pgEnum("transparency", [ - "high", - "medium", - "low", -]); +export const transparencyEnum = pgEnum("transparency", ["high", "medium", "low"]); -export const verificationTokenPurposeEnum = pgEnum( - "verification_token_purpose", - ["confirm_account", "password_reset", "unlock_account", "delete_account"], -); +export const verificationTokenPurposeEnum = pgEnum("verification_token_purpose", [ + "confirm_account", + "password_reset", + "unlock_account", + "delete_account", +]); export const sources = pgTable( "source", @@ -87,9 +80,7 @@ export const sources = pgTable( name: varchar("name", { length: 255 }).notNull(), displayName: varchar("display_name", { length: 255 }), description: varchar("description", { length: 1024 }), - createdAt: timestamp("created_at", { mode: "string" }) - .defaultNow() - .notNull(), + createdAt: timestamp("created_at", { mode: "string" }).defaultNow().notNull(), updatedAt: timestamp("updated_at", { mode: "string" }), bias: biasEnum("bias").notNull().default("neutral"), reliability: reliabilityEnum("reliability").notNull().default("reliable"), @@ -121,9 +112,7 @@ export const articles = pgTable( sentiment: articleSentimentEnum("sentiment").notNull().default("neutral"), metadata: jsonb("metadata"), tokenStatistics: jsonb("token_statistics"), - image: varchar("image", { length: 1024 }).generatedAlwaysAs( - () => sql`(metadata->>'image')`, - ), + image: varchar("image", { length: 1024 }).generatedAlwaysAs(() => sql`(metadata->>'image')`), excerpt: varchar("excerpt", { length: 255 }).generatedAlwaysAs( () => sql`((left(body, 200) || '...'))`, ), @@ -145,18 +134,11 @@ export const articles = pgTable( (table) => [ index("article_sourceId_idx").on(table.sourceId), index("idx_article_published_at").using("btree", table.publishedAt.desc()), - index("idx_article_published_id").using( - "btree", - table.publishedAt.desc(), - table.id.desc(), - ), + index("idx_article_published_id").using("btree", table.publishedAt.desc(), table.id.desc()), unique("unq_article_hash").on(table.hash), index("gin_article_tsv").using("gin", table.tsv), index("gin_articleLink_trgm").using("gin", table.link.op("gin_trgm_ops")), - index("gin_articleTitle_trgm").using( - "gin", - table.title.op("gin_trgm_ops"), - ), + index("gin_articleTitle_trgm").using("gin", table.title.op("gin_trgm_ops")), index("gin_articleCategories").using("gin", table.categories), foreignKey({ columns: [table.sourceId], @@ -212,11 +194,7 @@ export const bookmarks = pgTable( }, (table) => [ index("bookmark_user_id_idx").on(table.userId), - index("idx_bookmark_user_created").using( - "btree", - table.userId, - table.createdAt.desc(), - ), + index("idx_bookmark_user_created").using("btree", table.userId, table.createdAt.desc()), foreignKey({ columns: [table.userId], foreignColumns: [users.id], @@ -265,11 +243,7 @@ export const comments = pgTable( (table) => [ index("comment_user_id_idx").on(table.userId), index("comment_article_id_idx").on(table.articleId), - index("idx_comment_article_created").using( - "btree", - table.articleId, - table.createdAt.desc(), - ), + index("idx_comment_article_created").using("btree", table.articleId, table.createdAt.desc()), foreignKey({ columns: [table.userId], foreignColumns: [users.id], @@ -321,10 +295,7 @@ export const loginAttempts = pgTable( }, (table) => [ index("login_attempt_user_id_idx").on(table.userId), - index("idx_login_attempt_created_at").using( - "btree", - table.createdAt.desc(), - ), + index("idx_login_attempt_created_at").using("btree", table.createdAt.desc()), foreignKey({ columns: [table.userId], foreignColumns: [users.id], @@ -351,11 +322,7 @@ export const loginHistories = pgTable( }, (table) => [ index("login_history_user_id_idx").on(table.userId), - index("idx_login_history_created_at").using( - "btree", - table.userId, - table.createdAt.desc(), - ), + index("idx_login_history_created_at").using("btree", table.userId, table.createdAt.desc()), index("login_history_ip_address_idx").on(table.ipAddress), foreignKey({ columns: [table.userId], @@ -368,9 +335,7 @@ export const loginHistories = pgTable( export const refreshTokens = pgTable( "refresh_tokens", { - id: integer("id") - .generatedAlwaysAsIdentity({ name: "refresh_tokens_id_seq" }) - .primaryKey(), + id: integer("id").generatedAlwaysAsIdentity({ name: "refresh_tokens_id_seq" }).primaryKey(), refreshToken: varchar("refresh_token", { length: 128 }).notNull(), username: varchar("username", { length: 255 }).notNull(), validUntil: timestamp("valid", { mode: "string" }).notNull(), @@ -389,10 +354,7 @@ export const verificationTokens = pgTable( }, (table) => [ index("verification_token_user_id_idx").on(table.userId), - index("idx_verification_token_created_at").using( - "btree", - table.createdAt.desc(), - ), + index("idx_verification_token_created_at").using("btree", table.createdAt.desc()), uniqueIndex("unq_verification_token_user_purpose") .on(table.userId, table.purpose) .where(sql`token IS NOT NULL`), @@ -437,19 +399,16 @@ export const bookmarksRelations = relations(bookmarks, ({ one, many }) => ({ articles: many(bookmarkArticles), })); -export const bookmarkArticlesRelations = relations( - bookmarkArticles, - ({ one }) => ({ - bookmark: one(bookmarks, { - fields: [bookmarkArticles.bookmarkId], - references: [bookmarks.id], - }), - article: one(articles, { - fields: [bookmarkArticles.articleId], - references: [articles.id], - }), +export const bookmarkArticlesRelations = relations(bookmarkArticles, ({ one }) => ({ + bookmark: one(bookmarks, { + fields: [bookmarkArticles.bookmarkId], + references: [bookmarks.id], }), -); + article: one(articles, { + fields: [bookmarkArticles.articleId], + references: [articles.id], + }), +})); export const commentsRelations = relations(comments, ({ one }) => ({ article: one(articles, { @@ -462,19 +421,16 @@ export const commentsRelations = relations(comments, ({ one }) => ({ }), })); -export const followedSourcesRelations = relations( - followedSources, - ({ one }) => ({ - follower: one(users, { - fields: [followedSources.followerId], - references: [users.id], - }), - source: one(sources, { - fields: [followedSources.sourceId], - references: [sources.id], - }), +export const followedSourcesRelations = relations(followedSources, ({ one }) => ({ + follower: one(users, { + fields: [followedSources.followerId], + references: [users.id], }), -); + source: one(sources, { + fields: [followedSources.sourceId], + references: [sources.id], + }), +})); export const loginAttemptsRelations = relations(loginAttempts, ({ one }) => ({ user: one(users, { @@ -490,12 +446,9 @@ export const loginHistoriesRelations = relations(loginHistories, ({ one }) => ({ }), })); -export const verificationTokensRelations = relations( - verificationTokens, - ({ one }) => ({ - user: one(users, { - fields: [verificationTokens.userId], - references: [users.id], - }), +export const verificationTokensRelations = relations(verificationTokens, ({ one }) => ({ + user: one(users, { + fields: [verificationTokens.userId], + references: [users.id], }), -); +}));