From 0e6318cd7422fd2cb649a6bb2aac4415ace01523 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Sat, 28 Jun 2025 15:51:45 +0500 Subject: [PATCH 01/13] Refactor database relations schema to enhance clarity and maintainability This update restructures the database relations definition by consolidating the relationship mappings for articles, users, comments, and tags into a more organized format. The changes improve code readability and maintainability while ensuring accurate representation of relationships within the database schema. --- src/comments/comments.plugin.ts | 18 ++-- src/core/database/relations.ts | 151 +++++++++++++++++++++----------- src/profiles/profiles.schema.ts | 19 +++- 3 files changed, 126 insertions(+), 62 deletions(-) diff --git a/src/comments/comments.plugin.ts b/src/comments/comments.plugin.ts index 6f36ceb..10c5ac9 100644 --- a/src/comments/comments.plugin.ts +++ b/src/comments/comments.plugin.ts @@ -1,9 +1,7 @@ import { eq } from "drizzle-orm"; import { Elysia, NotFoundError, t } from "elysia"; import { StatusCodes } from "http-status-codes"; -import { articles } from "@/articles/articles.schema"; import { db } from "@/core/database/db"; -import { follows } from "@/profiles/profiles.schema"; import { RealWorldError } from "@/shared/errors"; import { auth } from "@/shared/plugins"; import { commentsModel, UUID } from "./comments.model"; @@ -27,15 +25,13 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) const enrichedComments = await db.query.comments.findMany({ with: { - author: currentUserId - ? { - with: { - followers: { - where: { followerId: currentUserId }, - }, - }, - } - : true, + author: { + with: { + followers: { + where: {}, + }, + }, + }, }, where: { articleId: article.id }, orderBy: { createdAt: "desc" }, diff --git a/src/core/database/relations.ts b/src/core/database/relations.ts index 0e0ccd7..959d45e 100644 --- a/src/core/database/relations.ts +++ b/src/core/database/relations.ts @@ -15,52 +15,105 @@ const schema = { favorites, }; -export const relations = defineRelations( - schema, - ({ - one, - many, - articles, - users, - comments, - articlesToTags, - favorites, - tags, - }) => ({ - articles: { - user: one.users({ - from: articles.authorId, - to: users.id, - alias: "articles_authorId_users_id", - }), - usersViaComments: many.users({ - from: articles.id.through(comments.articleId), - to: users.id.through(comments.authorId), - alias: "articles_id_users_id_via_comments", - }), - tags: many.tags({ - from: articles.id.through(articlesToTags.articleId), - to: tags.id.through(articlesToTags.tagId), - }), - usersViaFavorites: many.users({ - from: articles.id.through(favorites.articleId), - to: users.id.through(favorites.userId), - alias: "articles_id_users_id_via_favorites", - }), - }, - users: { - articlesAuthorId: many.articles({ - alias: "articles_authorId_users_id", - }), - articlesViaComments: many.articles({ - alias: "articles_id_users_id_via_comments", - }), - articlesViaFavorites: many.articles({ - alias: "articles_id_users_id_via_favorites", - }), - }, - tags: { - articles: many.articles(), - }, - }), -); +export const relations = defineRelations(schema, (r) => ({ + // Articles relations + articles: { + author: r.one.users({ + from: r.articles.authorId, + to: r.users.id, + }), + tags: r.many.tags({ + from: r.articles.id.through(r.articlesToTags.articleId), + to: r.tags.id.through(r.articlesToTags.tagId), + }), + comments: r.many.comments({ + from: r.articles.id, + to: r.comments.articleId, + }), + favorites: r.many.favorites({ + from: r.articles.id, + to: r.favorites.articleId, + }), + }, + + // Users relations + users: { + // One-to-many: users can have many articles, comments, favorites + articles: r.many.articles({ + from: r.users.id, + to: r.articles.authorId, + }), + comments: r.many.comments({ + from: r.users.id, + to: r.comments.authorId, + }), + favorites: r.many.favorites({ + from: r.users.id, + to: r.favorites.userId, + }), + + // Many-to-many: users can follow many users and be followed by many users + following: r.many.users({ + from: r.users.id.through(r.follows.followerId), + to: r.users.id.through(r.follows.followedId), + }), + followers: r.many.users({ + from: r.users.id.through(r.follows.followedId), + to: r.users.id.through(r.follows.followerId), + }), + }, + + // Comments relations + comments: { + author: r.one.users({ + from: r.comments.authorId, + to: r.users.id, + }), + article: r.one.articles({ + from: r.comments.articleId, + to: r.articles.id, + }), + }, + + // Tags relations + tags: { + articles: r.many.articles({ + from: r.tags.id.through(r.articlesToTags.tagId), + to: r.articles.id.through(r.articlesToTags.articleId), + }), + }, + + // Junction table relations (if needed for direct access) + follows: { + follower: r.one.users({ + from: r.follows.followerId, + to: r.users.id, + }), + followed: r.one.users({ + from: r.follows.followedId, + to: r.users.id, + }), + }, + + articlesToTags: { + article: r.one.articles({ + from: r.articlesToTags.articleId, + to: r.articles.id, + }), + tag: r.one.tags({ + from: r.articlesToTags.tagId, + to: r.tags.id, + }), + }, + + favorites: { + user: r.one.users({ + from: r.favorites.userId, + to: r.users.id, + }), + article: r.one.articles({ + from: r.favorites.articleId, + to: r.articles.id, + }), + }, +})); diff --git a/src/profiles/profiles.schema.ts b/src/profiles/profiles.schema.ts index 4cea6bc..90adc27 100644 --- a/src/profiles/profiles.schema.ts +++ b/src/profiles/profiles.schema.ts @@ -1,6 +1,7 @@ import { sql } from "drizzle-orm"; import { check, + index, pgTable, primaryKey, timestamp, @@ -8,15 +9,19 @@ import { } from "drizzle-orm/pg-core"; import { users } from "@/users/users.schema"; +/** + * Junction table for many-to-many relationship between users (following/followers) + * This implements the users-to-users relationship through the follows table + */ export const follows = pgTable( "follows", { followerId: uuid("follower_id") .notNull() - .references(() => users.id), + .references(() => users.id, { onDelete: "cascade" }), followedId: uuid("followed_id") .notNull() - .references(() => users.id), + .references(() => users.id, { onDelete: "cascade" }), createdAt: timestamp("created_at").notNull().defaultNow(), updatedAt: timestamp("updated_at") .notNull() @@ -24,7 +29,17 @@ export const follows = pgTable( .$onUpdate(() => new Date()), }, (table) => [ + // Primary key on both foreign key columns primaryKey({ columns: [table.followerId, table.followedId] }), + // Individual indexes for single-side queries + index("follows_follower_id_idx").on(table.followerId), + index("follows_followed_id_idx").on(table.followedId), + // Composite index for efficient many-to-many relationship queries + index("follows_follower_followed_idx").on( + table.followerId, + table.followedId, + ), + // Prevent self-following check( "unique_follower_following", sql`${table.followerId} != ${table.followedId}`, From e59114e01c7bd365ecba12b4a4ef329d7baa8ae6 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Sun, 29 Jun 2025 23:55:20 +0500 Subject: [PATCH 02/13] Refactor articles and comments plugins to standardize query syntax and enhance clarity This update modifies the articles and comments plugins by replacing the `where` clause syntax in database queries with a consistent object structure. Additionally, it simplifies the handling of tags and improves the logic for enriching comments. These changes enhance code readability and maintainability while ensuring accurate data retrieval for articles and comments. --- src/articles/articles.plugin.ts | 46 +++++++++++++++++++++---------- src/comments/comments.plugin.ts | 31 +++++++++++++++++---- src/core/database/relations.ts | 1 + src/core/plugins/errors.plugin.ts | 4 +-- 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/articles/articles.plugin.ts b/src/articles/articles.plugin.ts index 4a10bd1..e1b1d69 100644 --- a/src/articles/articles.plugin.ts +++ b/src/articles/articles.plugin.ts @@ -93,12 +93,14 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) ? { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, } : true, - tags: { with: { tag: true } }, + tags: true, favorites: true, }, // orderBy: [desc(articles.createdAt)], @@ -133,12 +135,14 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) ? { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, } : true, - tags: { with: { tag: true } }, + tags: true, }, }); @@ -186,11 +190,13 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) author: { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, }, - tags: { with: { tag: true } }, + tags: true, favorites: true, }, orderBy: { @@ -353,11 +359,13 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) author: { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, }, - tags: { with: { tag: true } }, + tags: true, favorites: true, // Load all favorites to get count }, }); @@ -427,11 +435,13 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) author: { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, }, - tags: { with: { tag: true } }, + tags: true, favorites: true, // Load all favorites to get count }, }); @@ -458,7 +468,9 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) author: { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, }, @@ -492,11 +504,13 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) author: { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, }, - tags: { with: { tag: true } }, + tags: true, favorites: true, // Load all favorites to get count }, }); @@ -527,11 +541,13 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) author: { with: { followers: { - where: eq(follows.followerId, currentUserId), + where: { + id: currentUserId, + }, }, }, }, - tags: { with: { tag: true } }, + tags: true, favorites: true, }, }); diff --git a/src/comments/comments.plugin.ts b/src/comments/comments.plugin.ts index 10c5ac9..c9c7d66 100644 --- a/src/comments/comments.plugin.ts +++ b/src/comments/comments.plugin.ts @@ -28,7 +28,9 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) author: { with: { followers: { - where: {}, + where: { + id: currentUserId ?? undefined, + }, }, }, }, @@ -38,14 +40,16 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) }); const firstComment = enrichedComments[0]; + console.log(firstComment); if (firstComment) { - const followers = firstComment.author?.followers; + const followers = firstComment.author.followers; + console.log(followers); enrichedComments.forEach((comment) => { comment.author.followers = followers; }); } - return toCommentsResponse(enrichedComments, { currentUserId }); + return toCommentsResponse(transformedComments, { currentUserId }); }, { detail: { @@ -96,7 +100,9 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) author: { with: { followers: { - where: { followerId: currentUserId }, + where: { + id: currentUserId, + }, }, }, }, @@ -106,7 +112,22 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) throw new NotFoundError("comment"); } - return toCommentResponse(enrichedComment, { currentUserId }); + // Transform to match EnrichedComment type + const transformedComment = { + ...enrichedComment, + author: { + ...enrichedComment.author!, + followers: + enrichedComment.author!.followers?.map((follower) => ({ + followerId: follower.id, + followedId: enrichedComment.authorId, + createdAt: new Date(), + updatedAt: new Date(), + })) || undefined, + }, + }; + + return toCommentResponse(transformedComment, { currentUserId }); }, { detail: { diff --git a/src/core/database/relations.ts b/src/core/database/relations.ts index 959d45e..86d7a31 100644 --- a/src/core/database/relations.ts +++ b/src/core/database/relations.ts @@ -68,6 +68,7 @@ export const relations = defineRelations(schema, (r) => ({ author: r.one.users({ from: r.comments.authorId, to: r.users.id, + optional: false, }), article: r.one.articles({ from: r.comments.articleId, diff --git a/src/core/plugins/errors.plugin.ts b/src/core/plugins/errors.plugin.ts index e58bf49..d035a43 100644 --- a/src/core/plugins/errors.plugin.ts +++ b/src/core/plugins/errors.plugin.ts @@ -1,4 +1,4 @@ -import { DrizzleQueryError } from "drizzle-orm/errors"; +import { DrizzleError } from "drizzle-orm/errors"; import { type Elysia, NotFoundError, ValidationError } from "elysia"; import { pick } from "radashi"; import { DEFAULT_ERROR_MESSAGE } from "@/shared/constants"; @@ -29,7 +29,7 @@ export const errors = (app: Elysia) => } // db errors - if (error instanceof DrizzleQueryError) { + if (error instanceof DrizzleError) { return formatDBError(error); } From 4cc97149091f0251fe491824a11b1d899005cd49 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 00:00:15 +0500 Subject: [PATCH 03/13] Refactor articles plugin to standardize query syntax and improve type definitions This update modifies the articles plugin by replacing the `where` clause syntax in database queries with a consistent object structure. Additionally, it enhances type definitions for enriched articles by refining the handling of followers and tags. These changes improve code clarity and maintainability while ensuring accurate data retrieval for articles and their relationships. --- src/articles/articles.plugin.ts | 45 +++++++++++-------- .../interfaces/enriched-article.interface.ts | 13 ++---- src/core/database/relations.ts | 1 + 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/articles/articles.plugin.ts b/src/articles/articles.plugin.ts index e1b1d69..d63ef61 100644 --- a/src/articles/articles.plugin.ts +++ b/src/articles/articles.plugin.ts @@ -89,24 +89,19 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) RAW: filters.length > 0 ? and(...filters) : undefined, }, with: { - author: currentUserId - ? { - with: { - followers: { - where: { - id: currentUserId, - }, - }, + author: { + with: { + followers: { + where: { + id: currentUserId ?? undefined, }, - } - : true, + }, + }, + }, tags: true, favorites: true, }, - // orderBy: [desc(articles.createdAt)], - orderBy: { - createdAt: "desc", - }, + orderBy: { createdAt: "desc" }, limit, offset, }); @@ -185,7 +180,11 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) // Get articles from followed authors const enrichedArticles = await db.query.articles.findMany({ - where: inArray(articles.authorId, followedIds), + where: { + authorId: { + in: followedIds, + }, + }, with: { author: { with: { @@ -231,7 +230,11 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) .onConflictDoNothing(); const relevantTags = await db.query.tags.findMany({ - where: inArray(tags.name, tagList), + where: { + name: { + in: tagList, + }, + }, }); const [createdArticle] = await db @@ -261,7 +264,7 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) where: { id: createdArticle.id }, with: { author: true, - tags: { with: { tag: true } }, + tags: true, }, }); @@ -339,7 +342,11 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) // Get all relevant tags in one query const relevantTags = await db.query.tags.findMany({ - where: inArray(tags.name, article.tagList), + where: { + name: { + in: article.tagList, + }, + }, }); // Connect tags to article @@ -474,7 +481,7 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) }, }, }, - tags: { with: { tag: true } }, + tags: true, favorites: true, }, }); diff --git a/src/articles/interfaces/enriched-article.interface.ts b/src/articles/interfaces/enriched-article.interface.ts index 23b81fb..d73b095 100644 --- a/src/articles/interfaces/enriched-article.interface.ts +++ b/src/articles/interfaces/enriched-article.interface.ts @@ -1,6 +1,5 @@ import type { InferSelectModel } from "drizzle-orm"; -import type { follows } from "@/profiles/profiles.schema"; -import type { articlesToTags, tags } from "@/tags/tags.schema"; +import type { tags } from "@/tags/tags.schema"; import type { users } from "@/users/users.schema"; import type { articles, favorites } from "../articles.schema"; @@ -9,13 +8,9 @@ import type { articles, favorites } from "../articles.schema"; */ export type EnrichedArticle = InferSelectModel & { author: InferSelectModel & { - followers?: Array>; + followers?: Array>; }; - tags: Array< - InferSelectModel & { - tag: InferSelectModel; - } - >; + tags: Array>; favorites?: Array>; }; @@ -24,7 +19,7 @@ export type EnrichedArticle = InferSelectModel & { */ export type PersonalizedEnrichedArticle = EnrichedArticle & { author: InferSelectModel & { - followers?: Array>; + followers?: Array>; }; favorites: Array>; }; diff --git a/src/core/database/relations.ts b/src/core/database/relations.ts index 86d7a31..324d2c2 100644 --- a/src/core/database/relations.ts +++ b/src/core/database/relations.ts @@ -21,6 +21,7 @@ export const relations = defineRelations(schema, (r) => ({ author: r.one.users({ from: r.articles.authorId, to: r.users.id, + optional: false, }), tags: r.many.tags({ from: r.articles.id.through(r.articlesToTags.articleId), From d1ede43f7718ab8af29331298ab332552da5b877 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 00:00:52 +0500 Subject: [PATCH 04/13] Fix follower filtering logic and update tag mapping in articles response This update corrects the filtering logic for followers by changing the property accessed from `followerId` to `id`, ensuring accurate identification of current user follows. Additionally, it updates the tag mapping to directly access the tag name, enhancing clarity and maintainability of the articles response structure. --- src/articles/mappers/to-articles-response.ts | 5 ++--- src/articles/mappers/to-response.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/articles/mappers/to-articles-response.ts b/src/articles/mappers/to-articles-response.ts index fd6e819..bb6812c 100644 --- a/src/articles/mappers/to-articles-response.ts +++ b/src/articles/mappers/to-articles-response.ts @@ -14,8 +14,7 @@ export function toArticlesResponse( const myFavorites = article.favorites?.filter((f) => f.userId === currentUserId) ?? []; const myFollows = - article.author.followers?.filter((f) => f.followerId === currentUserId) ?? - []; + article.author.followers?.filter((f) => f.id === currentUserId) ?? []; const favoritesCount = article.favorites?.length ?? 0; const isFavorited = myFavorites.length > 0; const isFollowing = myFollows.length > 0; @@ -24,7 +23,7 @@ export function toArticlesResponse( title: article.title, description: article.description, tagList: article.tags - .map((t) => t.tag.name) + .map((t) => t.name) .sort((a, b) => a.localeCompare(b)), createdAt: article.createdAt.toISOString(), updatedAt: article.updatedAt.toISOString(), diff --git a/src/articles/mappers/to-response.ts b/src/articles/mappers/to-response.ts index b6e64a0..a3f384c 100644 --- a/src/articles/mappers/to-response.ts +++ b/src/articles/mappers/to-response.ts @@ -39,7 +39,7 @@ export function toResponse( const favorited = article.favorites?.some((f) => f.userId === currentUserId); const favoritesCount = article.favorites?.length ?? 0; const following = article.author.followers?.some( - (f) => f.followerId === currentUserId, + (f) => f.id === currentUserId, ); return { @@ -49,7 +49,7 @@ export function toResponse( description: article.description, body: article.body, tagList: article.tags - .map((t) => t.tag.name) + .map((t) => t.name) .sort((a, b) => a.localeCompare(b)), createdAt: article.createdAt.toISOString(), updatedAt: article.updatedAt.toISOString(), From d643ae8e9b5ae97f717c57bc5ef17e2419fbf261 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 00:07:08 +0500 Subject: [PATCH 05/13] Refactor articles and profiles plugins to improve code clarity and maintainability This update comments out the authentication middleware in the articles plugin for potential future use, while also removing unused imports in the profiles plugin. These changes enhance code readability and maintainability across both plugins. --- src/profiles/profiles.plugin.ts | 1 - src/shared/plugins/auth.plugin.ts | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/profiles/profiles.plugin.ts b/src/profiles/profiles.plugin.ts index 4821e4d..1eb642b 100644 --- a/src/profiles/profiles.plugin.ts +++ b/src/profiles/profiles.plugin.ts @@ -4,7 +4,6 @@ import { StatusCodes } from "http-status-codes"; import { db } from "@/core/database/db"; import { RealWorldError } from "@/shared/errors"; import { auth } from "@/shared/plugins"; -import { users } from "@/users/users.schema"; import { toResponse } from "./mappers"; import { profilesModel } from "./profiles.model"; import { follows } from "./profiles.schema"; diff --git a/src/shared/plugins/auth.plugin.ts b/src/shared/plugins/auth.plugin.ts index 1e8d3d0..0cca325 100644 --- a/src/shared/plugins/auth.plugin.ts +++ b/src/shared/plugins/auth.plugin.ts @@ -1,10 +1,8 @@ -import { eq } from "drizzle-orm"; import { Elysia, t } from "elysia"; import { StatusCodes } from "http-status-codes"; import { db } from "@/core/database/db"; import { env } from "@/core/env"; import { RealWorldError } from "@/shared/errors"; -import { users } from "@/users/users.schema"; import { name } from "../../../package.json"; import jwt from "./jwt.plugin"; import { token } from "./token.plugin"; @@ -64,7 +62,9 @@ export const auth = new Elysia() // Check user exists in DB const user = await db.query.users.findFirst({ - where: eq(users.id, currentUserId), + where: { + id: currentUserId, + }, }); if (!user) { throw new RealWorldError(StatusCodes.UNAUTHORIZED, { From 56f5ce5b29cda75026ab72a4abcd0d00227b5ff1 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 00:09:56 +0500 Subject: [PATCH 06/13] Refactor comments plugin to simplify comment response handling and improve type definitions This update streamlines the comment response logic by removing unnecessary transformations and directly returning enriched comments. Additionally, it refines the type definition for followers in the enriched comment interface, enhancing clarity and maintainability of the comments plugin. --- src/comments/comments.plugin.ts | 30 ++----------------- .../interfaces/enriched-comment.interface.ts | 3 +- src/comments/mappers/to-response.mapper.ts | 2 +- src/shared/plugins/auth.plugin.ts | 2 +- 4 files changed, 5 insertions(+), 32 deletions(-) diff --git a/src/comments/comments.plugin.ts b/src/comments/comments.plugin.ts index c9c7d66..8f87a0d 100644 --- a/src/comments/comments.plugin.ts +++ b/src/comments/comments.plugin.ts @@ -38,18 +38,7 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) where: { articleId: article.id }, orderBy: { createdAt: "desc" }, }); - - const firstComment = enrichedComments[0]; - console.log(firstComment); - if (firstComment) { - const followers = firstComment.author.followers; - console.log(followers); - enrichedComments.forEach((comment) => { - comment.author.followers = followers; - }); - } - - return toCommentsResponse(transformedComments, { currentUserId }); + return toCommentsResponse(enrichedComments, { currentUserId }); }, { detail: { @@ -112,22 +101,7 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) throw new NotFoundError("comment"); } - // Transform to match EnrichedComment type - const transformedComment = { - ...enrichedComment, - author: { - ...enrichedComment.author!, - followers: - enrichedComment.author!.followers?.map((follower) => ({ - followerId: follower.id, - followedId: enrichedComment.authorId, - createdAt: new Date(), - updatedAt: new Date(), - })) || undefined, - }, - }; - - return toCommentResponse(transformedComment, { currentUserId }); + return toCommentResponse(enrichedComment, { currentUserId }); }, { detail: { diff --git a/src/comments/interfaces/enriched-comment.interface.ts b/src/comments/interfaces/enriched-comment.interface.ts index aca41ef..f9c1acc 100644 --- a/src/comments/interfaces/enriched-comment.interface.ts +++ b/src/comments/interfaces/enriched-comment.interface.ts @@ -1,10 +1,9 @@ import type { InferSelectModel } from "drizzle-orm"; -import type { follows } from "@/profiles/profiles.schema"; import type { users } from "@/users/users.schema"; import type { comments } from "../comments.schema"; export type EnrichedComment = InferSelectModel & { author: InferSelectModel & { - followers?: InferSelectModel[]; + followers?: Array>; }; }; diff --git a/src/comments/mappers/to-response.mapper.ts b/src/comments/mappers/to-response.mapper.ts index 7365c2e..7b06f56 100644 --- a/src/comments/mappers/to-response.mapper.ts +++ b/src/comments/mappers/to-response.mapper.ts @@ -37,7 +37,7 @@ export function toCommentResponse( following: Boolean( currentUserId && enrichedComment.author.followers?.some( - (f) => f.followerId === currentUserId, + (f) => f.id === currentUserId, ), ), }, diff --git a/src/shared/plugins/auth.plugin.ts b/src/shared/plugins/auth.plugin.ts index 0cca325..8e4e9d7 100644 --- a/src/shared/plugins/auth.plugin.ts +++ b/src/shared/plugins/auth.plugin.ts @@ -37,7 +37,7 @@ export const auth = new Elysia() iat: Math.floor(Date.now() / 1000), })) satisfies SignFn, jwtPayload: jwtPayload || null, - currentUserId: jwtPayload ? jwtPayload.uid : null, + currentUserId: jwtPayload ? jwtPayload.uid : undefined, }, }; }) From 2736fe22be684a1771db513998fb2b2cb761bd99 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:00:31 +0500 Subject: [PATCH 07/13] Refactor database reset script and articles plugin for improved clarity and maintainability This update enhances the database reset script by replacing the previous reset logic with a more robust SQL query that drops all tables and enums. Additionally, the articles plugin is refactored to simplify the filtering logic for authors, favorites, and tags, ensuring a more consistent and readable query structure. These changes improve code clarity and maintainability across the affected files. --- scripts/db/reset.ts | 49 +++++++++------ src/articles/articles.plugin.ts | 108 ++++++++------------------------ src/comments/comments.plugin.ts | 2 +- 3 files changed, 57 insertions(+), 102 deletions(-) diff --git a/scripts/db/reset.ts b/scripts/db/reset.ts index c0a3e4c..cc610a6 100644 --- a/scripts/db/reset.ts +++ b/scripts/db/reset.ts @@ -1,22 +1,35 @@ +import { $ } from "bun"; import chalk from "chalk"; -import { drizzle } from "drizzle-orm/bun-sql"; -import { reset } from "drizzle-seed"; -import * as articlesSchema from "@/articles/articles.schema"; -import * as commentsSchema from "@/comments/comments.schema"; -import { env } from "@/core/env"; -import * as profilesSchema from "@/profiles/profiles.schema"; -import * as tagsSchema from "@/tags/tags.schema"; -import * as usersSchema from "@/users/users.schema"; - -const schema = { - ...usersSchema, - ...profilesSchema, - ...tagsSchema, - ...articlesSchema, - ...commentsSchema, -}; +import { sql } from "drizzle-orm"; +import { db } from "@/core/database"; console.info(chalk.gray("Resetting database")); -// See: https://github.com/drizzle-team/drizzle-orm/issues/3599 -await reset(drizzle(env.DATABASE_URL), schema); +const query = sql` + -- Delete all tables + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + END $$; + + -- Delete enums + DO $$ DECLARE + r RECORD; + BEGIN + FOR r IN (select t.typname as enum_name + from pg_type t + join pg_enum e on t.oid = e.enumtypid + join pg_catalog.pg_namespace n ON n.oid = t.typnamespace + where n.nspname = current_schema()) LOOP + EXECUTE 'DROP TYPE IF EXISTS ' || quote_ident(r.enum_name); + END LOOP; + END $$; + `; + +await db.execute(query); + +// Push the schema to the database +await $`bun run db:push`.quiet(); console.info(`[${chalk.green("✓")}] Database reset complete`); diff --git a/src/articles/articles.plugin.ts b/src/articles/articles.plugin.ts index d63ef61..06f0b54 100644 --- a/src/articles/articles.plugin.ts +++ b/src/articles/articles.plugin.ts @@ -1,8 +1,7 @@ -import { and, eq, inArray } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { Elysia, NotFoundError, t } from "elysia"; import { StatusCodes } from "http-status-codes"; import { db } from "@/core/database/db"; -import { follows } from "@/profiles/profiles.schema"; import { DEFAULT_LIMIT, DEFAULT_OFFSET } from "@/shared/constants"; import { RealWorldError } from "@/shared/errors"; import { auth } from "@/shared/plugins"; @@ -29,71 +28,26 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) }, auth: { currentUserId }, }) => { - const [authorUser, favoritedUser] = await Promise.all([ - authorUsername - ? db.query.users.findFirst({ - // where: eq(users.username, authorUsername), - // relational queries v2: - where: { - username: authorUsername, - }, - }) - : undefined, - favoritedByUsername - ? db.query.users.findFirst({ - where: { - username: favoritedByUsername, - }, - }) - : undefined, - ]); - - if ( - (authorUsername && !authorUser) || - (favoritedByUsername && !favoritedUser) - ) { - return toArticlesResponse([]); - } - - // Build dynamic filters - const filters = []; - if (authorUser) { - filters.push(eq(articles.authorId, authorUser.id)); - } - if (favoritedUser) { - filters.push( - inArray( - articles.id, - db - .select({ articleId: favorites.articleId }) - .from(favorites) - .where(eq(favorites.userId, favoritedUser.id)), - ), - ); - } - if (tagName) { - filters.push( - inArray( - articles.id, - db - .select({ articleId: articlesToTags.articleId }) - .from(articlesToTags) - .innerJoin(tags, eq(tags.id, articlesToTags.tagId)) - .where(eq(tags.name, tagName)), - ), - ); - } - const enrichedArticles = await db.query.articles.findMany({ where: { - RAW: filters.length > 0 ? and(...filters) : undefined, + author: { + username: authorUsername, + }, + favorites: { + user: { + username: favoritedByUsername, + }, + }, + tags: { + name: tagName, + }, }, with: { author: { with: { followers: { where: { - id: currentUserId ?? undefined, + id: currentUserId, }, }, }, @@ -105,7 +59,6 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) limit, offset, }); - return toArticlesResponse(enrichedArticles, { currentUserId, }); @@ -126,17 +79,15 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) const enrichedArticle = await db.query.articles.findFirst({ where: { slug }, with: { - author: currentUserId - ? { - with: { - followers: { - where: { - id: currentUserId, - }, - }, + author: { + with: { + followers: { + where: { + id: currentUserId, }, - } - : true, + }, + }, + }, tags: true, }, }); @@ -169,20 +120,12 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) query: { limit = DEFAULT_LIMIT, offset = DEFAULT_OFFSET }, auth: { currentUserId }, }) => { - // Get followed user IDs - const followed = await db - .select({ followedId: follows.followedId }) - .from(follows) - .where(eq(follows.followerId, currentUserId)); - - const followedIds = followed.map((f) => f.followedId); - if (followedIds.length === 0) return toArticlesResponse([]); - - // Get articles from followed authors const enrichedArticles = await db.query.articles.findMany({ where: { - authorId: { - in: followedIds, + author: { + followers: { + id: currentUserId, + }, }, }, with: { @@ -204,7 +147,6 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) limit, offset, }); - return toArticlesResponse(enrichedArticles, { currentUserId }); }, { diff --git a/src/comments/comments.plugin.ts b/src/comments/comments.plugin.ts index 8f87a0d..710edb8 100644 --- a/src/comments/comments.plugin.ts +++ b/src/comments/comments.plugin.ts @@ -29,7 +29,7 @@ export const commentsPlugin = new Elysia({ tags: ["Comments"] }) with: { followers: { where: { - id: currentUserId ?? undefined, + id: currentUserId, }, }, }, From d2277d483d2810e4b8bb98ac8228b2100e543071 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:04:01 +0500 Subject: [PATCH 08/13] Refactor articles plugin to enhance query filtering logic and improve maintainability This update simplifies the filtering logic in the articles plugin by utilizing conditional object spreading for author, favorites, and tags. These changes improve code clarity and maintainability, ensuring a more consistent query structure for retrieving enriched articles. --- src/articles/articles.plugin.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/articles/articles.plugin.ts b/src/articles/articles.plugin.ts index 06f0b54..5ae6575 100644 --- a/src/articles/articles.plugin.ts +++ b/src/articles/articles.plugin.ts @@ -30,17 +30,23 @@ export const articlesPlugin = new Elysia({ tags: ["Articles"] }) }) => { const enrichedArticles = await db.query.articles.findMany({ where: { - author: { - username: authorUsername, - }, - favorites: { - user: { - username: favoritedByUsername, + ...(authorUsername && { + author: { + username: authorUsername, }, - }, - tags: { - name: tagName, - }, + }), + ...(favoritedByUsername && { + favorites: { + user: { + username: favoritedByUsername, + }, + }, + }), + ...(tagName && { + tags: { + name: tagName, + }, + }), }, with: { author: { From 7fd97f14bbc8184e2711c88613e6d04a8ae726f5 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:06:01 +0500 Subject: [PATCH 09/13] Update architecture and documentation for beta branch transition This commit updates the links in the ARCHITECTURE.md and README.md files to point to the beta branch of Bedstack (Stripped). Additionally, it adds a note in the README to inform users that they are viewing the beta branch. The GitHub workflows for code quality and tests are also modified to include the beta branch, ensuring proper CI/CD processes for this version. --- .github/workflows/code-quality.yml | 1 + .github/workflows/tests.yml | 1 + ARCHITECTURE.md | 4 ++-- README.md | 5 ++++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 14f8e53..72eff17 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -6,6 +6,7 @@ on: pull_request: branches: - main + - beta jobs: biome: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 76bac50..b751a1d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,7 @@ on: push: branches: - main + - beta paths-ignore: - 'docs/**' workflow_dispatch: diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1a47965..7f36cf9 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ ## Overview -**Bedstack (Stripped)** is a _distilled version_ of [the full Bedstack architecture](https://github.com/bedtime-coders/bedstack/blob/main/ARCHITECTURE.md). It keeps the _feature-sliced, modular structure_ but simplifies the layering for _rapid prototyping_. +**Bedstack (Stripped)** is a _distilled version_ of [the full Bedstack architecture](https://github.com/bedtime-coders/bedstack/blob/beta/ARCHITECTURE.md). It keeps the _feature-sliced, modular structure_ but simplifies the layering for _rapid prototyping_. Each feature is self-contained and designed for clarity, fast development, and maintainability - without the overhead of full enterprise layering. @@ -109,7 +109,7 @@ drizzle/ # Migrations, reset, seed ### See Also -- [Bedstack Full Architecture](https://github.com/bedtime-coders/bedstack/blob/main/ARCHITECTURE.md) +- [Bedstack Full Architecture](https://github.com/bedtime-coders/bedstack/blob/beta/ARCHITECTURE.md) - [ElysiaJS Docs](https://elysiajs.com/docs) - [Drizzle ORM Docs](https://orm.drizzle.team/docs) - [TypeBox Docs](https://typebox.io/docs) diff --git a/README.md b/README.md index c7033a2..76b1d33 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,15 @@ Logo for Bedstack RealWorld example

Bedstack (Stripped)

-[![Tests Status](https://github.com/bedtime-coders/bedstack-stripped/actions/workflows/tests.yml/badge.svg?event=push&branch=main&)](https://github.com/bedtime-coders/bedstac/actions/workflows/tests.yml?query=branch%3Amain+event%3Apush) [![Discord](https://img.shields.io/discord/1164270344115335320?label=Chat&color=5865f4&logo=discord&labelColor=121214)](https://discord.gg/8UcP9QB5AV) [![License](https://custom-icon-badges.demolab.com/github/license/bedtime-coders/bedstack-stripped?label=License&color=blue&logo=law&labelColor=0d1117)](https://github.com/bedtime-coders/bedstack-stripped/blob/main/LICENSE) [![Bun](https://img.shields.io/badge/Bun-14151a?logo=bun&logoColor=fbf0df)](https://bun.sh/) [![ElysiaJS](https://custom-icon-badges.demolab.com/badge/ElysiaJS-0f172b.svg?logo=elysia)](https://elysiajs.com/) [![Drizzle](https://img.shields.io/badge/Drizzle-C5F74F?logo=drizzle&logoColor=000)](https://drizzle.team/) [![Biome](https://img.shields.io/badge/Biome-24272f?logo=biome&logoColor=f6f6f9)](https://biomejs.dev/) [![Scalar](https://img.shields.io/badge/Scalar-080808?logo=scalar&logoColor=e7e7e7)](https://scalar.com/) [![Star](https://custom-icon-badges.demolab.com/github/stars/bedtime-coders/bedstack-stripped?logo=star&logoColor=373737&label=Star)](https://github.com/bedtime-coders/bedstack-stripped/stargazers/) +[![Tests Status](https://github.com/bedtime-coders/bedstack-stripped/actions/workflows/tests.yml/badge.svg?event=push&branch=beta&)](https://github.com/bedtime-coders/bedstac/actions/workflows/tests.yml?query=branch%beta+event%3Apush) [![Discord](https://img.shields.io/discord/1164270344115335320?label=Chat&color=5865f4&logo=discord&labelColor=121214)](https://discord.gg/8UcP9QB5AV) [![License](https://custom-icon-badges.demolab.com/github/license/bedtime-coders/bedstack-stripped?label=License&color=blue&logo=law&labelColor=0d1117)](https://github.com/bedtime-coders/bedstack-stripped/blob/beta/LICENSE) [![Bun](https://img.shields.io/badge/Bun-14151a?logo=bun&logoColor=fbf0df)](https://bun.sh/) [![ElysiaJS](https://custom-icon-badges.demolab.com/badge/ElysiaJS-0f172b.svg?logo=elysia)](https://elysiajs.com/) [![Drizzle](https://img.shields.io/badge/Drizzle-C5F74F?logo=drizzle&logoColor=000)](https://drizzle.team/) [![Biome](https://img.shields.io/badge/Biome-24272f?logo=biome&logoColor=f6f6f9)](https://biomejs.dev/) [![Scalar](https://img.shields.io/badge/Scalar-080808?logo=scalar&logoColor=e7e7e7)](https://scalar.com/) [![Star](https://custom-icon-badges.demolab.com/github/stars/bedtime-coders/bedstack-stripped?logo=star&logoColor=373737&label=Star)](https://github.com/bedtime-coders/bedstack-stripped/stargazers/) ⚡ Stripped version of [Bedstack](https://github.com/bedtime-coders/bedstack) for rapid prototyping +[!IMPORTANT] +> You are viewing the **beta** branch of Bedstack (Stripped). For the main branch, see [Bedstack](https://github.com/bedtime-coders/bedstack). + ## Bedstack: Bun + ElysiaJS + Drizzle Stack **Bedstack** is a collection of bleeding-edge technologies to build modern web applications. From 60381bd6f2faa1548332a9f1dcd1745bd02992f0 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:06:40 +0500 Subject: [PATCH 10/13] Update README to enhance clarity on beta branch visibility and link to main branch --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76b1d33..68f740c 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ -[!IMPORTANT] -> You are viewing the **beta** branch of Bedstack (Stripped). For the main branch, see [Bedstack](https://github.com/bedtime-coders/bedstack). +> [!IMPORTANT] +> You are viewing the **beta** branch of _Bedstack (Stripped)_. Click [here](https://github.com/bedtime-coders/bedstack-stripped/tree/main) to view the main branch. ## Bedstack: Bun + ElysiaJS + Drizzle Stack From fa48928c5cfef2bd1339cf68915099377584d81d Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:08:03 +0500 Subject: [PATCH 11/13] Update README to clarify beta branch usage and link to Drizzle v1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 68f740c..ec2a2ec 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ > [!IMPORTANT] -> You are viewing the **beta** branch of _Bedstack (Stripped)_. Click [here](https://github.com/bedtime-coders/bedstack-stripped/tree/main) to view the main branch. +> You are viewing the **beta** branch of _Bedstack (Stripped)_, where we use the [beta version of Drizzle v1](https://orm.drizzle.team/roadmap). Click [here](https://github.com/bedtime-coders/bedstack-stripped/tree/main) to view the main branch. ## Bedstack: Bun + ElysiaJS + Drizzle Stack From a31933f0eb22a904c6b82e782c2c5e6c0147f5cd Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:13:28 +0500 Subject: [PATCH 12/13] Update documentation and workflows for Drizzle v1 branch transition This commit updates the links in the ARCHITECTURE.md and README.md files to point to the Drizzle v1 branch of Bedstack (Stripped). It also modifies the GitHub workflows for code quality and tests to reflect the new branch, ensuring proper CI/CD processes for this version. Additionally, the README now includes information about the use of Drizzle ORM v1 and its Relational API v2. --- .github/workflows/code-quality.yml | 2 +- .github/workflows/tests.yml | 2 +- ARCHITECTURE.md | 4 ++-- README.md | 11 +++++++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 72eff17..2301476 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -6,7 +6,7 @@ on: pull_request: branches: - main - - beta + - drizzle-v1 jobs: biome: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b751a1d..d31ea3c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: push: branches: - main - - beta + - drizzle-v1 paths-ignore: - 'docs/**' workflow_dispatch: diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7f36cf9..d7dce40 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ ## Overview -**Bedstack (Stripped)** is a _distilled version_ of [the full Bedstack architecture](https://github.com/bedtime-coders/bedstack/blob/beta/ARCHITECTURE.md). It keeps the _feature-sliced, modular structure_ but simplifies the layering for _rapid prototyping_. +**Bedstack (Stripped)** is a _distilled version_ of [the full Bedstack architecture](https://github.com/bedtime-coders/bedstack/blob/drizzle-v1/ARCHITECTURE.md). It keeps the _feature-sliced, modular structure_ but simplifies the layering for _rapid prototyping_. Each feature is self-contained and designed for clarity, fast development, and maintainability - without the overhead of full enterprise layering. @@ -109,7 +109,7 @@ drizzle/ # Migrations, reset, seed ### See Also -- [Bedstack Full Architecture](https://github.com/bedtime-coders/bedstack/blob/beta/ARCHITECTURE.md) +- [Bedstack Full Architecture](https://github.com/bedtime-coders/bedstack/blob/drizzle-v1/ARCHITECTURE.md) - [ElysiaJS Docs](https://elysiajs.com/docs) - [Drizzle ORM Docs](https://orm.drizzle.team/docs) - [TypeBox Docs](https://typebox.io/docs) diff --git a/README.md b/README.md index ec2a2ec..63c6833 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,21 @@ Logo for Bedstack RealWorld example

Bedstack (Stripped)

-[![Tests Status](https://github.com/bedtime-coders/bedstack-stripped/actions/workflows/tests.yml/badge.svg?event=push&branch=beta&)](https://github.com/bedtime-coders/bedstac/actions/workflows/tests.yml?query=branch%beta+event%3Apush) [![Discord](https://img.shields.io/discord/1164270344115335320?label=Chat&color=5865f4&logo=discord&labelColor=121214)](https://discord.gg/8UcP9QB5AV) [![License](https://custom-icon-badges.demolab.com/github/license/bedtime-coders/bedstack-stripped?label=License&color=blue&logo=law&labelColor=0d1117)](https://github.com/bedtime-coders/bedstack-stripped/blob/beta/LICENSE) [![Bun](https://img.shields.io/badge/Bun-14151a?logo=bun&logoColor=fbf0df)](https://bun.sh/) [![ElysiaJS](https://custom-icon-badges.demolab.com/badge/ElysiaJS-0f172b.svg?logo=elysia)](https://elysiajs.com/) [![Drizzle](https://img.shields.io/badge/Drizzle-C5F74F?logo=drizzle&logoColor=000)](https://drizzle.team/) [![Biome](https://img.shields.io/badge/Biome-24272f?logo=biome&logoColor=f6f6f9)](https://biomejs.dev/) [![Scalar](https://img.shields.io/badge/Scalar-080808?logo=scalar&logoColor=e7e7e7)](https://scalar.com/) [![Star](https://custom-icon-badges.demolab.com/github/stars/bedtime-coders/bedstack-stripped?logo=star&logoColor=373737&label=Star)](https://github.com/bedtime-coders/bedstack-stripped/stargazers/) +[![Tests Status](https://github.com/bedtime-coders/bedstack-stripped/actions/workflows/tests.yml/badge.svg?event=push&branch=drizzle-v1&)](https://github.com/bedtime-coders/bedstac/actions/workflows/tests.yml?query=branch%drizzle-v1+event%3Apush) [![Discord](https://img.shields.io/discord/1164270344115335320?label=Chat&color=5865f4&logo=discord&labelColor=121214)](https://discord.gg/8UcP9QB5AV) [![License](https://custom-icon-badges.demolab.com/github/license/bedtime-coders/bedstack-stripped?label=License&color=blue&logo=law&labelColor=0d1117)](https://github.com/bedtime-coders/bedstack-stripped/blob/drizzle-v1/LICENSE) [![Bun](https://img.shields.io/badge/Bun-14151a?logo=bun&logoColor=fbf0df)](https://bun.sh/) [![ElysiaJS](https://custom-icon-badges.demolab.com/badge/ElysiaJS-0f172b.svg?logo=elysia)](https://elysiajs.com/) [![Drizzle](https://img.shields.io/badge/Drizzle-C5F74F?logo=drizzle&logoColor=000)](https://drizzle.team/) [![Biome](https://img.shields.io/badge/Biome-24272f?logo=biome&logoColor=f6f6f9)](https://biomejs.dev/) [![Scalar](https://img.shields.io/badge/Scalar-080808?logo=scalar&logoColor=e7e7e7)](https://scalar.com/) [![Star](https://custom-icon-badges.demolab.com/github/stars/bedtime-coders/bedstack-stripped?logo=star&logoColor=373737&label=Star)](https://github.com/bedtime-coders/bedstack-stripped/stargazers/) ⚡ Stripped version of [Bedstack](https://github.com/bedtime-coders/bedstack) for rapid prototyping > [!IMPORTANT] -> You are viewing the **beta** branch of _Bedstack (Stripped)_, where we use the [beta version of Drizzle v1](https://orm.drizzle.team/roadmap). Click [here](https://github.com/bedtime-coders/bedstack-stripped/tree/main) to view the main branch. +> You are viewing the **`drizzle-v1`** branch of _Bedstack (Stripped)_, where we use [Drizzle ORM v1, currently in beta](https://orm.drizzle.team/roadmap). Click [here](https://github.com/bedtime-coders/bedstack-stripped/tree/main) to view the main branch. + +## Drizzle ORM v1 & Relational API v2 + +This branch uses [Drizzle ORM v1](https://orm.drizzle.team/roadmap), which is currently in beta. The main feature from Drizzle ORM v1 that we use here is Relational API v2 (often referred to as rqbv2). + +- GitHub Discussion to learn more about Relational API v2: https://github.com/drizzle-team/drizzle-orm/discussions/2316. +- Drizzle website page to track the release of Drizzle ORM v1: https://orm.drizzle.team/roadmap. ## Bedstack: Bun + ElysiaJS + Drizzle Stack From 514de6bca424f562b4fe3a9bea88aff8e58ae984 Mon Sep 17 00:00:00 2001 From: Yam Borodetsky Date: Mon, 30 Jun 2025 01:15:09 +0500 Subject: [PATCH 13/13] Refactor error handling utility to update DrizzleQueryError to DrizzleError type This commit modifies the error handling utility by replacing the DrizzleQueryError type with DrizzleError in the formatDBError function. This change aligns the error formatting with the updated type definitions in the Drizzle ORM, ensuring consistency and improved error handling across the application. --- src/shared/errors/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/errors/utils.ts b/src/shared/errors/utils.ts index 58b5629..88ffad4 100644 --- a/src/shared/errors/utils.ts +++ b/src/shared/errors/utils.ts @@ -1,4 +1,4 @@ -import type { DrizzleQueryError } from "drizzle-orm/errors"; +import type { DrizzleError } from "drizzle-orm/errors"; import type { NotFoundError, ValidationError } from "elysia"; import { ConflictingFieldsError } from "./conflicting-fields"; @@ -72,7 +72,7 @@ export function formatNotFoundError(error: NotFoundError) { }; } -export function formatDBError(error: DrizzleQueryError) { +export function formatDBError(error: DrizzleError) { console.error(error); return { errors: {