feat(db): migration and database setup

This commit is contained in:
2025-11-10 16:57:27 +02:00
parent 594b08a2d1
commit fbca02bec6
31 changed files with 2854 additions and 1928 deletions
+65
View File
@@ -0,0 +1,65 @@
import { md5 } from "@basango/encryption";
import { eq } from "drizzle-orm";
import { v7 as uuidV7 } from "uuid";
import { Database } from "@/client";
import { ArticleMetadata, Sentiment, TokenStatistics, article } from "@/schema";
import { computeReadingTime, computeTokenStatistics } from "@/utils/computed";
import { getSourceIdByName } from "./sources";
export type CreateArticleParams = {
title: string;
body: string;
categories: string[];
link: string;
sourceId: string;
publishedAt: Date;
sentiment?: Sentiment;
tokenStatistics?: TokenStatistics;
readingTime?: number;
metadata?: ArticleMetadata;
};
export async function createArticle(db: Database, params: CreateArticleParams) {
const data = {
...params,
hash: md5(params.link),
readingTime: computeReadingTime(params.body),
sentiment: "neutral" as Sentiment,
sourceId: await getSourceIdByName(db, params.sourceId),
tokenStatistics: computeTokenStatistics({
body: params.body,
categories: params.categories,
title: params.title,
}),
};
const duplicated = await getArticleByHash(db, data.hash);
if (duplicated !== undefined) {
return {
id: duplicated.id,
sourceId: duplicated.sourceId,
};
}
const [result] = await db
.insert(article)
.values({ id: uuidV7(), ...data })
.returning({
id: article.id,
sourceId: article.sourceId,
});
if (result === undefined) {
throw new Error("Failed to create article");
}
return result;
}
export async function getArticleByHash(db: Database, hash: string) {
return db.query.article.findFirst({
where: eq(article.hash, hash),
});
}
+65
View File
@@ -0,0 +1,65 @@
import { eq } from "drizzle-orm";
import { v7 as uuidV7 } from "uuid";
import { Database } from "@/client";
import { NotFoundError } from "@/errors";
import { Credibility, source } from "@/schema";
export type CreateSourceParams = {
name: string;
url: string;
displayName?: string;
description?: string;
credibility: Credibility;
updatedAt?: Date;
};
export async function createSource(db: Database, params: CreateSourceParams) {
const [result] = await db
.insert(source)
.values({ id: uuidV7(), ...params })
.returning();
return result;
}
export type DeleteSourceParams = {
id: string;
};
export async function deleteSource(db: Database, params: DeleteSourceParams) {
const [result] = await db.delete(source).where(eq(source.id, params.id)).returning();
return result;
}
export async function getSourceByName(db: Database, name: string) {
return db.query.source.findFirst({
where: eq(source.name, name),
});
}
export async function getSourceIdByName(db: Database, name: string): Promise<string> {
const result = await db.query.source.findFirst({
columns: {
id: true,
},
where: eq(source.name, name),
});
if (!result) {
throw new NotFoundError(`Source with name "${name}" not found`);
}
return result.id;
}
export type GetSourceByIdParams = {
id: string;
};
export async function getSourceById(db: Database, params: GetSourceByIdParams) {
return db.query.source.findFirst({
where: eq(source.id, params.id),
});
}