feat(api): setting up

This commit is contained in:
2025-11-09 16:28:36 +02:00
parent d72f3871a4
commit 4b82a11207
35 changed files with 2280 additions and 1516 deletions
+334 -349
View File
@@ -1,6 +1,7 @@
import { relations, sql } from "drizzle-orm";
import {
boolean,
check,
customType,
doublePrecision,
foreignKey,
@@ -8,326 +9,207 @@ import {
inet,
integer,
jsonb,
pgEnum,
pgSequence,
pgTable,
primaryKey,
text,
timestamp,
unique,
uniqueIndex,
uuid,
varchar,
} from "drizzle-orm/pg-core";
export const tsvector = customType<{
data: string;
}>({
const tsvector = customType<{ data: string; driverData: string }>({
dataType() {
return "tsvector";
},
});
type NumericConfig = {
precision?: number;
scale?: number;
};
export const numericCasted = customType<{
data: number;
driverData: string;
config: NumericConfig;
}>({
dataType: (config) => {
if (config?.precision && config?.scale) {
return `numeric(${config.precision}, ${config.scale})`;
}
return "numeric";
},
fromDriver: (value: string) => Number.parseFloat(value),
toDriver: (value: number) => value.toString(),
export const refreshTokensIdSeq = pgSequence("refresh_tokens_id_seq", {
cache: "1",
cycle: false,
increment: "1",
maxValue: "9223372036854775807",
minValue: "1",
startWith: "1",
});
export const articleSentimentEnum = pgEnum("article_sentiment", [
"positive",
"neutral",
"negative",
]);
// legacy table for doctrine migrations
export const doctrineMigrationVersions = pgTable("doctrine_migration_versions", {
executedAt: timestamp("executed_at", { mode: "string" }).default(sql`NULL`),
executionTime: integer("execution_time"),
version: varchar({ length: 191 }).primaryKey().notNull(),
});
export const biasEnum = pgEnum("bias", ["neutral", "slightly", "partisan", "extreme"]);
export const reliabilityEnum = pgEnum("reliability", [
"trusted",
"reliable",
"average",
"low_trust",
"unreliable",
]);
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 sources = pgTable(
"source",
{
bias: biasEnum("bias").notNull().default("neutral"),
createdAt: timestamp("created_at", { mode: "string" }).defaultNow().notNull(),
description: varchar("description", { length: 1024 }),
displayName: varchar("display_name", { length: 255 }),
id: uuid("id").notNull().defaultRandom().primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
reliability: reliabilityEnum("reliability").notNull().default("reliable"),
transparency: transparencyEnum("transparency").notNull().default("medium"),
updatedAt: timestamp("updated_at", { mode: "string" }),
url: varchar("url", { length: 255 }).notNull(),
},
(table) => [
uniqueIndex("unq_source_name").using(
"btree",
sql`lower
(${table.name})`,
),
uniqueIndex("unq_sourceUrl").using(
"btree",
sql`lower
(${table.url})`,
),
],
);
export const articles = pgTable(
"article",
{
bias: biasEnum("bias").notNull().default("neutral"),
body: text("body").notNull(),
categories: text("categories").array(),
crawledAt: timestamp("crawled_at", { mode: "string" }).notNull(),
excerpt: varchar("excerpt", { length: 255 }).generatedAlwaysAs(
() => sql`((left(body, 200) || '...'))`,
),
hash: varchar("hash", { length: 32 }).notNull(),
id: uuid("id").notNull().defaultRandom().primaryKey(),
image: varchar("image", { length: 1024 }).generatedAlwaysAs(() => sql`(metadata->>'image')`),
link: varchar("link", { length: 1024 }).notNull(),
metadata: jsonb("metadata"),
publishedAt: timestamp("published_at", { mode: "string" }).notNull(),
readingTime: integer("reading_time").default(1),
reliability: reliabilityEnum("reliability").notNull().default("reliable"),
sentiment: articleSentimentEnum("sentiment").notNull().default("neutral"),
sourceId: uuid("sourceId").notNull(),
title: varchar("title", { length: 1024 }).notNull(),
tokenStatistics: jsonb("token_statistics"),
transparency: transparencyEnum("transparency").notNull().default("medium"),
tsv: tsvector("tsv").generatedAlwaysAs(
() => sql`(
setweight(to_tsvector('french', coalesce(title, '')), 'A')
|| setweight(to_tsvector('french', coalesce(body, '')), 'B')
)`,
),
updatedAt: timestamp("updated_at", { mode: "string" }),
},
(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()),
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_articleCategories").using("gin", table.categories),
foreignKey({
columns: [table.sourceId],
foreignColumns: [sources.id],
name: "article_sourceId_fkey",
}).onDelete("cascade"),
{
expression: sql`reading_time >= 0`,
kind: "check",
name: "chk_article_reading_time",
},
{
expression: sql`(metadata IS NULL OR jsonb_typeof(metadata) IN ('object','array'))`,
kind: "check",
name: "chk_article_metadata_json",
},
],
);
export const users = pgTable(
"user",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
email: varchar("email", { length: 255 }).notNull(),
id: uuid("id").notNull().defaultRandom().primaryKey(),
isConfirmed: boolean("is_confirmed").notNull().default(false),
isLocked: boolean("is_locked").notNull().default(false),
name: varchar("name", { length: 255 }).notNull(),
password: varchar("password", { length: 512 }).notNull(),
roles: jsonb("roles").notNull(),
updatedAt: timestamp("updated_at", { mode: "string" }),
},
(table) => [
uniqueIndex("unq_user_email").using("btree", sql`lower (${table.email})`),
{
expression: sql`jsonb_typeof(roles) = 'array'`,
kind: "check",
name: "chk_user_roles_array",
},
],
);
export const bookmarks = pgTable(
export const bookmark = pgTable(
"bookmark",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
description: varchar("description", { length: 512 }),
id: uuid("id").notNull().defaultRandom().primaryKey(),
isPublic: boolean("is_public").notNull().default(false),
name: varchar("name", { length: 255 }).notNull(),
updatedAt: timestamp("updated_at", { mode: "string" }),
description: varchar({ length: 512 }).default(sql`NULL`),
id: uuid().primaryKey().notNull(),
isPublic: boolean("is_public").default(false).notNull(),
name: varchar({ length: 255 }).notNull(),
updatedAt: timestamp("updated_at", { mode: "string" }).default(sql`NULL`),
userId: uuid("user_id").notNull(),
},
(table) => [
index("bookmark_user_id_idx").on(table.userId),
index("idx_bookmark_user_created").using("btree", table.userId, table.createdAt.desc()),
foreignKey({
columns: [table.userId],
foreignColumns: [users.id],
name: "bookmark_user_id_fkey",
}).onDelete("cascade"),
],
);
export const bookmarkArticles = pgTable(
"bookmark_article",
{
articleId: uuid("article_id").notNull(),
bookmarkId: uuid("bookmark_id").notNull(),
},
(table) => [
primaryKey({
columns: [table.bookmarkId, table.articleId],
name: "bookmark_article_pkey",
}),
index("bookmark_article_bookmark_idx").on(table.bookmarkId),
index("bookmark_article_article_idx").on(table.articleId),
foreignKey({
columns: [table.bookmarkId],
foreignColumns: [bookmarks.id],
name: "bookmark_article_bookmark_id_fkey",
}).onDelete("cascade"),
foreignKey({
columns: [table.articleId],
foreignColumns: [articles.id],
name: "bookmark_article_article_id_fkey",
}).onDelete("cascade"),
],
);
export const comments = pgTable(
"comment",
{
articleId: uuid("article_id").notNull(),
content: varchar("content", { length: 512 }).notNull(),
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
id: uuid("id").notNull().defaultRandom().primaryKey(),
isSpam: boolean("is_spam").notNull().default(false),
sentiment: articleSentimentEnum("sentiment").notNull().default("neutral"),
userId: uuid("user_id").notNull(),
},
(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()),
foreignKey({
columns: [table.userId],
foreignColumns: [users.id],
name: "comment_user_id_fkey",
}).onDelete("cascade"),
foreignKey({
columns: [table.articleId],
foreignColumns: [articles.id],
name: "comment_article_id_fkey",
}).onDelete("cascade"),
],
);
export const followedSources = pgTable(
"followed_source",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
followerId: uuid("follower_id").notNull(),
id: uuid("id").notNull().defaultRandom().primaryKey(),
sourceId: uuid("sourceId").notNull(),
},
(table) => [
index("followed_source_follower_idx").on(table.followerId),
index("followed_source_sourceIdx").on(table.sourceId),
index("idx_followed_source_follower_created").using(
index("idx_bookmark_user_created").using(
"btree",
table.followerId,
table.createdAt.desc(),
table.userId.asc().nullsLast().op("timestamp_ops"),
table.createdAt.desc().nullsFirst().op("timestamp_ops"),
),
index("idx_da62921da76ed395").using("btree", table.userId.asc().nullsLast().op("uuid_ops")),
foreignKey({
columns: [table.followerId],
foreignColumns: [users.id],
name: "followed_source_follower_id_fkey",
}).onDelete("cascade"),
foreignKey({
columns: [table.sourceId],
foreignColumns: [sources.id],
name: "followed_source_sourceId_fkey",
columns: [table.userId],
foreignColumns: [user.id],
name: "fk_da62921da76ed395",
}).onDelete("cascade"),
],
);
export const loginAttempts = pgTable(
export const loginAttempt = pgTable(
"login_attempt",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
id: uuid("id").notNull().defaultRandom().primaryKey(),
id: uuid().primaryKey().notNull(),
userId: uuid("user_id").notNull(),
},
(table) => [
index("login_attempt_user_id_idx").on(table.userId),
index("idx_login_attempt_created_at").using("btree", table.createdAt.desc()),
index("idx_8c11c1ba76ed395").using("btree", table.userId.asc().nullsLast().op("uuid_ops")),
index("idx_login_attempt_created_at").using(
"btree",
table.createdAt.desc().nullsFirst().op("timestamp_ops"),
),
foreignKey({
columns: [table.userId],
foreignColumns: [users.id],
name: "login_attempt_user_id_fkey",
foreignColumns: [user.id],
name: "fk_8c11c1ba76ed395",
}).onDelete("cascade"),
],
);
export const loginHistories = pgTable(
export const loginHistory = pgTable(
"login_history",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
deviceClient: varchar("device_client", { length: 255 }),
deviceDevice: varchar("device_device", { length: 255 }),
deviceIsBot: boolean("device_is_bot").notNull().default(false),
deviceOperatingSystem: varchar("device_operating_system", { length: 255 }),
id: uuid("id").notNull().defaultRandom().primaryKey(),
deviceClient: varchar("device_client", { length: 255 }).default(sql`NULL`),
deviceDevice: varchar("device_device", { length: 255 }).default(sql`NULL`),
deviceIsBot: boolean("device_is_bot").default(false).notNull(),
deviceOperatingSystem: varchar("device_operating_system", { length: 255 }).default(sql`NULL`),
id: uuid().primaryKey().notNull(),
ipAddress: inet("ip_address"),
locationAccuracyRadius: integer("location_accuracy_radius"),
locationLatitude: doublePrecision("location_latitude"),
locationLongitude: doublePrecision("location_longitude"),
locationTimeZone: varchar("location_time_zone", { length: 255 }),
locationTimeZone: varchar("location_time_zone", { length: 255 }).default(sql`NULL`),
userId: uuid("user_id").notNull(),
},
(table) => [
index("login_history_user_id_idx").on(table.userId),
index("idx_login_history_created_at").using("btree", table.userId, table.createdAt.desc()),
index("login_history_ip_address_idx").on(table.ipAddress),
index("idx_37976e36a76ed395").using("btree", table.userId.asc().nullsLast().op("uuid_ops")),
index("idx_login_history_created_at").using(
"btree",
table.userId.asc().nullsLast().op("uuid_ops"),
table.createdAt.desc().nullsFirst().op("timestamp_ops"),
),
index("idx_login_history_ip_address").using(
"btree",
table.ipAddress.asc().nullsLast().op("inet_ops"),
),
foreignKey({
columns: [table.userId],
foreignColumns: [users.id],
name: "login_history_user_id_fkey",
foreignColumns: [user.id],
name: "fk_37976e36a76ed395",
}).onDelete("cascade"),
],
);
export const verificationToken = pgTable(
"verification_token",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
id: uuid().primaryKey().notNull(),
purpose: varchar({ length: 255 }).notNull(),
token: varchar({ length: 60 }).default(sql`NULL`),
userId: uuid("user_id").notNull(),
},
(table) => [
index("idx_c1cc006ba76ed395").using("btree", table.userId.asc().nullsLast().op("uuid_ops")),
index("idx_verif_token_created_at").using(
"btree",
table.createdAt.desc().nullsFirst().op("timestamp_ops"),
),
uniqueIndex("unq_verif_user_purpose_token")
.using(
"btree",
table.userId.asc().nullsLast().op("text_ops"),
table.purpose.asc().nullsLast().op("text_ops"),
)
.where(sql`(token IS NOT NULL)`),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "fk_c1cc006ba76ed395",
}).onDelete("cascade"),
],
);
export const followedSource = pgTable(
"followed_source",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
followerId: uuid("follower_id").notNull(),
id: uuid().primaryKey().notNull(),
sourceId: uuid("source_id").notNull(),
},
(table) => [
index("idx_7a763a3e953c1c61").using("btree", table.sourceId.asc().nullsLast().op("uuid_ops")),
index("idx_7a763a3eac24f853").using("btree", table.followerId.asc().nullsLast().op("uuid_ops")),
index("idx_followed_source_follower_created").using(
"btree",
table.followerId.asc().nullsLast().op("timestamp_ops"),
table.createdAt.desc().nullsFirst().op("uuid_ops"),
),
foreignKey({
columns: [table.followerId],
foreignColumns: [user.id],
name: "fk_7a763a3eac24f853",
}).onDelete("cascade"),
foreignKey({
columns: [table.sourceId],
foreignColumns: [source.id],
name: "fk_7a763a3e953c1c61",
}).onDelete("cascade"),
],
);
export const comment = pgTable(
"comment",
{
articleId: uuid("article_id").notNull(),
content: varchar({ length: 512 }).notNull(),
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
id: uuid().primaryKey().notNull(),
isSpam: boolean("is_spam").default(false).notNull(),
sentiment: varchar({ length: 30 }).default("neutral").notNull(),
userId: uuid("user_id").notNull(),
},
(table) => [
index("idx_9474526c7294869c").using("btree", table.articleId.asc().nullsLast().op("uuid_ops")),
index("idx_9474526ca76ed395").using("btree", table.userId.asc().nullsLast().op("uuid_ops")),
index("idx_comment_article_created").using(
"btree",
table.articleId.asc().nullsLast().op("timestamp_ops"),
table.createdAt.desc().nullsFirst().op("uuid_ops"),
),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "fk_9474526ca76ed395",
}).onDelete("cascade"),
foreignKey({
columns: [table.articleId],
foreignColumns: [article.id],
name: "fk_9474526c7294869c",
}).onDelete("cascade"),
],
);
@@ -335,120 +217,223 @@ export const loginHistories = pgTable(
export const refreshTokens = pgTable(
"refresh_tokens",
{
id: integer("id").generatedAlwaysAsIdentity({ name: "refresh_tokens_id_seq" }).primaryKey(),
id: integer().primaryKey().notNull(),
refreshToken: varchar("refresh_token", { length: 128 }).notNull(),
username: varchar("username", { length: 255 }).notNull(),
validUntil: timestamp("valid", { mode: "string" }).notNull(),
},
(table) => [unique("uniq_refresh_token_token").on(table.refreshToken)],
);
export const verificationTokens = pgTable(
"verification_token",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
id: uuid("id").notNull().defaultRandom().primaryKey(),
purpose: verificationTokenPurposeEnum("purpose").notNull(),
token: varchar("token", { length: 60 }),
userId: uuid("user_id").notNull(),
username: varchar({ length: 255 }).notNull(),
valid: timestamp({ mode: "string" }).notNull(),
},
(table) => [
index("verification_token_user_id_idx").on(table.userId),
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`),
foreignKey({
columns: [table.userId],
foreignColumns: [users.id],
name: "verification_token_user_id_fkey",
}).onDelete("cascade"),
uniqueIndex("uniq_9bace7e1c74f2195").using(
"btree",
table.refreshToken.asc().nullsLast().op("text_ops"),
),
],
);
// Relations
export const article = pgTable(
"article",
{
bias: varchar({ length: 30 }).default("neutral").notNull(),
body: text().notNull(),
categories: text().array(),
crawledAt: timestamp("crawled_at", { mode: "string" }).notNull(),
excerpt: varchar({ length: 255 }).generatedAlwaysAs(sql`("left"(body, 200) || '...'::text)`),
hash: varchar({ length: 32 }).notNull(),
id: uuid().primaryKey().notNull(),
image: varchar({ length: 1024 }).generatedAlwaysAs(sql`(metadata ->> 'image'::text)`),
link: varchar({ length: 1024 }).notNull(),
metadata: jsonb(),
publishedAt: timestamp("published_at", { mode: "string" }).notNull(),
readingTime: integer("reading_time").default(1),
reliability: varchar({ length: 30 }).default("reliable").notNull(),
sentiment: varchar({ length: 30 }).default("neutral").notNull(),
sourceId: uuid("source_id").notNull(),
title: varchar({ length: 1024 }).notNull(),
tokenStatistics: jsonb("token_statistics"),
transparency: varchar({ length: 30 }).default("medium").notNull(),
tsv: tsvector("tsv").generatedAlwaysAs(
sql`(setweight(to_tsvector('french'::regconfig, (COALESCE(title, ''::character varying))::text), 'A'::"char") || setweight(to_tsvector('french'::regconfig, COALESCE(body, ''::text)), 'B'::"char"))`,
),
updatedAt: timestamp("updated_at", { mode: "string" }).default(sql`NULL`),
},
(table) => [
index("gin_article_categories").using(
"gin",
table.categories.asc().nullsLast().op("array_ops"),
),
index("gin_article_link_trgm").using("gin", table.link.asc().nullsLast().op("gin_trgm_ops")),
index("gin_article_title_trgm").using("gin", table.title.asc().nullsLast().op("gin_trgm_ops")),
index("gin_article_tsv").using("gin", table.tsv.asc().nullsLast().op("tsvector_ops")),
index("idx_23a0e66953c1c61").using("btree", table.sourceId.asc().nullsLast().op("uuid_ops")),
index("idx_article_published_at").using(
"btree",
table.publishedAt.desc().nullsFirst().op("timestamp_ops"),
),
index("idx_article_published_id").using(
"btree",
table.publishedAt.desc().nullsFirst().op("timestamp_ops"),
table.id.desc().nullsFirst().op("uuid_ops"),
),
uniqueIndex("unq_article_hash").using("btree", table.hash.asc().nullsLast().op("text_ops")),
foreignKey({
columns: [table.sourceId],
foreignColumns: [source.id],
name: "fk_23a0e66953c1c61",
}).onDelete("cascade"),
check("chk_article_reading_time", sql`reading_time >= 0`),
check(
"chk_article_sentiment",
sql`(sentiment)::text = ANY ((ARRAY['positive'::character varying, 'neutral'::character varying, 'negative'::character varying])::text[])`,
),
check(
"chk_article_metadata_json",
sql`(metadata IS NULL) OR (jsonb_typeof(metadata) = ANY (ARRAY['object'::text, 'array'::text]))`,
),
],
);
export const sourcesRelations = relations(sources, ({ many }) => ({
articles: many(articles),
followers: many(followedSources),
}));
export const user = pgTable(
"user",
{
createdAt: timestamp("created_at", { mode: "string" }).notNull(),
email: varchar({ length: 255 }).notNull(),
id: uuid().primaryKey().notNull(),
isConfirmed: boolean("is_confirmed").default(false).notNull(),
isLocked: boolean("is_locked").default(false).notNull(),
name: varchar({ length: 255 }).notNull(),
password: varchar({ length: 512 }).notNull(),
roles: jsonb().notNull(),
updatedAt: timestamp("updated_at", { mode: "string" }).default(sql`NULL`),
},
(_table) => [
uniqueIndex("unq_user_email").using("btree", sql`lower((email)::text)`),
check("chk_user_roles_json", sql`jsonb_typeof(roles) = 'array'::text`),
],
);
export const articlesRelations = relations(articles, ({ one, many }) => ({
bookmarkLinks: many(bookmarkArticles),
comments: many(comments),
source: one(sources, {
fields: [articles.sourceId],
references: [sources.id],
export const source = pgTable(
"source",
{
bias: varchar({ length: 30 }).default("neutral").notNull(),
description: varchar({ length: 1024 }).default(sql`NULL`),
displayName: varchar("display_name", { length: 255 }).default(sql`NULL`),
id: uuid().primaryKey().notNull(),
name: varchar({ length: 255 }).notNull(),
reliability: varchar({ length: 30 }).default("reliable").notNull(),
transparency: varchar({ length: 30 }).default("medium").notNull(),
updatedAt: timestamp("updated_at", { mode: "string" }).default(sql`NULL`),
url: varchar({ length: 255 }).notNull(),
},
(_table) => [
uniqueIndex("unq_source_name").using("btree", sql`lower((name)::text)`),
uniqueIndex("unq_source_url").using("btree", sql`lower((url)::text)`),
],
);
export const bookmarkArticle = pgTable(
"bookmark_article",
{
articleId: uuid("article_id").notNull(),
bookmarkId: uuid("bookmark_id").notNull(),
},
(table) => [
index("idx_6fe2655d7294869c").using("btree", table.articleId.asc().nullsLast().op("uuid_ops")),
index("idx_6fe2655d92741d25").using("btree", table.bookmarkId.asc().nullsLast().op("uuid_ops")),
foreignKey({
columns: [table.bookmarkId],
foreignColumns: [bookmark.id],
name: "fk_6fe2655d92741d25",
}).onDelete("cascade"),
foreignKey({
columns: [table.articleId],
foreignColumns: [article.id],
name: "fk_6fe2655d7294869c",
}).onDelete("cascade"),
primaryKey({ columns: [table.bookmarkId, table.articleId], name: "bookmark_article_pkey" }),
],
);
export const bookmarkRelations = relations(bookmark, ({ one, many }) => ({
bookmarkArticles: many(bookmarkArticle),
user: one(user, {
fields: [bookmark.userId],
references: [user.id],
}),
}));
export const appUsersRelations = relations(users, ({ many }) => ({
bookmarks: many(bookmarks),
comments: many(comments),
followedSources: many(followedSources),
loginAttempts: many(loginAttempts),
loginHistories: many(loginHistories),
verificationTokens: many(verificationTokens),
export const userRelations = relations(user, ({ many }) => ({
bookmarks: many(bookmark),
comments: many(comment),
followedSources: many(followedSource),
loginAttempts: many(loginAttempt),
loginHistories: many(loginHistory),
verificationTokens: many(verificationToken),
}));
export const bookmarksRelations = relations(bookmarks, ({ one, many }) => ({
articles: many(bookmarkArticles),
user: one(users, {
fields: [bookmarks.userId],
references: [users.id],
export const loginAttemptRelations = relations(loginAttempt, ({ one }) => ({
user: one(user, {
fields: [loginAttempt.userId],
references: [user.id],
}),
}));
export const bookmarkArticlesRelations = relations(bookmarkArticles, ({ one }) => ({
article: one(articles, {
fields: [bookmarkArticles.articleId],
references: [articles.id],
}),
bookmark: one(bookmarks, {
fields: [bookmarkArticles.bookmarkId],
references: [bookmarks.id],
export const loginHistoryRelations = relations(loginHistory, ({ one }) => ({
user: one(user, {
fields: [loginHistory.userId],
references: [user.id],
}),
}));
export const commentsRelations = relations(comments, ({ one }) => ({
article: one(articles, {
fields: [comments.articleId],
references: [articles.id],
}),
user: one(users, {
fields: [comments.userId],
references: [users.id],
export const verificationTokenRelations = relations(verificationToken, ({ one }) => ({
user: one(user, {
fields: [verificationToken.userId],
references: [user.id],
}),
}));
export const followedSourcesRelations = relations(followedSources, ({ one }) => ({
follower: one(users, {
fields: [followedSources.followerId],
references: [users.id],
export const followedSourceRelations = relations(followedSource, ({ one }) => ({
source: one(source, {
fields: [followedSource.sourceId],
references: [source.id],
}),
source: one(sources, {
fields: [followedSources.sourceId],
references: [sources.id],
user: one(user, {
fields: [followedSource.followerId],
references: [user.id],
}),
}));
export const loginAttemptsRelations = relations(loginAttempts, ({ one }) => ({
user: one(users, {
fields: [loginAttempts.userId],
references: [users.id],
export const sourceRelations = relations(source, ({ many }) => ({
articles: many(article),
followedSources: many(followedSource),
}));
export const commentRelations = relations(comment, ({ one }) => ({
article: one(article, {
fields: [comment.articleId],
references: [article.id],
}),
user: one(user, {
fields: [comment.userId],
references: [user.id],
}),
}));
export const loginHistoriesRelations = relations(loginHistories, ({ one }) => ({
user: one(users, {
fields: [loginHistories.userId],
references: [users.id],
export const articleRelations = relations(article, ({ one, many }) => ({
bookmarkArticles: many(bookmarkArticle),
comments: many(comment),
source: one(source, {
fields: [article.sourceId],
references: [source.id],
}),
}));
export const verificationTokensRelations = relations(verificationTokens, ({ one }) => ({
user: one(users, {
fields: [verificationTokens.userId],
references: [users.id],
export const bookmarkArticleRelations = relations(bookmarkArticle, ({ one }) => ({
article: one(article, {
fields: [bookmarkArticle.articleId],
references: [article.id],
}),
bookmark: one(bookmark, {
fields: [bookmarkArticle.bookmarkId],
references: [bookmark.id],
}),
}));