feat(api): setting up
This commit is contained in:
+334
-349
@@ -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],
|
||||
}),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user