feat(dashboard): more type safety

This commit is contained in:
2025-11-17 01:26:33 +02:00
parent f39635e04f
commit 22aab9ffc6
25 changed files with 120 additions and 71 deletions
+5 -4
View File
@@ -1,5 +1,6 @@
import { DEFAULT_TIMEZONE } from "@basango/domain/constants";
import {
Article,
Distribution,
Distributions,
ID,
@@ -143,7 +144,7 @@ export async function getArticles(db: Database, params: GetArticlesParams) {
.orderBy(desc(articles.publishedAt), desc(articles.id))
.limit(pagination.limit + 1);
return buildPaginatedResult(rows, pagination, {
return buildPaginatedResult<Article>(rows, pagination, {
date: "publishedAt",
id: "id",
});
@@ -154,7 +155,7 @@ export async function getArticlesPublicationGraph(
params: GetPublicationsParams,
): Promise<Publications> {
const [startDate, endDate] = buildDateRange(params.range);
const [previousRangeStart, previousRangeEnd] = buildPreviousRange([startDate, endDate]);
const [previousStart, previousEnd] = buildPreviousRange([startDate, endDate]);
const data = await db.execute<Publication>(sql`
WITH bounds AS (
@@ -193,8 +194,8 @@ export async function getArticlesPublicationGraph(
sql`
SELECT COALESCE(COUNT(*)::int, 0) AS count
FROM article a
WHERE a.published_at >= timezone(${DEFAULT_TIMEZONE}, ${previousRangeStart})
AND a.published_at <= timezone(${DEFAULT_TIMEZONE}, ${previousRangeEnd})
WHERE a.published_at >= timezone(${DEFAULT_TIMEZONE}, ${previousStart})
AND a.published_at <= timezone(${DEFAULT_TIMEZONE}, ${previousEnd})
`,
)
.then((res) => res.rows);
+1 -1
View File
@@ -24,7 +24,7 @@ export async function getSources(db: Database) {
rows.map(async (row) => ({
...row,
articles: await countArticlesBySourceId(db, row.id),
publicationGraph: await getSourcePublicationGraph(db, { id: row.id }),
publications: await getSourcePublicationGraph(db, { id: row.id }),
})),
);
}
+6
View File
@@ -1,4 +1,10 @@
{
"compilerOptions": {
"paths": {
"#db/*": ["./src/*"],
"#domain/*": ["../domain/src/*"]
}
},
"exclude": ["node_modules"],
"extends": "@basango/tsconfig/base.json",
"include": ["src"]
+15
View File
@@ -2,6 +2,8 @@ import { z } from "@hono/zod-openapi";
import { idSchema, sentimentSchema } from "#domain/models/shared";
import { sourceSchema } from "./sources";
// schemas
export const articleMetadataSchema = z.object({
author: z.string().optional().openapi({
@@ -70,11 +72,19 @@ export const articleSchema = z.object({
description: "The date and time when the article was created in the system.",
example: "2023-01-01T12:00:00Z",
}),
excerpt: z.string().optional().openapi({
description: "A brief excerpt or summary of the article.",
example: "This article discusses the latest advancements in AI technology.",
}),
hash: z.string().min(1).openapi({
description: "The unique hash of the article link.",
example: "d41d8cd98f00b204e9800998ecf8427e",
}),
id: idSchema,
image: z.url().optional().openapi({
description: "The URL of the main image associated with the article.",
example: "https://example.com/image.jpg",
}),
link: z.string().url().openapi({
description: "The URL of the article.",
example: "https://example.com/article",
@@ -84,6 +94,11 @@ export const articleSchema = z.object({
description: "The publication date of the article as a Date object.",
example: "2023-01-01T00:00:00Z",
}),
readingTime: z.number().int().min(1).openapi({
description: "Estimated reading time of the article in minutes.",
example: 5,
}),
source: sourceSchema.optional(),
sourceId: z.union([z.uuid(), z.string().min(1)]).openapi({
description: "The unique identifier of the source from which the article was crawled.",
example: "b3e1c8f4-5d6a-4c9e-8f1e-2d3c4b5a6f7g",
+5
View File
@@ -1,4 +1,9 @@
{
"compilerOptions": {
"paths": {
"#domain/*": ["./src/*"]
}
},
"exclude": ["node_modules"],
"extends": "@basango/tsconfig/base.json",
"include": ["src"]
+1 -1
View File
@@ -1,5 +1,5 @@
{
"exclude": ["node_modules"],
"extends": "@basango/tsconfig/base.json",
"include": ["src/**/*"]
"include": ["src"]
}
-7
View File
@@ -11,13 +11,6 @@
"moduleDetection": "force",
"moduleResolution": "Bundler",
"noUncheckedIndexedAccess": true,
"paths": {
"#api/*": ["../../apps/api/src/*"],
"#crawler/*": ["../../apps/crawler/src/*"],
"#dashboard/*": ["../../apps/dashboard/src/*"],
"#db/*": ["../db/src/*"],
"#domain/*": ["../domain/src/*"]
},
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,