Skip to content

Layered Architecture#185

Merged
yamcodes merged 28 commits intomainfrom
layered
Jun 20, 2025
Merged

Layered Architecture#185
yamcodes merged 28 commits intomainfrom
layered

Conversation

@yamcodes
Copy link
Collaborator

@yamcodes yamcodes commented Jun 14, 2025

Description

Refactor the project to Layered Architecture.

For context, see ARCHITECTURE.md and PROJECT_STRUCTURE.md.

For roadmap, see https://github.com/agnyz/bedstack/milestone/3.


PR Checklist

  • Read the Developer's Guide in CONTRIBUTING.md
  • Use a concise title to represent the changes introduced in this PR
  • Provide a detailed description of the changes introduced in this PR, and, if necessary, some screenshots
  • Reference an issue or discussion where the feature or changes have been previously discussed
  • Add a failing test that passes with the changes introduced in this PR, or explain why it's not feasible
  • Add documentation for the feature or changes introduced in this PR to the docs; you can run them with bun docs

Summary by CodeRabbit

  • New Features

    • Added detailed architecture and project structure documentation.
    • Introduced new controllers and modules for articles, comments, and profiles with RESTful endpoints.
    • Added comprehensive DTOs for API request validation and response formatting.
    • Implemented robust error handling with standardized error responses.
    • Added new database schemas and relations for articles, comments, favorites, tags, and user follows.
    • Introduced utility functions for validation, date formatting, and slug generation.
  • Bug Fixes

    • Improved type safety and error handling in service and repository layers.
    • Enhanced validation and ownership checks for comment and profile operations.
  • Refactor

    • Replaced plugin-based route registration with controller-based organization.
    • Migrated schema definitions to ORM-based models and updated import paths for consistency.
    • Removed legacy files and outdated type schemas for a cleaner codebase.
    • Simplified service constructors and unified error classes.
  • Documentation

    • Added detailed architecture and project structure documentation.
    • Updated formatting and consistency in various markdown files.
  • Chores

    • Updated dependencies and scripts for database operations and development tooling.
    • Introduced index files for consolidated exports in DTOs, interfaces, mappers, and utilities.
    • Updated TypeScript configuration for module resolution and path aliases.

### Description

<!-- Provide a comprehensive description here about what your PR aims to
solve. -->

<!-- You may also add additional context -->

- Rearranged the architecture into layered architecture, focusing on
articles (making minimal changes in other features just to support the
articles feature)
- Fixed API summaries to match
https://realworld-docs.netlify.app/specifications/backend/endpoints

For context see [the added ARCHITECTURE.md
file](https://github.com/agnyz/bedstack/blob/3bae8b09298d43d0019bbf1609287790d25c82a5/ARCHITECTURE.md).

Continued from #153 

---

### PR Checklist

<!-- Please do not remove this section -->

<!-- Mark each item with an "x" ([ ] becomes [x]) -->

- [x] Read the Developer's Guide in
[CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md)
- [x] Use a concise title to represent the changes introduced in this PR
- [x] Provide a detailed description of the changes introduced in this
PR, and, if necessary, some screenshots
- [x] Reference an issue or discussion where the feature or changes have
been previously discussed
- [x] Add a failing test that passes with the changes introduced in this
PR, or explain why it's not feasible
- [x] Add documentation for the feature or changes introduced in this PR
to the docs; you can run them with `bun docs`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive, RESTful articles and comments API with
full CRUD operations, authentication, and validation.
  - Added support for article feeds, favoriting, and following authors.
  - Enhanced error handling with more descriptive error responses.
- Implemented new endpoints and DTOs for articles, comments, and user
profiles.

- **Bug Fixes**
- Improved type safety and consistency across data models and API
responses.

- **Documentation**
  - Added detailed architecture and project structure documentation.

- **Refactor**
- Restructured codebase for better modularity, type safety, and
maintainability.
- Replaced and reorganized schema and interface definitions for articles
and comments.

- **Chores**
  - Updated configuration files for linting, formatting, and tooling.
  - Enhanced TypeScript configuration and path aliases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 14, 2025

Warning

Rate limit exceeded

@yamcodes has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 22 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between be73c4f and faa81c4.

📒 Files selected for processing (5)
  • src/articles/articles.service.test.ts (2 hunks)
  • src/auth/auth.service.test.ts (1 hunks)
  • src/auth/auth.service.ts (4 hunks)
  • src/profiles/profiles.service.test.ts (3 hunks)
  • src/users/users.service.test.ts (2 hunks)

Walkthrough

This change refactors the codebase into a modular, layered architecture for articles, comments, profiles, and tags, introducing controllers, services, repositories, schemas, DTOs, interfaces, and mappers for each feature. It replaces plugin-based routing with controller-based routing, standardizes error handling, enhances type safety, and adds extensive documentation and utility modules.

Changes

Files / Groups Change Summary
.vscode/settings.json Added TypeScript SDK path to workspace settings.
ARCHITECTURE.md, PROJECT_STRUCTURE.md Added new documentation detailing system architecture and project structure.
CODE_OF_CONDUCT.md, CONTRIBUTING.md, GOVERNANCE.md, README.md, docs/* Formatting, whitespace, and minor markdown adjustments.
db/drop.ts, src/constants.ts, src/database.providers.ts, src/errors.ts Deleted legacy scripts, constants, and provider files.
drizzle.config.ts, package.json Updated Drizzle config, migration paths, dependencies, and scripts.
drizzle/migrations/0009_ambitious_matthew_murdock.sql, drizzle/migrations/meta/* Added/updated migration SQL, snapshot, and journal files.
drizzle/reset.ts Added new script to reset the database by dropping all tables and enums.
drizzle/seed.ts Updated import paths and naming conventions for models and providers.
src/app.module.ts Refactored to use controller-based route registration and improved error handling.
src/articles/* (controllers, services, repositories, schemas, DTOs, interfaces, mappers) Refactored articles feature into layered architecture; replaced plugin with controller; restructured types, schemas, mappers, and error handling.
src/comments/* (controllers, services, repositories, schemas, DTOs, interfaces, mappers) Modularized comments feature with new controller, service, repository, schemas, DTOs, interfaces, and mappers.
src/profiles/* (controllers, services, repositories, schemas, DTOs, interfaces, mappers) Refactored profiles feature into layered structure; replaced plugin with controller; improved domain mapping and error handling.
src/tags/* (DTOs, interfaces, mappers) Added/updated DTOs, interfaces, and mappers for tags feature.
src/common/constants/*, src/common/errors/*, src/common/interfaces/*, src/common/utils/* Introduced new constants, error utilities, interface definitions, and general-purpose utility functions.
src/database/database.providers.ts Added new database provider module with combined schemas and ORM setup.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Repository
    participant Database

    Client->>Controller: HTTP Request (e.g., POST /articles)
    Controller->>Service: Validate & pass request data
    Service->>Repository: Transform & enforce business logic
    Repository->>Database: Query/Mutate data
    Database-->>Repository: Return DB result
    Repository-->>Service: Return data/rows
    Service-->>Controller: Map to domain/DTO, handle errors
    Controller-->>Client: HTTP Response (DTO or error)
Loading

Possibly related PRs

  • bedtime-coders/bedstack#163: Refactors the articles feature into a layered architecture, closely mirroring the modularization and restructuring present here.
  • bedtime-coders/bedstack#169: Modularizes the comments feature, introducing controllers, services, repositories, and DTOs, directly related to the comments restructuring in this PR.
  • bedtime-coders/bedstack#173: Refactors the profiles feature into a layered architecture with controllers and updated domain mappings, paralleling the changes made here.

Suggested reviewers

  • aruaycodes

Poem

The codebase hopped to layers neat,
Controllers, services—oh, what a feat!
Repos and mappers, DTOs in rows,
Documentation grows and grows.
With every feature modular and bright,
This rabbit’s heart leaps with delight!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@yamcodes yamcodes added this to the Layered Architecture milestone Jun 14, 2025
@yamcodes yamcodes added the refactor Code changes that do not alter end functionality, or fix a bug label Jun 14, 2025
@yamcodes yamcodes self-assigned this Jun 14, 2025
yamcodes and others added 8 commits June 20, 2025 18:17
### Description

<!-- Provide a comprehensive description here about what your PR aims to
solve. -->

<!-- You may also add additional context -->

- Refactor Comments into our Layered architecture by creating dedicated
mappers, dto, interface, and controller just like article
- separate mappers into atomic files

---

### PR Checklist

<!-- Please do not remove this section -->

<!-- Mark each item with an "x" ([ ] becomes [x]) -->

- [x] Read the Developer's Guide in
[CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md)
- [x] Use a concise title to represent the changes introduced in this PR
- [x] Provide a detailed description of the changes introduced in this
PR, and, if necessary, some screenshots
- [x] Reference an issue or discussion where the feature or changes have
been previously discussed
- [x] Add a failing test that passes with the changes introduced in this
PR, or explain why it's not feasible
- [x] Add documentation for the feature or changes introduced in this PR
to the docs; you can run them with `bun docs`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a dedicated comments API with endpoints to create,
retrieve, and delete comments on articles.
  - Added structured response formats for single and multiple comments.

- **Refactor**
- Separated comment-related routes from the articles API to a standalone
comments controller.
- Improved data mapping for articles and comments using dedicated mapper
functions and centralized exports.

- **Bug Fixes**
  - Corrected import paths for comments schema to ensure consistency.

- **Chores**
- Enhanced type safety and maintainability with new and updated
interfaces for comments.
  - Updated documentation and response schemas for improved API clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
### Description

<!-- Provide a comprehensive description here about what your PR aims to
solve. -->

<!-- You may also add additional context -->

Refactor Tags to Layered Architecture.

---

### PR Checklist

<!-- Please do not remove this section -->

<!-- Mark each item with an "x" ([ ] becomes [x]) -->

- [x] Read the Developer's Guide in
[CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md)
- [x] Use a concise title to represent the changes introduced in this PR
- [x] Provide a detailed description of the changes introduced in this
PR, and, if necessary, some screenshots
- [x] Reference an issue or discussion where the feature or changes have
been previously discussed
- [x] Add a failing test that passes with the changes introduced in this
PR, or explain why it's not feasible
- [x] Add documentation for the feature or changes introduced in this PR
to the docs; you can run them with `bun docs`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced controllers for articles, comments, and tags, enabling
RESTful endpoints for managing articles, comments, and tags.
- Added comprehensive DTOs and mappers for structured request/response
validation and data transformation.
- Implemented modular architecture with clear separation of concerns for
controllers, services, repositories, and mappers.
  - Added detailed error handling with standardized error responses.

- **Improvements**
- Refactored the codebase to enhance type safety, modularity, and
maintainability.
- Updated service and repository methods for better consistency and
clarity.
- Improved project structure and documentation for easier navigation and
development.

- **Documentation**
- Added detailed architecture and project structure documentation to
guide contributors and maintainers.

- **Chores**
- Added and updated configuration files for linting, formatting, and
project tooling.
- Updated TypeScript configuration and path aliases for improved
developer experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
### Description

<!-- Provide a comprehensive description here about what your PR aims to
solve. -->

<!-- You may also add additional context -->

Refactor of Users to Layered Architecture

---

### PR Checklist

<!-- Please do not remove this section -->

<!-- Mark each item with an "x" ([ ] becomes [x]) -->

- [x] Read the Developer's Guide in
[CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md)
- [x] Use a concise title to represent the changes introduced in this PR
- [x] Provide a detailed description of the changes introduced in this
PR, and, if necessary, some screenshots
- [x] Reference an issue or discussion where the feature or changes have
been previously discussed
- [x] Add a failing test that passes with the changes introduced in this
PR, or explain why it's not feasible
- [x] Add documentation for the feature or changes introduced in this PR
to the docs; you can run them with `bun docs`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced new user registration, login, profile retrieval, and
profile update endpoints following the RealWorld API specification.
- Added comprehensive data validation and response formatting for
user-related operations.
- Implemented new data structures and type definitions for user and tag
entities to improve consistency and type safety.

- **Refactor**
- Replaced the previous user plugin with a new controller-based approach
for handling user routes.
- Updated internal mappings and service logic for better separation of
concerns and maintainability.

- **Chores**
- Consolidated and reorganized type definitions, data transfer objects,
and mappers for easier import and improved code structure.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
### Description

<!-- Provide a comprehensive description here about what your PR aims to
solve. -->

<!-- You may also add additional context -->

Refactor Profiles into Layered Architecture

Closes #167 

---

### PR Checklist

<!-- Please do not remove this section -->

<!-- Mark each item with an "x" ([ ] becomes [x]) -->

- [x] Read the Developer's Guide in
[CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md)
- [x] Use a concise title to represent the changes introduced in this PR
- [x] Provide a detailed description of the changes introduced in this
PR, and, if necessary, some screenshots
- [x] Reference an issue or discussion where the feature or changes have
been previously discussed
- [x] Add a failing test that passes with the changes introduced in this
PR, or explain why it's not feasible
- [x] Add documentation for the feature or changes introduced in this PR
to the docs; you can run them with `bun docs`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Improved consistency and structure of profile data returned by the
API, including clearer typing for profile responses.
- **Bug Fixes**
- Corrected a typo in the internal variable name for the profiles
repository.
- **Refactor**
- Unified naming conventions and response handling for profile
endpoints.
- Centralized and standardized profile data transformation and mapping.
- Enhanced route descriptions and response schemas for profile-related
endpoints.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
### Description

<!-- Provide a comprehensive description here about what your PR aims to
solve. -->

<!-- You may also add additional context -->

Misc refactor to Layered Architecture

Closes #168 

---

### PR Checklist

<!-- Please do not remove this section -->

<!-- Mark each item with an "x" ([ ] becomes [x]) -->

- [x] Read the Developer's Guide in
[CONTRIBUTING.md](https://github.com/agnyz/bedstack/blob/main/CONTRIBUTING.md)
- [x] Use a concise title to represent the changes introduced in this PR
- [x] Provide a detailed description of the changes introduced in this
PR, and, if necessary, some screenshots
- [x] Reference an issue or discussion where the feature or changes have
been previously discussed
- [x] Add a failing test that passes with the changes introduced in this
PR, or explain why it's not feasible
- [x] Add documentation for the feature or changes introduced in this PR
to the docs; you can run them with `bun docs`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced standardized error handling with a new `RealWorldError`
class and structured error responses.
- Added utility modules for error formatting, date operations, and
validation tasks.
- Added reusable interface types for API responses, pagination, and
common entities.
- Centralized and expanded constants for pagination and error messages.

- **Refactor**
- Unified and simplified error handling across services, replacing
custom error classes with standardized errors and HTTP status codes.
- Updated import paths throughout the codebase for consistency using a
single alias pattern.
- Reorganized and clarified project structure documentation and code
organization.
- Improved and centralized constants and utility exports for easier
access.

- **Bug Fixes**
- Fixed broken Markdown links and standardized code block formatting in
documentation.

- **Chores**
  - Simplified TypeScript path aliases in configuration.
- Cleaned up whitespace and improved formatting in all documentation
files.

- **Documentation**
- Expanded and clarified project structure, usage guidelines, and added
new documentation for utilities and interfaces.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…s-env and drizzle-seed, and remove @faker-js/faker. Adjust undici-types version and add new dependencies including isexe, path-key, and shebang-command.
…oving comments and users imports to align with the layered architecture.
@yamcodes yamcodes marked this pull request as ready for review June 20, 2025 14:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

♻️ Duplicate comments (1)
src/articles/articles.service.ts (1)

143-155: Apply same database constraint solution for update race condition

This has the same race condition issue as the create method. The slug uniqueness should be enforced at the database level.

🧹 Nitpick comments (23)
CONTRIBUTING.md (1)

30-30: Trim trailing whitespace
Removed an extra space at the end of the Biome description. Consider hyphenating “Rust-based” for correctness.

src/comments/mappers/to-new-comment-row.mapper.ts (1)

3-13: Simplify parameter structure for better API design.

The first parameter body: { body: string } creates unnecessary nesting. Consider flattening the parameters or using destructuring for a cleaner interface.

-export function toNewCommentRow(
-  body: { body: string },
-  articleId: number,
-  authorId: number,
-): NewCommentRow {
-  return {
-    body: body.body,
-    articleId,
-    authorId,
-  };
-}
+export function toNewCommentRow(
+  body: string,
+  articleId: number,
+  authorId: number,
+): NewCommentRow {
+  return {
+    body,
+    articleId,
+    authorId,
+  };
+}

Alternatively, if you need to maintain the object structure for consistency with other parts of the codebase, consider using destructuring:

-export function toNewCommentRow(
-  body: { body: string },
-  articleId: number,
-  authorId: number,
-): NewCommentRow {
-  return {
-    body: body.body,
-    articleId,
-    authorId,
-  };
-}
+export function toNewCommentRow(
+  { body }: { body: string },
+  articleId: number,
+  authorId: number,
+): NewCommentRow {
+  return {
+    body,
+    articleId,
+    authorId,
+  };
+}
src/articles/mappers/to-create-article-input.mapper.ts (1)

4-13: Clean and correct mapper implementation.

The toCreateArticleInput function correctly transforms the DTO to the input interface, properly handling the optional tagList field with nullish coalescing. The implementation is type-safe and follows good functional programming practices.

Consider adding JSDoc documentation for consistency with other mappers:

+/**
+ * Map a CreateArticleDto to CreateArticleInput
+ * @param dto - The DTO containing article data
+ * @returns The mapped input object
+ */
 export function toCreateArticleInput({
   article,
 }: CreateArticleDto): CreateArticleInput {
src/articles/mappers/to-new-article-row.mapper.ts (1)

4-13: Clean mapper implementation with proper separation of concerns.

The function correctly transforms input data and generates a slug from the title. However, consider that slug uniqueness isn't guaranteed - multiple articles with the same title would generate identical slugs.

Consider handling potential slug collisions at the service layer:

export function toNewArticleRow(
  input: CreateArticleInput,
  authorId: number,
): NewArticleRow {
  return {
    ...input,
    slug: slugify(input.title),
    authorId,
  };
}

If slug uniqueness is required, you might want to add a timestamp or random suffix generation at the service level when conflicts are detected.

src/comments/mappers/to-comment-response.mapper.ts (1)

4-17: Well-structured mapper with proper date handling.

The mapper correctly transforms domain objects to response DTOs and handles date conversion to ISO strings. Consider adding null checks for optional author fields if they can be undefined.

If bio or image can be null/undefined, consider defensive mapping:

export const toCommentResponse = (comment: IComment): CommentResponseDto => ({
  comment: {
    id: comment.id,
    createdAt: comment.createdAt.toISOString(),
    updatedAt: comment.updatedAt.toISOString(),
    body: comment.body,
    author: {
      username: comment.author.username,
-     bio: comment.author.bio,
-     image: comment.author.image,
+     bio: comment.author.bio ?? null,
+     image: comment.author.image ?? null,
      following: comment.author.following,
    },
  },
});
src/common/errors/realworld.error.ts (1)

4-12: Well-designed error class following RealWorld specification.

The error class properly extends Error and provides structured error handling. The JSON.stringify approach for the message works well for logging and debugging.

Consider adding a toString method for more readable error messages in logs:

export class RealWorldError extends Error {
  constructor(
    public status: number,
    public errors: Record<string, string[]>,
  ) {
    super(JSON.stringify(errors));
    this.name = 'RealWorldError';
  }
+
+  toString(): string {
+    return `${this.name} (${this.status}): ${JSON.stringify(this.errors)}`;
+  }
}
src/articles/dto/create-article.dto.ts (1)

3-11: Comprehensive DTO with proper validation rules.

The DTO correctly validates required fields with minimum length constraints and handles optional tagList. The validation rules align well with typical article creation requirements.

Consider adding maximum length validation to prevent extremely long inputs:

export const CreateArticleDto = t.Object({
  article: t.Object({
-   title: t.String({ minLength: 1 }),
-   description: t.String({ minLength: 1 }),
-   body: t.String({ minLength: 1 }),
+   title: t.String({ minLength: 1, maxLength: 255 }),
+   description: t.String({ minLength: 1, maxLength: 500 }),
+   body: t.String({ minLength: 1, maxLength: 50000 }),
    tagList: t.Optional(t.Array(t.String({ minLength: 1 }))),
  }),
});
src/articles/interfaces/article.interface.ts (1)

14-21: Address the TODO comment for proper type definition.

The current approach of using Omit and intersection types for the author field is a valid workaround, but the TODO comment indicates this should be properly addressed. Consider creating a dedicated IArticleAuthor interface in the profiles module to avoid this hack.

Would you like me to help create a proper IArticleAuthor interface to replace this temporary solution?

drizzle/reset.ts (2)

8-30: Consider adding environment protection for the reset script.

The SQL logic is correct and properly handles cascading deletions with quote_ident for injection protection. However, consider adding environment checks to prevent accidental execution in production.

async function reset() {
+  // Prevent accidental execution in production
+  if (process.env.NODE_ENV === 'production') {
+    console.error('❌ Reset script cannot be run in production environment');
+    process.exit(1);
+  }
+  
  console.log('⏳ Resetting database...');

37-37: Consider making process exit conditional.

The unconditional process.exit(0) might not be ideal if this function needs to be imported and used programmatically elsewhere.

-  process.exit(0);
+  if (require.main === module) {
+    process.exit(0);
+  }
src/articles/articles.module.ts (1)

14-14: Fix typo in variable name.

There's a typo in the variable name that should be corrected for consistency.

-  const tagsRepositry = new TagsRepository(db);
+  const tagsRepository = new TagsRepository(db);

And update the reference on line 16:

-  const tagsService = new TagsService(tagsRepositry);
+  const tagsService = new TagsService(tagsRepository);
src/database/database.providers.ts (1)

13-21: Consider making logger configurable based on environment

The database setup looks good. However, having the logger always enabled might impact performance in production environments.

Consider making the logger configurable:

+import { env } from '@/common/env';

 export const db = drizzle(queryClient, {
   schema: {
     ...usersSchema,
     ...articlesSchema,
     ...tagsSchema,
     ...commentsSchema,
   },
-  logger: true,
+  logger: env.NODE_ENV === 'development',
 });
src/common/interfaces/api.interface.ts (1)

29-35: Consider adding HTTP method to ApiContext

The ApiContext interface provides comprehensive request information but is missing the HTTP method, which could be useful for request handling logic.

 export type ApiContext = {
   userId?: number;
   headers: Headers;
   query: Record<string, string>;
   params: Record<string, string>;
   body: unknown;
+  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
 };
src/app.module.ts (1)

68-68: Consider managing scalarVersion as a constant

The hardcoded scalar version might become outdated over time.

Consider moving the version to a constants file or package.json for easier maintenance:

+import { SCALAR_VERSION } from '@/common/constants';

 // In swagger config
-        scalarVersion: '1.31.10',
+        scalarVersion: SCALAR_VERSION,
src/articles/mappers/to-domain.mapper.ts (1)

8-34: Consider performance optimization for large datasets.

The mapper uses multiple some() operations which have O(n) complexity. For articles with many favorites or authors with many followers, this could impact performance.

Consider pre-computing these relationships at the database level or using Sets for O(1) lookups:

export function toDomain(
  article: ArticleRow,
  { tagList, currentUserId }: ToDomainOptions,
): IArticle {
+  const favoritedBySet = currentUserId ? 
+    new Set(article.favoritedBy.map(user => user.userId)) : 
+    new Set();
+  const followersSet = currentUserId ? 
+    new Set(article.author.followers.map(f => f.followerId)) : 
+    new Set();
+
  return {
    // ... other fields
-    favorited: currentUserId ? 
-      article.favoritedBy.some((user) => user.userId === currentUserId) : 
-      false,
+    favorited: currentUserId ? favoritedBySet.has(currentUserId) : false,
    // ... other fields
    author: {
      // ... other author fields
-      following: currentUserId ? 
-        article.author.followers.some((follower) => follower.followerId === currentUserId) : 
-        false,
+      following: currentUserId ? followersSet.has(currentUserId) : false,
    },
  };
}
drizzle/migrations/meta/0009_snapshot.json (1)

6-454: Consider adding database indexes for query performance.

The schema lacks indexes on frequently queried columns which could impact performance as data grows.

Consider adding indexes for:

  • articles.author_id (foreign key queries)
  • comments.article_id (comment retrieval by article)
  • comments.author_id (author's comments queries)
  • articles.created_at (chronological ordering)
  • comments.created_at (comment ordering)

These indexes would improve query performance for common operations.

src/comments/comments.controller.ts (1)

48-56: Simplify optional userId handling.

The ternary operation to convert null to undefined is unnecessary complexity that could be simplified.

Apply this diff to simplify the logic:

        async ({ params, store, request }) => {
          const userId = await store.authService.getOptionalUserIdFromHeader(
            request.headers,
          );
          const comments = await store.commentsService.getComments(
            params.slug,
-            userId === null ? undefined : userId,
+            userId ?? undefined,
          );
          return toCommentsResponse(comments);
        },
src/auth/auth.service.ts (1)

73-88: Potential information disclosure in error messages.

The detailed error messages could provide information to attackers about the expected authentication format. Consider using more generic messages for security.

Apply this diff to reduce information disclosure:

    if (!rawHeader)
      throw new RealWorldError(StatusCodes.UNAUTHORIZED, {
-        Authorization: ['is missing'],
+        Authorization: ['Invalid authentication'],
      });

    const tokenParts = rawHeader?.split(' ');
    const tokenType = tokenParts?.[0];
    if (tokenType !== 'Token')
      throw new RealWorldError(StatusCodes.UNAUTHORIZED, {
-        token: ['is invalid. Expected header format: "Token jwt"'],
+        token: ['Invalid authentication format'],
      });

    const token = tokenParts?.[1];
    if (!token)
      throw new RealWorldError(StatusCodes.UNAUTHORIZED, {
-        token: ['is missing. Expected header format: "Token jwt"'],
+        token: ['Invalid authentication format'],
      });
src/common/utils/validation.utils.ts (1)

37-40: Consider using a more comprehensive email validation regex

The current email regex pattern is quite basic and may not handle all valid email formats according to RFC 5322. Consider using a more comprehensive pattern or a dedicated email validation library for production use.

For better email validation coverage, consider this more comprehensive regex:

-  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+  const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

Or consider using a dedicated library like validator.js for more robust email validation.

src/common/errors/errors.utils.ts (1)

3-7: Track the temporary type definition

Good to see the comment acknowledging this is temporary. Consider creating a tracking issue to update this once Elysia exports the type officially.

Would you like me to create a GitHub issue to track updating this temporary type definition when Elysia exports it?

ARCHITECTURE.md (1)

153-153: Fix grammatical issue: Add comma after conjunctive adverb.

The static analysis tool correctly identified a missing comma after the conjunctive adverb "Also".

-Also clearly service-level: it's a standardized rule, not controller-specific logic.
+Also, clearly service-level: it's a standardized rule, not controller-specific logic.
src/profiles/profiles.service.ts (1)

6-6: Address the TODO comment about UsersRepository removal.

The TODO comment indicates uncertainty about why UsersRepository was removed. Based on the architectural changes, this appears to be part of the modularization effort where each domain manages its own data access.

The UsersRepository was likely removed to maintain clear separation of concerns - the profiles domain should handle profile-related operations through ProfilesRepository, which can access user data via the database schema relations. This aligns with the layered architecture documented in ARCHITECTURE.md.

Would you like me to help clarify this architectural decision or remove the TODO comment?

src/articles/articles.repository.ts (1)

239-253: Consider implementing transaction-based favorite operations

The TODO comment correctly identifies that these operations should be atomic. The current implementation performs 2-3 separate queries which could lead to race conditions or inconsistent state.

Would you like me to implement a transaction-based solution that combines these operations into a single atomic operation, or create an issue to track this optimization?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f25046 and c54d5f4.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • bun.lockb is excluded by !**/bun.lockb
📒 Files selected for processing (107)
  • .vscode/settings.json (1 hunks)
  • ARCHITECTURE.md (1 hunks)
  • CODE_OF_CONDUCT.md (2 hunks)
  • CONTRIBUTING.md (3 hunks)
  • GOVERNANCE.md (1 hunks)
  • PROJECT_STRUCTURE.md (1 hunks)
  • README.md (3 hunks)
  • db/drop.ts (0 hunks)
  • docs/dev-container.md (2 hunks)
  • docs/getting-started.md (2 hunks)
  • docs/index.md (2 hunks)
  • docs/what-is-bedstack.md (2 hunks)
  • drizzle.config.ts (2 hunks)
  • drizzle/migrations/0009_ambitious_matthew_murdock.sql (1 hunks)
  • drizzle/migrations/meta/0009_snapshot.json (1 hunks)
  • drizzle/migrations/meta/_journal.json (1 hunks)
  • drizzle/reset.ts (1 hunks)
  • drizzle/seed.ts (1 hunks)
  • package.json (2 hunks)
  • src/app.module.ts (2 hunks)
  • src/articles/articles.controller.ts (1 hunks)
  • src/articles/articles.model.ts (0 hunks)
  • src/articles/articles.module.ts (1 hunks)
  • src/articles/articles.plugin.ts (0 hunks)
  • src/articles/articles.repository.ts (3 hunks)
  • src/articles/articles.schema.ts (1 hunks)
  • src/articles/articles.service.ts (3 hunks)
  • src/articles/comments/comments.schema.ts (0 hunks)
  • src/articles/comments/comments.service.ts (0 hunks)
  • src/articles/dto/article-feed-query.dto.ts (1 hunks)
  • src/articles/dto/article-response.dto.ts (1 hunks)
  • src/articles/dto/articles-response.dto.ts (1 hunks)
  • src/articles/dto/create-article.dto.ts (1 hunks)
  • src/articles/dto/index.ts (1 hunks)
  • src/articles/dto/list-articles-query.dto.ts (1 hunks)
  • src/articles/dto/update-article.dto.ts (1 hunks)
  • src/articles/interfaces/article-feed-row.interface.ts (1 hunks)
  • src/articles/interfaces/article-feed.interface.ts (1 hunks)
  • src/articles/interfaces/article-row.interface.ts (1 hunks)
  • src/articles/interfaces/article.interface.ts (1 hunks)
  • src/articles/interfaces/create-article-input.interface.ts (1 hunks)
  • src/articles/interfaces/index.ts (1 hunks)
  • src/articles/interfaces/new-article-row.interface.ts (1 hunks)
  • src/articles/interfaces/update-article-input.interface.ts (1 hunks)
  • src/articles/interfaces/update-article-row.interface.ts (1 hunks)
  • src/articles/mappers/index.ts (1 hunks)
  • src/articles/mappers/to-create-article-input.mapper.ts (1 hunks)
  • src/articles/mappers/to-domain.mapper.ts (1 hunks)
  • src/articles/mappers/to-feed-domain.mapper.ts (1 hunks)
  • src/articles/mappers/to-feed-response.mapper.ts (1 hunks)
  • src/articles/mappers/to-new-article-row.mapper.ts (1 hunks)
  • src/articles/mappers/to-response.mapper.ts (1 hunks)
  • src/auth/auth.service.ts (4 hunks)
  • src/comments/comments.controller.ts (1 hunks)
  • src/comments/comments.module.ts (1 hunks)
  • src/comments/comments.repository.ts (2 hunks)
  • src/comments/comments.schema.ts (1 hunks)
  • src/comments/comments.service.ts (1 hunks)
  • src/comments/dto/comment-response.dto.ts (1 hunks)
  • src/comments/dto/comments-response.dto.ts (1 hunks)
  • src/comments/dto/create-comment.dto.ts (1 hunks)
  • src/comments/dto/index.ts (1 hunks)
  • src/comments/interfaces/comment-row.interface.ts (1 hunks)
  • src/comments/interfaces/comment.interface.ts (1 hunks)
  • src/comments/interfaces/index.ts (1 hunks)
  • src/comments/interfaces/new-comment-row.interface.ts (1 hunks)
  • src/comments/mappers/index.ts (1 hunks)
  • src/comments/mappers/to-comment-response.mapper.ts (1 hunks)
  • src/comments/mappers/to-comments-response.mapper.ts (1 hunks)
  • src/comments/mappers/to-domain.mapper.ts (1 hunks)
  • src/comments/mappers/to-new-comment-row.mapper.ts (1 hunks)
  • src/common/constants/errors.constants.ts (1 hunks)
  • src/common/constants/index.ts (1 hunks)
  • src/common/constants/pagination.constants.ts (1 hunks)
  • src/common/errors/errors.utils.ts (1 hunks)
  • src/common/errors/index.ts (1 hunks)
  • src/common/errors/realworld.error.ts (1 hunks)
  • src/common/interfaces/api.interface.ts (1 hunks)
  • src/common/interfaces/common.interface.ts (1 hunks)
  • src/common/interfaces/index.ts (1 hunks)
  • src/common/utils/date.utils.ts (1 hunks)
  • src/common/utils/index.ts (1 hunks)
  • src/common/utils/validation.utils.ts (1 hunks)
  • src/constants.ts (0 hunks)
  • src/database.providers.ts (0 hunks)
  • src/database/database.providers.ts (1 hunks)
  • src/errors.ts (0 hunks)
  • src/profiles/dto/index.ts (1 hunks)
  • src/profiles/dto/profile-response.dto.ts (1 hunks)
  • src/profiles/interfaces/index.ts (1 hunks)
  • src/profiles/interfaces/profile-row.interface.ts (1 hunks)
  • src/profiles/interfaces/profile.interface.ts (1 hunks)
  • src/profiles/mappers/index.ts (1 hunks)
  • src/profiles/mappers/to-domain.mapper.ts (1 hunks)
  • src/profiles/mappers/to-response.mapper.ts (1 hunks)
  • src/profiles/profiles.controller.ts (1 hunks)
  • src/profiles/profiles.module.ts (1 hunks)
  • src/profiles/profiles.plugin.ts (0 hunks)
  • src/profiles/profiles.repository.ts (2 hunks)
  • src/profiles/profiles.schema.ts (0 hunks)
  • src/profiles/profiles.service.ts (1 hunks)
  • src/tags/dto/index.ts (1 hunks)
  • src/tags/dto/tags-response.dto.ts (1 hunks)
  • src/tags/interfaces/index.ts (1 hunks)
  • src/tags/interfaces/tag-row.interface.ts (1 hunks)
  • src/tags/interfaces/tag.interface.ts (1 hunks)
  • src/tags/mappers/index.ts (1 hunks)
⛔ Files not processed due to max files limit (33)
  • src/tags/mappers/to-domain.mapper.ts
  • src/tags/mappers/to-response.mapper.ts
  • src/tags/tags.controller.ts
  • src/tags/tags.model.ts
  • src/tags/tags.module.ts
  • src/tags/tags.plugin.ts
  • src/tags/tags.repository.ts
  • src/tags/tags.schema.ts
  • src/tags/tags.service.ts
  • src/users/dto/create-user.dto.ts
  • src/users/dto/index.ts
  • src/users/dto/login-user.dto.ts
  • src/users/dto/update-user.dto.ts
  • src/users/dto/user-response.dto.ts
  • src/users/index.ts
  • src/users/interfaces/create-user-input.interface.ts
  • src/users/interfaces/index.ts
  • src/users/interfaces/update-user-input.interface.ts
  • src/users/interfaces/user-follow-row.interface.ts
  • src/users/interfaces/user-row.interface.ts
  • src/users/interfaces/user.interface.ts
  • src/users/mappers/index.ts
  • src/users/mappers/to-domain.mapper.ts
  • src/users/mappers/to-response.mapper.ts
  • src/users/mappers/to-row.mapper.ts
  • src/users/users.controller.ts
  • src/users/users.model.ts
  • src/users/users.module.ts
  • src/users/users.plugin.ts
  • src/users/users.repository.ts
  • src/users/users.schema.ts
  • src/users/users.service.ts
  • tsconfig.json
💤 Files with no reviewable changes (10)
  • src/constants.ts
  • src/database.providers.ts
  • src/profiles/profiles.schema.ts
  • src/articles/comments/comments.schema.ts
  • src/profiles/profiles.plugin.ts
  • db/drop.ts
  • src/articles/articles.plugin.ts
  • src/errors.ts
  • src/articles/articles.model.ts
  • src/articles/comments/comments.service.ts
🧰 Additional context used
🪛 LanguageTool
docs/getting-started.md

[grammar] ~49-~49: It seems that a pronoun is missing.
Context: ...oders/bedstack/blob/main/SUPPORT.md). * If have any questions or just want to chat...

(IF_VB)

GOVERNANCE.md

[uncategorized] ~30-~30: The noun “decision-making” (= the process of deciding something) is spelled with a hyphen.
Context: ...s not always public, the results of the decision making process must be public. Decisions will ...

(DECISION_MAKING)

CODE_OF_CONDUCT.md

[style] ~32-~32: Try using a synonym here to strengthen your wording.
Context: ...ind - Trolling, insulting or derogatory comments, and personal or political attacks - Pu...

(COMMENT_REMARK)

CONTRIBUTING.md

[uncategorized] ~30-~30: This expression is usually spelled with a hyphen.
Context: ... and formatting**. It is an ultra-fast, Rust based linter and formatter. It also lints JSO...

(BASED_HYPHEN)

docs/what-is-bedstack.md

[uncategorized] ~5-~5: Possible missing comma found.
Context: ...d flexible enough to handle complex use cases as demonstrated by this [RealWorld](htt...

(AI_HYDRA_LEO_MISSING_COMMA)

PROJECT_STRUCTURE.md

[style] ~93-~93: Using many exclamation marks might seem excessive (in this case: 5 exclamation marks for a text that’s 2616 characters long)
Context: ...to common/ only when it's reused. > [!TIP] > Only one constants/ folder exis...

(EN_EXCESSIVE_EXCLAMATION)


[style] ~115-~115: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...e relations() in the same file unless very large - If split, name the second file `featu...

(EN_WEAK_ADJECTIVE)

ARCHITECTURE.md

[uncategorized] ~153-~153: A comma may be missing after the conjunctive/linking adverb ‘Also’.
Context: ...turn email.toLowerCase().trim(); } ``` Also clearly service-level: it's a standardi...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)

🪛 markdownlint-cli2 (0.17.2)
docs/dev-container.md

49-49: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)

🔇 Additional comments (118)
docs/index.md (2)

27-29: Clean up trailing whitespace
Removed extra spaces after the icon: key to enforce consistent markdown formatting.


76-76: Ensure no trailing blank lines
Standardized file ending by removing extra blank line.

README.md (8)

12-12: Remove heading trailing space
Trimmed the trailing whitespace in the Discord invitation heading for consistency.


28-32: Normalize code block indentation
Converted the indented env-copy snippet to a fenced code block aligned with backticks.


36-38: Update snippet formatting
Standardized the Docker startup snippet as a fenced code block.


40-44: Format migration command
Converted the Postgres migration step to a fenced code block for visual consistency.


46-50: Fence development server command
Reformatted the bun dev step into a proper fenced code block.


52-56: Fence test command
Standardized the API test invocation as a fenced code snippet.


65-67: Fence build command
Aligned the production build step (bun run build) to the fenced code block style.


71-73: Fence preview command
Reformatted the bun preview command into a fenced code block.

CONTRIBUTING.md (2)

12-14: Standardize installation snippet
Changed the indented shell snippet for Bun installation to a top-level fenced code block.


72-75: Unify bullet style
Replaced asterisks with hyphens in the issue-closing keywords list for consistent markdown styling.

CODE_OF_CONDUCT.md (3)

20-23: Add list prefixes for clarity
Inserted leading hyphens on existing list items under “Our Standards” to ensure they render correctly.


25-27: Add list prefixes for clarity
Inserted leading hyphens on the remaining standard examples to match list formatting.


109-109: Tighten punctuation spacing
Removed the extra space after the comma in the “Permanent Ban” clause to tighten sentence structure.

docs/what-is-bedstack.md (5)

5-6: Consolidate introductory text
Merged and reformatted the opening paragraph links for readability.


7-7: Reformat spec reference
Reflowed the RealWorld spec sentence into its own line for consistency.


13-13: Standardize feature list item
Removed trailing spaces on the Bun feature bullet for uniform list formatting.


15-15: Standardize feature list item
Removed trailing spaces on the ElysiaJS feature bullet for consistency.


17-17: Standardize feature list item
Removed trailing spaces on the Drizzle feature bullet to align list styling.

docs/getting-started.md (2)

11-14: LGTM: Formatting improvements enhance consistency.

The bullet point style changes from asterisks to hyphens improve markdown consistency across the documentation.


28-30: LGTM: Code block formatting standardized.

Removing leading spaces from code blocks improves readability and follows standard markdown formatting practices.

Also applies to: 34-36, 40-42

docs/dev-container.md (2)

13-15: LGTM: Bullet point formatting standardized.

The change from asterisks to hyphens for bullet points improves consistency with the project's markdown style.


41-43: LGTM: Indentation improvements enhance readability.

The adjusted indentation for the numbered sub-steps improves the visual hierarchy and readability.

GOVERNANCE.md (1)

7-8: LGTM: Formatting improvements enhance document structure.

The bullet point style standardization and indentation adjustments improve the document's readability and consistency with the project's markdown style guidelines.

Also applies to: 16-34

src/common/constants/pagination.constants.ts (1)

1-29: LGTM: Well-designed pagination constants with excellent documentation.

This implementation demonstrates several best practices:

  • Clear, descriptive constant names using SCREAMING_SNAKE_CASE
  • Comprehensive JSDoc documentation explaining the rationale behind values
  • Sensible default values (20 items) with appropriate limits (max 100)
  • Clear explanation of offset-based vs page-based pagination choice
  • Good separation of concerns by centralizing pagination configuration

The constants provide a solid foundation for consistent pagination behavior across the application.

src/common/interfaces/common.interface.ts (1)

1-63: LGTM: Excellent foundation for type-safe development.

This interface file demonstrates outstanding TypeScript practices:

Strengths:

  • Clear separation of concerns: Each interface has a single, well-defined responsibility
  • Comprehensive documentation: JSDoc comments explain the purpose and usage of each type
  • Type safety: Strong typing throughout with appropriate generic constraints
  • Reusability: Generic types like PaginatedResult<T> can be used across different domains
  • Database integration: Smart use of Drizzle ORM types with the InferNewRow utility
  • Modern TypeScript: Leverages advanced features like conditional types and mapped types

Notable implementations:

  • BaseEntity follows standard entity patterns with audit fields
  • InferNewRow cleverly omits system fields from insert operations
  • Prettify utility improves IDE type display experience
  • FilterParams provides flexible filtering capability

This provides an excellent foundation for the layered architecture and supports consistent API patterns across the application.

drizzle/migrations/meta/_journal.json (1)

67-74: Migration journal entry is correctly ordered and formatted.

The new entry for 0009_ambitious_matthew_murdock follows the existing idx and version sequence and maintains the JSON structure. No issues detected.

src/common/constants/errors.constants.ts (1)

1-1: Introduce a centralized default error message.

DEFAULT_ERROR_MESSAGE provides a consistent fallback across error handlers. Ensure it's utilized in your global error utilities to standardize user-facing messages.

.vscode/settings.json (1)

18-20: Update VS Code workspace settings.

Adding domain-specific spellcheck words and specifying typescript.tsdk aligns the editor with the project’s conventions and ensures consistent tooling.

src/tags/dto/index.ts (1)

1-1: Barrel export for tag DTOs.

Re-exporting tags-response.dto here cleanly aggregates DTOs and mirrors the pattern used in other modules.

src/common/interfaces/index.ts (1)

1-2: Aggregate common interfaces in a single barrel.

This barrel module simplifies imports for common.interface and api.interface, enhancing modularity and consistency.

src/comments/dto/index.ts (1)

1-3: Approve barrel export for comment DTOs.

This index file correctly consolidates all comment DTO exports, matching the established pattern and improving import ergonomics.

src/profiles/mappers/index.ts (1)

1-2: Approve barrel export for profile mappers.

The file cleanly re-exports both domain and response mappers, consistent with other feature layers.

src/profiles/dto/index.ts (1)

1-1: Approve barrel export for profile DTOs.

Centralizing profile-response DTO exports in a single entry point aligns with the layered architecture conventions.

src/tags/mappers/index.ts (1)

1-2: Approve barrel export for tag mappers.

This index module correctly aggregates tag-to-domain and tag-to-response mappers, mirroring other domains.

src/common/constants/index.ts (1)

1-2: Approve barrel export for shared constants.

Re-exporting pagination and error constants here simplifies shared imports and adheres to project structure.

src/common/errors/index.ts (1)

1-2: Centralized error exports look good.

This index cleanly aggregates the RealWorldError and associated utilities for unified error handling across the application.

src/tags/interfaces/index.ts (1)

1-2: Interface re-exports are correctly consolidated.

This index provides a single entry point for tag-related interfaces, aligning with the layered architecture conventions.

src/comments/interfaces/comment-row.interface.ts (1)

1-4: Type alias for CommentRow is clear and type-safe.

Leveraging InferSelectModel ensures the row shape stays in sync with the schema.

src/profiles/interfaces/index.ts (1)

1-2: Profile interfaces re-export is consistent.

This index aligns with other feature directories and simplifies imports for profile-related types.

src/comments/interfaces/index.ts (1)

1-3: Comments interfaces properly aggregated.

The index centralizes exports for comment, comment-row, and new-comment-row interfaces, matching the project’s modular structure.

src/comments/interfaces/new-comment-row.interface.ts (1)

1-4: Appropriate type alias for new comment rows.

The NewCommentRow alias correctly infers the shape from the comments schema, promoting type safety in the repository and service layers.

drizzle/migrations/0009_ambitious_matthew_murdock.sql (1)

1-4: Ensure application handles nullable bio and image fields.

Dropping the NOT NULL constraint allows bio and image to be null, which may require updates to the User entity and downstream DTOs or mappers to handle optional values gracefully.

Please verify all usages of bio and image in the codebase and update interfaces or mappers if needed.

src/articles/interfaces/update-article-row.interface.ts (1)

1-3: Correct partial type for article updates.

The UpdateArticleRow alias appropriately wraps NewArticleRow in Partial<> to allow optional properties during update operations.

src/articles/interfaces/new-article-row.interface.ts (1)

1-4: Type alias infers new article row correctly.

NewArticleRow is correctly defined using InferNewRow<typeof articles>, aligning the database schema with TypeScript types for creation operations.

src/articles/interfaces/create-article-input.interface.ts (1)

1-6: CreateArticleInput interface matches API spec.

The interface clearly defines the required fields (title, description, body, tagList) for article creation requests.

drizzle.config.ts (3)

3-10: LGTM: Improved encapsulation.

Making dbCredentials local instead of exported improves encapsulation and reduces the module's public API surface.


15-17: LGTM: Consistent with architectural refactoring.

The changes to output directory (./drizzle/migrations) and schema pattern (.schema.ts) align well with the layered architecture refactoring mentioned in the PR objectives.


20-20: LGTM: Clean shorthand property notation.

Using shorthand property notation for dbCredentials is more concise and follows modern JavaScript/TypeScript best practices.

src/profiles/interfaces/profile-row.interface.ts (1)

1-6: LGTM: Well-designed interface with clear type composition.

The ProfileRow interface effectively uses TypeScript's intersection types to extend UserRow with followers information. The type composition is clean and follows best practices for modular type design.

src/articles/dto/update-article.dto.ts (1)

4-7: LGTM: Proper use of partial types for update operations.

The DTO correctly uses t.Partial() to make all fields optional for update operations, which is the appropriate pattern for PATCH/PUT endpoints. The dual export of both schema and type follows Elysia best practices.

src/articles/mappers/to-feed-domain.mapper.ts (1)

3-15: LGTM: Clean mapper implementation following the pattern.

The toFeedDomain mapper correctly implements the transformation from database row to domain object. The direct property mapping is appropriate and the function follows the pure function pattern, making it predictable and testable.

src/comments/mappers/index.ts (1)

1-4: LGTM! Consistent barrel export pattern.

The index file correctly consolidates all comment mapper exports, following the established architectural pattern across the codebase. This promotes clean imports and maintains modularity.

src/profiles/interfaces/profile.interface.ts (1)

1-6: Well-defined domain interface.

The IProfile interface correctly models a user profile with appropriate nullable fields for optional data (bio, image) and required fields for essential data (username, following status). The type definition aligns well with the domain requirements.

src/common/utils/index.ts (1)

1-4: Good consolidation of utility functions.

The index file appropriately centralizes common utility exports, providing a clean single entry point for utility functions across the application. This supports the modular architecture goals.

src/profiles/mappers/to-response.mapper.ts (1)

4-18: Excellent mapper implementation with proper documentation.

The toResponse function is well-implemented with clear JSDoc documentation and correct transformation from domain model to response DTO. The nesting of profile data under the profile key follows API design conventions, and all fields are properly mapped.

src/tags/dto/tags-response.dto.ts (1)

1-8: LGTM! Well-structured DTO implementation.

The implementation correctly uses Elysia's validation schema and follows the expected pattern for response DTOs. The schema properly defines tags as an array of strings, and the type export is correctly implemented.

src/articles/dto/index.ts (1)

1-7: LGTM! Standard barrel file implementation.

The barrel file correctly consolidates all article DTOs with consistent export patterns, making imports cleaner for consumers.

src/comments/mappers/to-comments-response.mapper.ts (1)

1-9: LGTM! Well-structured mapper implementation.

The mapper correctly transforms an array of comment entities to the expected response format, properly reusing the existing toCommentResponse mapper and extracting the nested comment data.

src/articles/interfaces/update-article-input.interface.ts (1)

1-7: LGTM! Well-designed interface for partial updates.

The interface correctly defines all properties as optional, which is appropriate for update operations where users may want to modify only specific fields.

src/comments/dto/create-comment.dto.ts (1)

1-9: LGTM! Well-structured DTO with proper validation.

The DTO correctly implements the nested structure for comment creation with appropriate validation. The minimum length constraint ensures comments are not empty, which is good for data quality.

drizzle/seed.ts (1)

3-8: Import restructuring aligns well with the new layered architecture.

The updated import paths correctly reflect the modular organization by domain (articles, comments, tags, users) and the new database provider location.

src/comments/dto/comments-response.dto.ts (1)

4-6: Verify the property access pattern for nested DTO references.

The usage of CommentResponseDto.properties.comment assumes that CommentResponseDto has a comment property that can be accessed via .properties. This pattern could be fragile if the referenced DTO structure changes.

Consider documenting this dependency or using a more explicit approach:

+// Assumes CommentResponseDto has structure: { comment: CommentSchema }
 export const CommentsResponseDto = t.Object({
   comments: t.Array(CommentResponseDto.properties.comment),
 });

Let me verify the structure of CommentResponseDto to ensure this property access is valid:

#!/bin/bash
# Description: Verify the structure of CommentResponseDto to ensure property access is valid
# Expected: Find CommentResponseDto definition with a 'comment' property

fd -e ts -e js | xargs rg -l "CommentResponseDto" | head -5
rg -A 10 "export.*CommentResponseDto.*=" --type ts
src/articles/mappers/to-response.mapper.ts (1)

4-25: LGTM! Clean and comprehensive mapping implementation.

The mapper function correctly transforms domain objects to response DTOs with proper:

  • Date formatting using ISO strings
  • Author profile extraction with explicit field mapping
  • Structured response format with nested article property

The implementation follows good practices for data transformation in a layered architecture.

src/articles/interfaces/article-row.interface.ts (1)

6-10: LGTM! Well-structured interface for enriched article data.

The interface correctly:

  • Uses Drizzle ORM's InferSelectModel for type inference
  • Omits authorId when including the full author object to avoid redundancy
  • Properly types related entities (author, favorites, tags)
  • Maintains consistency with the modular architecture

This design supports efficient database queries with joined relations while maintaining type safety.

src/articles/dto/list-articles-query.dto.ts (1)

4-12: LGTM! Excellent use of DTO composition for query parameters.

The implementation demonstrates good practices:

  • Uses t.Composite to extend existing ArticleFeedQueryDto functionality
  • Applies appropriate validation with minLength: 1 to prevent empty filter strings
  • Includes logical filtering options (tag, author, favorited) for article queries
  • Maintains clean separation between pagination and filtering concerns

This approach promotes reusability and maintains consistency across the API.

src/comments/mappers/to-domain.mapper.ts (1)

10-34: LGTM! Excellent mapper implementation with clear documentation.

The function demonstrates best practices:

  • Comprehensive JSDoc documentation explaining parameters and return value
  • Custom type ToCommentsDomainAuthor provides clear contract for author data
  • Complete field mapping from database row to domain object
  • Proper handling of timestamps and author relationship
  • Clean separation of concerns between data transformation and business logic

This mapper aligns well with the layered architecture pattern and provides a clear transformation boundary.

src/profiles/profiles.module.ts (1)

1-12: LGTM! Clean refactoring with improved consistency.

The changes demonstrate good architectural improvements:

  • Standardized import paths using @/ prefix for consistency
  • Simplified service dependency injection by removing UsersRepository
  • Fixed the typo in variable naming
  • Clean module setup pattern
src/articles/interfaces/article.interface.ts (1)

3-13: Well-structured interface with comprehensive type coverage.

The IArticle interface provides excellent type safety with all necessary article fields properly typed. The structure supports the layered architecture pattern effectively.

src/articles/interfaces/article-feed-row.interface.ts (1)

4-17: Excellent use of type composition and utility types.

The interface demonstrates good TypeScript practices:

  • Leverages Drizzle ORM's InferSelectModel for schema consistency
  • Properly omits fields not needed in feed context
  • Clean extension with additional computed fields
  • Well-structured author object with appropriate fields
src/articles/mappers/to-feed-response.mapper.ts (1)

4-23: Well-implemented mapper function with clean transformation logic.

The function demonstrates good practices:

  • Clear parameter destructuring with proper typing
  • Efficient array mapping with field selection
  • Proper date formatting using toISOString()
  • Smart default handling for articlesCount using nullish coalescing
  • Pure function design with no side effects
src/profiles/mappers/to-domain.mapper.ts (1)

4-25: Well-designed domain mapper with proper relationship handling.

The function demonstrates solid domain mapping practices:

  • Clear separation between data and domain layers
  • Proper null safety handling for currentUserId
  • Efficient follower relationship computation using Array.some()
  • Good JSDoc documentation
  • Clean, readable logic flow
src/comments/dto/comment-response.dto.ts (1)

1-19: Well-structured DTO with proper type safety.

The implementation follows best practices for Elysia DTOs:

  • Proper use of t.Union([t.Null(), t.String()]) for nullable fields
  • Clear nested object structure matching the domain model
  • Export of both schema and static type for runtime and compile-time validation
src/common/utils/date.utils.ts (1)

1-32: Excellent utility functions with comprehensive documentation.

The implementations are correct and well-documented:

  • formatDateToISO provides a clear wrapper around toISOString()
  • isValidDate properly handles invalid Date objects using Number.isNaN(date.getTime())
  • getCurrentDateISO offers a convenient current timestamp function
  • JSDoc examples enhance developer experience
src/articles/articles.module.ts (2)

4-4: Correct database provider import path.

The import path update from @/database.providers to @/database/database.providers aligns with the new project structure.


15-15: Clean separation of concerns achieved.

The simplified ProfilesService constructor reflects the proper separation where comments functionality has been extracted to its own module. This aligns well with the layered architecture goals.

src/comments/interfaces/comment.interface.ts (1)

1-12: Well-defined domain interface with proper type distinction.

The IComment interface correctly defines the domain model structure:

  • Uses Date types for timestamps (appropriate for internal domain model)
  • Nullable fields for optional author information
  • Clean separation from the DTO which uses string types for API serialization

This demonstrates good architectural separation between domain models and API contracts.

src/tags/interfaces/tag-row.interface.ts (1)

1-9: LGTM! Clean interface definitions following Drizzle ORM patterns.

The interface definitions properly leverage Drizzle ORM's type inference utilities and follow a consistent naming convention. The separation between existing row types (TagRow, ArticleTagRow) and new row types (NewTagRow, NewArticleTagRow) provides clear contracts for database operations.

src/profiles/dto/profile-response.dto.ts (1)

1-12: LGTM! Well-structured DTO with appropriate nullable fields.

The DTO correctly models the profile response structure with appropriate nullable types for optional fields like bio and image. The use of Elysia's type system provides both runtime validation and compile-time type safety.

src/articles/dto/articles-response.dto.ts (1)

5-5: Verify the omitted 'body' field exists in ArticleResponseDto.

Ensure that the ArticleResponseDto.properties.article actually contains a body field to avoid runtime errors.

#!/bin/bash
# Description: Verify that ArticleResponseDto contains a 'body' field in its article property
# Expected: Find the 'body' field definition in the ArticleResponseDto schema

# Search for ArticleResponseDto definition and its properties
rg -A 10 -B 5 "ArticleResponseDto.*=.*t\.Object" src/articles/dto/
src/articles/interfaces/article-feed.interface.ts (1)

1-13: LGTM! Comprehensive interface with proper type relationships.

The IArticleFeed interface provides a complete structure for article feed items with appropriate types. The cross-module reference to IProfile demonstrates good architectural separation and reusability.

src/articles/mappers/index.ts (1)

1-6: LGTM! Clean barrel export pattern for mapper organization.

The barrel export approach provides a clean import interface for all article mappers, supporting the modular architecture by centralizing mapper exports.

package.json (3)

24-29: LGTM! Database script simplification aligns with reorganization.

The removal of explicit --config=db/config.ts flags and path changes from db/ to drizzle/ directory are consistent with the architectural refactoring. The replacement of db:drop with db:reset provides clearer semantics.


32-41: New dependencies support the layered architecture well.

The addition of authentication (@elysiajs/bearer, @elysiajs/jwt), validation (@sinclair/typebox), HTTP utilities (http-status-codes), and functional utilities (radashi) aligns with the modular architecture goals. Dependency upgrades are also appropriate.


54-57: ```shell
#!/bin/bash

Description: Search for version specifiers of @sinclair/typebox across the repository

echo "Searching for version specifiers of @sinclair/typebox:"
rg -n "@sinclair/typebox@"


</details>
<details>
<summary>src/articles/dto/article-response.dto.ts (1)</summary>

`1-22`: **Well-structured DTO with proper validation and typing.**

The implementation demonstrates excellent practices:
- Clean separation of runtime validation and TypeScript types
- Proper handling of nullable fields with `t.Union([t.Null(), t.String()])`
- Consistent nested object structure
- All expected article response fields included

The schema aligns well with the layered architecture approach.

</details>
<details>
<summary>src/tags/interfaces/tag.interface.ts (1)</summary>

`1-12`: **Clean and well-designed domain interfaces.**

The type definitions are excellent:
- Clear separation between `ITag` (tag entity) and `IArticleTag` (relationship)
- Proper timestamp handling with `Date` type
- Consistent naming conventions
- Follows domain-driven design principles

These interfaces provide solid foundation for the tags domain.

</details>
<details>
<summary>src/comments/comments.schema.ts (1)</summary>

`1-30`: **Excellent Drizzle ORM schema implementation.**

The schema demonstrates best practices:
- Proper foreign key relationships with cascade delete (appropriate for comments)
- Clean table structure with serial primary key and timestamps
- Well-defined relational mappings with custom relation names
- Correct import paths using alias resolution

The cascade delete behavior ensures data integrity when articles or users are removed.

</details>
<details>
<summary>src/articles/interfaces/index.ts (1)</summary>

`1-8`: **Perfect barrel export implementation.**

This consolidation file follows excellent practices:
- Clean wildcard re-exports for all article interfaces
- Consistent naming conventions across interface files
- Enables centralized imports for the articles domain
- Supports the modular layered architecture

This approach will improve maintainability and developer experience.

</details>
<details>
<summary>src/comments/comments.module.ts (1)</summary>

`13-35`: **Clean layered architecture implementation!**

The module setup follows proper dependency injection patterns with clear separation of concerns. Repositories are instantiated first, followed by services with their dependencies properly injected.

</details>
<details>
<summary>src/articles/dto/article-feed-query.dto.ts (1)</summary>

`16-34`: **Well-structured DTO with comprehensive validation!**

The DTO properly validates pagination parameters with clear constraints and helpful descriptions for API documentation. Good use of shared constants for consistency.

</details>
<details>
<summary>src/app.module.ts (1)</summary>

`23-48`: **Excellent error handling implementation!**

The error handler properly differentiates between various error types and formats them appropriately. Good use of the pick utility to prevent leaking internal error details.

</details>
<details>
<summary>src/articles/mappers/to-domain.mapper.ts (1)</summary>

`20-22`: **Verify null safety in favorited calculation.**

The `some()` operation doesn't handle the case where `currentUserId` is undefined, which could lead to incorrect favorited status.



Apply this diff to ensure proper null safety:

```diff
-    favorited: article.favoritedBy.some(
-      (user) => user.userId === currentUserId,
-    ),
+    favorited: currentUserId ? 
+      article.favoritedBy.some((user) => user.userId === currentUserId) : 
+      false,

Likely an incorrect or invalid review comment.

src/comments/comments.repository.ts (2)

9-15: LGTM! Improved null handling in create method.

The explicit null coalescing and updated type usage improves type safety and consistency with the new architecture.


58-64: Verify delete method behavior with concurrent operations.

The delete method now returns a boolean based on the number of deleted rows. Consider potential race conditions if the comment is deleted by another process between the check and deletion.

Run the following script to verify if there are any concurrent comment deletion patterns in the codebase:

#!/bin/bash
# Description: Check for concurrent comment deletion patterns and transaction usage

# Search for comment deletion patterns
rg -A 5 -B 5 "delete.*comment" --type ts

# Check for transaction usage in comment operations
rg -A 3 -B 3 "transaction.*comment|comment.*transaction" --type ts
src/comments/comments.controller.ts (2)

78-81: LGTM! Proper parameter validation for delete endpoint.

The explicit parameter validation with t.Numeric() for the comment ID ensures type safety and prevents invalid requests.


10-96: Verify error handling consistency across endpoints.

The controller doesn't explicitly handle service-level errors. Ensure that the service methods throw appropriate RealWorldError instances for consistent error responses.

Run the following script to verify error handling patterns in the comments service:

#!/bin/bash
# Description: Verify error handling patterns in comments service

# Check for RealWorldError usage in comments service
ast-grep --pattern 'throw new RealWorldError($_)'

# Check for try-catch blocks in service methods
ast-grep --pattern 'try {
  $$$
} catch ($$$) {
  $$$
}'
src/auth/auth.service.ts (2)

58-65: LGTM! Improved structured error handling.

The transition to RealWorldError with HTTP status codes and structured error details provides better consistency across the application.


112-117: LGTM! Improved optional authentication handling.

The change from null to undefined for optional authentication provides better TypeScript consistency and the error filtering logic is sound.

PROJECT_STRUCTURE.md (1)

1-124: Excellent documentation of the project structure!

The documentation clearly explains the flat, feature-sliced architecture with self-contained domain features. The structure aligns well with the layered architecture pattern and provides good guidance for maintaining consistency across the codebase.

src/common/utils/validation.utils.ts (1)

1-57: Well-structured validation utilities!

The validation functions are properly documented with JSDoc comments and examples. The use of type predicates in isNullOrUndefined and the URL constructor approach in isValidUrl are particularly good practices.

src/articles/articles.controller.ts (1)

1-226: Well-structured controller implementation!

The controller properly implements RESTful endpoints with:

  • Consistent authentication handling
  • Proper request/response validation using DTOs
  • Clear route organization with detailed OpenAPI metadata
  • Appropriate HTTP status codes (201 for creation, 204 for deletion)
src/profiles/profiles.controller.ts (2)

1-86: Clean controller implementation!

The controller properly delegates business logic to the service layer, uses consistent response mapping, and includes comprehensive OpenAPI documentation. The authentication and authorization checks are appropriately placed.


15-36: ```shell
#!/bin/bash

Locate the ProfilesService implementation and show the definition of findProfileByUsername

service_file=$(fd profiles.service.ts src | head -n1)
echo "ProfilesService file: $service_file"
rg -A5 -B5 "findProfileByUsername" -n "$service_file"


</details>
<details>
<summary>src/common/errors/errors.utils.ts (1)</summary>

`1-71`: **Excellent error handling utilities!**

The error formatting functions provide consistent, user-friendly error responses with:
- Proper path normalization (URL to dot notation)
- Message deduplication
- Redundant prefix removal for cleaner messages
- Edge case handling for empty validation errors

</details>
<details>
<summary>ARCHITECTURE.md (1)</summary>

`1-162`: **Excellent architecture documentation that provides clear guidance.**

This comprehensive architecture document effectively establishes the layered architecture principles and provides concrete guidance for the codebase refactoring. The documentation includes:

- Clear separation of concerns across controller, service, and repository layers
- Helpful Mermaid diagram showing relationships between components
- Practical examples demonstrating business logic placement
- Type design principles for interfaces vs classes and canonical forms

The examples and layer responsibilities align well with the actual implementation changes visible in the other files under review.

</details>
<details>
<summary>src/articles/articles.schema.ts (3)</summary>

`14-25`: **Well-structured articles table with proper constraints.**

The table definition follows PostgreSQL best practices with:
- Serial primary key for performance
- Unique constraint on slug for SEO-friendly URLs
- Not null constraints on required fields
- Proper foreign key constraint with cascade delete
- Timestamps with default values

The cascade delete on `authorId` ensures data integrity when users are deleted.

---

`27-40`: **Proper many-to-many relationship design for favorites.**

The `favoriteArticles` join table correctly implements:
- Composite primary key preventing duplicate favorites
- Foreign key constraints with cascade delete to maintain referential integrity
- Timestamps for audit trail
- Proper naming convention

This design efficiently handles the many-to-many relationship between users and their favorite articles.

---

`42-73`: **Well-defined relations for comprehensive data fetching.**

The relations definitions properly establish:
- Bidirectional relationships between articles and related entities
- Consistent relation names for query clarity
- Proper field and reference mappings
- Support for eager loading of related data

This enables efficient queries with Drizzle ORM's relational query API.

</details>
<details>
<summary>src/comments/comments.service.ts (2)</summary>

`17-46`: **Solid comment creation logic with proper validation and error handling.**

The `createComment` method correctly:
- Validates article existence before creating comment
- Uses mappers for data transformation between layers
- Handles repository failure with appropriate error responses
- Fetches author profile for complete domain model
- Sets following status correctly (false for self)

The error handling properly uses `RealWorldError` with HTTP status codes.

---

`54-80`: **Efficient comment retrieval with proper domain mapping.**

The `getComments` method:
- Validates article existence first
- Retrieves comments efficiently from repository
- Maps each comment to domain model with author details
- Correctly determines following status based on current user
- Handles optional authentication gracefully

The mapping logic properly transforms database rows to domain objects.

</details>
<details>
<summary>src/profiles/profiles.service.ts (2)</summary>

`10-30`: **Clean profile retrieval methods with proper error handling.**

Both `findProfileByUsername` and `findProfileByUserId` methods:
- Use the repository layer appropriately
- Handle not found cases with proper error types
- Transform data using domain mappers
- Accept optional current user for context

The method naming is clear and follows the service layer responsibilities outlined in the architecture.

---

`32-71`: **Efficient follow/unfollow implementation with proper validation.**

The follow/unfollow methods:
- Validate target profile existence first
- Check current follow status before making changes
- Avoid unnecessary database operations (idempotent)
- Return updated profile information
- Handle edge cases gracefully

This implementation prevents duplicate follows and unnecessary unfollows while maintaining data consistency.

</details>
<details>
<summary>src/profiles/profiles.repository.ts (4)</summary>

`10-26`: **Improved profile lookup methods with explicit null handling.**

The refactored methods:
- Use clear, descriptive method names
- Return explicit `Promise<ProfileRow | null>` types
- Use array destructuring with nullish coalescing for clean null handling
- Include follower relations for complete profile data

The `?? null` pattern ensures consistent null returns when no profile is found.

---

`28-35`: **Efficient method for retrieving followed user IDs.**

The `findFollowedUserIds` method:
- Uses selective column projection for performance
- Filters correctly by follower ID
- Maps results efficiently to return just the IDs

This is useful for bulk operations and follow status checks.

---

`37-63`: **Robust follow/unfollow operations with proper conflict handling.**

Both methods correctly:
- Use `onConflictDoNothing()` to handle duplicate follow attempts gracefully
- Return boolean success indicators based on affected rows
- Use proper query conditions with `and()` for unfollow operations

The boolean return values allow the service layer to handle idempotent operations efficiently.

---

`65-80`: **Efficient follow relationship check with proper query optimization.**

The `findFollowByUsers` method:
- Uses selective column projection for minimal data transfer
- Applies proper filtering with compound conditions
- Uses `limit(1)` for early termination optimization
- Returns boolean result for easy consumption

This method supports the service layer's validation logic efficiently.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

yamcodes and others added 14 commits June 20, 2025 20:31
…y and change mock user type from UserInDb to UserRow.
… and rename method from getTags to findTags, adjusting expected result format to return an empty array.
…ency and rename method from findByUsername to findProfileByUsername, ensuring clarity in expected behavior.
…ort paths for consistency, enhancing clarity and maintainability across the codebase.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…entUserId is not provided, ensuring consistent behavior in user following checks.
…t and updatedAt fields, ensuring accurate time tracking and consistency across user-related tables.
…limit values, improving code maintainability and readability.
…rrecting 'deleted' to 'delete' for improved clarity.
…e queries, improving readability and maintainability. Filters for authors, tags, and favorites are now accumulated and applied collectively, streamlining the query construction process.
…edAt fields, ensuring consistency in timestamp handling.
yamcodes added 4 commits June 20, 2025 20:59
…tion to 'bundler', and add error handling for DrizzleQueryError in the application. Introduced formatDBError function for improved database error reporting.
…s to include onUpdate timestamp functionality for improved data integrity.
… and UsersService to use synchronous error throwing for not found scenarios, enhancing clarity and consistency in error handling.
@yamcodes yamcodes merged commit a6fd7a4 into main Jun 20, 2025
7 checks passed
@yamcodes yamcodes deleted the layered branch June 20, 2025 16:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (10)
docs/getting-started.md (1)

49-49: Standardize list marker style

The final bullet still uses an asterisk. For uniformity, switch this (and the surrounding items) to hyphens to match the rest of the document.

- * If you have any questions or just want to chat, join our [Discord server](https://discord.gg/8UcP9QB5AV) or [GitHub Discussions](https://github.com/bedtime-coders/bedstack/discussions).
+ - If you have any questions or just want to chat, join our [Discord server](https://discord.gg/8UcP9QB5AV) or [GitHub Discussions](https://github.com/bedtime-coders/bedstack/discussions).
src/profiles/profiles.service.test.ts (1)

2-3: Import paths updated correctly for layered architecture.

The import paths have been properly updated to reflect the new module structure. However, consider removing the UsersRepository import since it's no longer used in the service instantiation.

-import { UsersRepository } from '@/users/users.repository';
src/app.module.ts (1)

25-58: Comprehensive error handling implementation with minor improvement suggestions.

The error handler effectively covers different error types and provides structured responses. However, consider these improvements:

  1. The console.error logging (line 51) might not be suitable for production environments
  2. Consider using a proper logging library instead of console.error
-      console.error(error);
+      // TODO: Replace with proper logging library (e.g., winston, pino)
+      console.error('[Error Handler]', error);
src/common/errors/errors.utils.ts (3)

4-8: Consider adding JSDoc for the temporary type definition.

Since this is a temporary type definition, adding documentation about when it can be removed would be helpful.

+/**
+ * Temporary type definition for Elysia custom status response
+ * TODO: Remove once Elysia exports this type officially
+ */
 type ElysiaCustomStatusResponse = {

24-29: Consider edge cases in path parsing.

The path parsing function handles basic cases well, but consider adding validation for edge cases:

 function parsePath(path: string): string {
+  if (!path || typeof path !== 'string') {
+    return 'general';
+  }
   return path
     .replace(/^\//, '') // remove leading slash
     .replace(/\//g, '.') // convert slash to dot notation
     .replace(/\[(\d+)\]/g, '.$1'); // optional: convert [0] to .0
 }

66-72: Consider improving not found error formatting.

The current implementation converts the error message to lowercase, which might not always be appropriate:

 export function formatNotFoundError(error: NotFoundError) {
   return {
     errors: {
-      [error.message.toLowerCase()]: 'not found',
+      [error.message.toLowerCase().replace(/\s+/g, '_')]: 'not found',
     },
   };
 }
src/tags/tags.service.test.ts (1)

19-24: Consider expanding test coverage

While the basic test is correct, consider adding more test cases for the tags service:

  • Test with actual tag data
  • Test error handling scenarios
  • Test any filtering or sorting logic

Would you like me to generate additional test cases to improve coverage?

src/articles/articles.schema.ts (2)

9-9: Avoid aliasing pgTable to prevent confusion

Aliasing pgTable as table could be confusing and goes against common Drizzle ORM conventions. Consider using pgTable directly for better clarity and consistency with other schema files.

-import {
-  integer,
-  primaryKey,
-  serial,
-  pgTable as table,
-  text,
-  timestamp,
-} from 'drizzle-orm/pg-core';
+import {
+  integer,
+  primaryKey,
+  serial,
+  pgTable,
+  text,
+  timestamp,
+} from 'drizzle-orm/pg-core';

14-28: Comprehensive articles table definition

The articles table is well-structured with:

  • Unique slug for SEO-friendly URLs
  • Proper timestamp management
  • Foreign key to users with cascade delete

Consider adding indexes on frequently queried columns like slug and authorId for better performance.

src/articles/articles.repository.ts (1)

249-280: Consider implementing the transaction optimization.

The TODO comments correctly identify an optimization opportunity. Currently, these methods perform 1-3 database operations, but could be optimized to a single transaction.

Would you like me to generate a transaction-based implementation for these methods? This would improve both performance and data consistency by ensuring atomicity of the favorite/unfavorite operations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c54d5f4 and be73c4f.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • GOVERNANCE.md (1 hunks)
  • docs/dev-container.md (2 hunks)
  • docs/getting-started.md (2 hunks)
  • drizzle/migrations/0010_chubby_carnage.sql (1 hunks)
  • drizzle/migrations/meta/0010_snapshot.json (1 hunks)
  • drizzle/migrations/meta/_journal.json (1 hunks)
  • package.json (1 hunks)
  • src/app.module.ts (2 hunks)
  • src/articles/articles.controller.ts (1 hunks)
  • src/articles/articles.repository.ts (3 hunks)
  • src/articles/articles.schema.ts (1 hunks)
  • src/articles/articles.service.test.ts (1 hunks)
  • src/articles/mappers/to-domain.mapper.ts (1 hunks)
  • src/auth/auth.service.test.ts (1 hunks)
  • src/comments/comments.schema.ts (1 hunks)
  • src/comments/comments.service.ts (1 hunks)
  • src/common/errors/errors.utils.ts (1 hunks)
  • src/profiles/profiles.service.test.ts (3 hunks)
  • src/tags/tags.schema.ts (1 hunks)
  • src/tags/tags.service.test.ts (3 hunks)
  • src/users/users.schema.ts (1 hunks)
  • src/users/users.service.test.ts (1 hunks)
  • tsconfig.json (2 hunks)
✅ Files skipped from review due to trivial changes (5)
  • src/auth/auth.service.test.ts
  • src/articles/articles.service.test.ts
  • docs/dev-container.md
  • src/users/users.service.test.ts
  • drizzle/migrations/meta/0010_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • drizzle/migrations/meta/_journal.json
  • GOVERNANCE.md
  • src/articles/articles.controller.ts
  • src/comments/comments.schema.ts
  • package.json
  • src/articles/mappers/to-domain.mapper.ts
  • src/comments/comments.service.ts
🧰 Additional context used
🪛 GitHub Check: test (latest)
src/profiles/profiles.service.test.ts

[failure] 30-30: error: expect(received).toThrow(expected)
Expected substring: "Profile not found"
Received message: "profile"

  at <anonymous> (/home/runner/work/bedstack/bedstack/src/profiles/profiles.service.test.ts:30:17)
  at <anonymous> (/home/runner/work/bedstack/bedstack/src/profiles/profiles.service.test.ts:27:64)
🪛 GitHub Actions: tests
src/profiles/profiles.service.test.ts

[error] 1-1: Test failure: Expected substring 'Profile not found' not found in output.

🪛 LanguageTool
docs/getting-started.md

[style] ~49-~49: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...oders/bedstack/blob/main/SUPPORT.md). * If you have any questions or just want to ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 markdownlint-cli2 (0.17.2)
docs/getting-started.md

29-29: Dollar signs used before commands without showing output
null

(MD014, commands-show-output)


35-35: Dollar signs used before commands without showing output
null

(MD014, commands-show-output)


41-41: Dollar signs used before commands without showing output
null

(MD014, commands-show-output)


46-46: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


47-47: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


48-48: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)


49-49: Unordered list style
Expected: dash; Actual: asterisk

(MD004, ul-style)

🔇 Additional comments (28)
tsconfig.json (4)

30-30: Verify compatibility of moduleResolution: "bundler"
Switching to the experimental "bundler" resolution is supported only in TS v5+. Ensure your CI and local toolchain use TS >= 5, and validate that existing imports resolve correctly under this strategy.


31-31: Approve baseUrl configuration
Setting "baseUrl": "./" aligns with your new path alias strategy and simplifies non-relative imports.


34-34: Approve generic path alias
Consolidating specific aliases into "@/*": ["./src/*"] improves maintainability and enforces consistent import paths under the layered architecture.


98-98: Validate noUncheckedIndexedAccess impact
Enabling this option tightens type safety by adding undefined to indexed access types but may introduce compilation errors. Confirm that the codebase has been updated (e.g., with non-null assertions or runtime guards) and that the full test suite compiles successfully.

docs/getting-started.md (2)

11-14: Consistent unordered list style

The prerequisites list items have been updated to use hyphens, aligning with the project's markdown style. This improves readability and consistency across documentation.


28-31: Normalize fenced code block indentation

The shell command examples are now unindented and aligned to the margin, making them easier to scan and consistent with other docs.

Also applies to: 34-37, 40-43

src/profiles/profiles.service.test.ts (2)

13-15: Method name updated to be more explicit.

The method name change from findByUsername to findProfileByUsername improves clarity and aligns with the domain-focused approach of the layered architecture.


24-24: Service instantiation correctly simplified.

The removal of UsersRepository dependency from the ProfilesService constructor improves separation of concerns and aligns with the layered architecture principles.

src/app.module.ts (3)

1-17: LGTM! Clean import organization following layered architecture.

The import statements are well-organized and clearly reflect the new layered architecture with dedicated controllers, error utilities, and proper separation of concerns.


83-87: Excellent transition to controller-based architecture.

The replacement of plugin-based routing with controller-based routing aligns perfectly with the layered architecture objectives. The controller registration is clean and follows a consistent pattern.


78-78: ```shell
#!/bin/bash

Check if @scalar/api-reference is declared in package.json

grep -R '"@scalar/api-reference"' -n package.json || echo "Not found in package.json"

Locate all uses of scalarVersion in source code

rg "scalarVersion" -n src/


</details>
<details>
<summary>src/common/errors/errors.utils.ts (2)</summary>

`10-17`: **Robust type guard implementation.**

The type guard correctly checks for the required properties and handles null/undefined cases properly.

---

`31-64`: **Well-structured validation error formatting with good cleanup logic.**

The function handles validation errors comprehensively, including:
- Fallback for empty error arrays
- Path parsing and message extraction
- Message cleanup to remove redundant prefixes
- Duplicate removal

The implementation is solid and handles edge cases well.

</details>
<details>
<summary>src/users/users.schema.ts (4)</summary>

`1-10`: **Good modular imports structure**

The imports are well-organized, separating domain-specific imports from framework imports. This aligns well with the layered architecture approach.

---

`26-31`: **Well-structured user relations**

The relations are properly defined with clear naming conventions, establishing connections to followers, following, articles, and favorites. This supports the modular architecture well.

---

`33-49`: **Robust follower relationship implementation**

The `userFollows` table correctly implements a many-to-many self-referential relationship with:
- Composite primary key preventing duplicate follows
- Cascade delete maintaining referential integrity
- Automatic timestamp management

This is a solid implementation of the follower pattern.

---

`12-24`: **Consider documenting password hashing strategy**

The schema correctly defines the password field, but ensure that password hashing is implemented at the service layer before storing in the database. Consider adding a comment to clarify this security requirement.

```shell
#!/bin/bash
# Description: Check if password hashing is implemented in the users service or repository

# Search for password hashing implementations
rg -A 5 "password|hash|bcrypt|argon2" --glob "src/users/**/*.{ts,js}" --glob "!**/*.test.*" --glob "!**/*.spec.*"

# Check for hashing libraries in package.json
cat package.json | jq '.dependencies | to_entries[] | select(.key | test("bcrypt|argon2|scrypt|crypto"))'
src/tags/tags.service.test.ts (2)

1-2: Import path correctly updated

The Database type import has been properly updated to reflect the new module structure.


12-14: Method name aligned with repository pattern

The override method name change from getTags to findTags correctly aligns with common repository naming conventions.

drizzle/migrations/0010_chubby_carnage.sql (1)

1-10: Migration looks correct - verify rollback strategy

The migration properly updates timestamp columns and removes unnecessary defaults. Ensure you have a rollback strategy in case issues arise.

#!/bin/bash
# Description: Check for corresponding down migration or rollback strategy

# Look for down migration files
fd -e sql . drizzle/migrations | grep -E "(down|rollback|revert)" || echo "No down migration files found"

# Check if there's a rollback script or documentation
fd -e md -e txt . | xargs rg -l "rollback|migration.*revert" || echo "No rollback documentation found"
src/tags/tags.schema.ts (3)

26-42: Well-structured article-tag relationship

The articleTags join table correctly implements the many-to-many relationship with:

  • Composite primary key
  • Proper foreign key constraints with cascade delete
  • Consistent timestamp management

44-55: Relations properly defined with clear naming

The bidirectional relations between tags and articles are well-defined with descriptive relation names.


11-18: Consider the implications of using tag name as primary key

Using name as the primary key means:

  • Tag names cannot be changed without updating all references
  • May have performance implications with string-based joins
  • Ensures tag uniqueness naturally

This is a valid design choice but ensure it aligns with your business requirements.

#!/bin/bash
# Description: Check how tags are used in the codebase to verify if tag names need to be mutable

# Search for tag update operations
rg -A 5 "update.*tag|tag.*update|rename.*tag|tag.*rename" --glob "src/**/*.{ts,js}" --glob "!**/*.test.*"

# Check if there are any tag normalization or validation logic
rg -A 3 "tag.*normalize|tag.*validate|tag.*transform" --glob "src/**/*.{ts,js}"
src/articles/articles.schema.ts (2)

30-46: Correct implementation of favorites feature

The favoriteArticles join table properly implements the many-to-many relationship between users and their favorite articles with appropriate constraints.


48-79: Well-organized article relations

The relations comprehensively cover all article associations:

  • Author relationship
  • Favorites tracking
  • Comments association
  • Tags relationship

The relation names are consistent and descriptive.

src/articles/articles.repository.ts (3)

1-24: Well-structured imports and type definitions.

The imports follow the new modular architecture pattern nicely, and the FindFilters and FindOptions types provide clear, type-safe interfaces for the repository methods.


100-149: Filter combination logic is now correctly implemented.

Great fix! The previous issue where filters were overwriting each other has been properly resolved. All filters are now accumulated into the allFilters array and combined using logical AND operations before being applied to both queries.


167-247: CRUD methods are well-implemented with proper error handling.

The repository methods demonstrate excellent practices:

  • Consistent null handling with explicit ?? null returns
  • Proper authorization checks in update/delete operations
  • Clear JSDoc documentation
  • Type-safe return types

Comment on lines +28 to +30
await expect(
service.findProfileByUsername(1, 'testuser'),
).rejects.toThrow('Profile not found');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Verification correct

Fix the failing test assertion.

The test is failing because the expected error message doesn't match the actual error thrown by the service. The test expects "Profile not found" but the service throws an error with message "profile".

Verify the actual error message thrown by ProfilesService.findProfileByUsername() when a profile is not found:

Update the test expectation to match the actual error message, or ensure the service throws the expected "Profile not found" message.


🏁 Script executed:

#!/bin/bash
# Description: Find the actual error message thrown by ProfilesService when profile not found
# Expected: Identify the correct error message to use in the test

ast-grep --pattern $'class ProfilesService {
  $$$
  findProfileByUsername($$$) {
    $$$
  }
  $$$
}'

# Also search for error throwing patterns in the profiles service
rg -A 5 -B 5 "throw.*[Ee]rror|NotFoundError" src/profiles/profiles.service.ts

Length of output: 1831


Fix the failing test assertion.

The test is failing because the expected error message doesn't match the actual error thrown by the service. The test expects "Profile not found" but the service throws an error with message "profile".

Verify the actual error message thrown by ProfilesService.findProfileByUsername() when a profile is not found:

#!/bin/bash
# Description: Find the actual error message thrown by ProfilesService when profile not found
# Expected: Identify the correct error message to use in the test

ast-grep --pattern $'class ProfilesService {
  $$$
  findProfileByUsername($$$) {
    $$$
  }
  $$$
}'

# Also search for error throwing patterns in the profiles service
rg -A 5 -B 5 "throw.*[Ee]rror|NotFoundError" src/profiles/profiles.service.ts

Update the test expectation to match the actual error message, or ensure the service throws the expected "Profile not found" message.

🧰 Tools
🪛 GitHub Check: test (latest)

[failure] 30-30: error: expect(received).toThrow(expected)
Expected substring: "Profile not found"
Received message: "profile"

  at <anonymous> (/home/runner/work/bedstack/bedstack/src/profiles/profiles.service.test.ts:30:17)
  at <anonymous> (/home/runner/work/bedstack/bedstack/src/profiles/profiles.service.test.ts:27:64)
🤖 Prompt for AI Agents
In src/profiles/profiles.service.test.ts around lines 28 to 30, the test expects
the error message "Profile not found" but the actual error thrown by
findProfileByUsername() is "profile". Verify the exact error message thrown by
the service by inspecting the service code or running the suggested search
commands. Then update the test assertion to expect the actual error message or
modify the service to throw the expected "Profile not found" message to align
the test with the service behavior.

Comment on lines +74 to +81
export function formatDBError(error: DrizzleQueryError) {
console.error(error);
return {
errors: {
database: 'error occurred',
},
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Database error handling needs improvement for debugging.

The current implementation logs to console and returns a generic message, which makes debugging difficult in production:

 export function formatDBError(error: DrizzleQueryError) {
-  console.error(error);
+  // TODO: Replace with proper logging library
+  console.error('[DB Error]', {
+    message: error.message,
+    cause: error.cause,
+    stack: error.stack
+  });
+  
   return {
     errors: {
-      database: 'error occurred',
+      database: process.env.NODE_ENV === 'development' 
+        ? error.message 
+        : 'A database error occurred',
     },
   };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function formatDBError(error: DrizzleQueryError) {
console.error(error);
return {
errors: {
database: 'error occurred',
},
};
}
export function formatDBError(error: DrizzleQueryError) {
// TODO: Replace with proper logging library
console.error('[DB Error]', {
message: error.message,
cause: error.cause,
stack: error.stack
});
return {
errors: {
database: process.env.NODE_ENV === 'development'
? error.message
: 'A database error occurred',
},
};
}
🤖 Prompt for AI Agents
In src/common/errors/errors.utils.ts around lines 74 to 81, improve the database
error handling by replacing the generic console.error call with structured
logging that captures detailed error information. Modify the returned error
object to include specific error details or codes from the DrizzleQueryError to
aid debugging while avoiding exposing sensitive information. This will enhance
observability and troubleshooting in production environments.

Comment on lines 157 to +159
const resultsCount = await this.db
.select({ count: count() })
.from(resultsQuery);
.from(countQuery.as('count_query'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix count query to avoid duplicate counting.

The count query has a critical bug that can lead to inflated article counts. Since countQuery includes LEFT JOINs to articleTags and favoriteArticles, articles with multiple tags or multiple favorited-by users will create duplicate rows, causing the count to be incorrect.

Apply this fix to get accurate counts:

-const resultsCount = await this.db
-  .select({ count: count() })
-  .from(countQuery.as('count_query'));
+const resultsCount = await this.db
+  .select({ count: count(sql`distinct ${articles.id}`) })
+  .from(countQuery.as('count_query'));

Alternatively, create a simpler count query without the problematic JOINs:

-// Create a separate count query that only includes filtering conditions
-const countQuery = this.db
-  .select({ id: articles.id })
-  .from(articles)
-  .innerJoin(users, eq(users.id, articles.authorId))
-  .leftJoin(articleTags, eq(articleTags.articleId, articles.id))
-  .leftJoin(favoriteArticles, eq(favoriteArticles.articleId, articles.id));
+// Create a separate count query that only includes filtering conditions
+const countQuery = this.db
+  .select({ id: articles.id })
+  .from(articles)
+  .innerJoin(users, eq(users.id, articles.authorId));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resultsCount = await this.db
.select({ count: count() })
.from(resultsQuery);
.from(countQuery.as('count_query'));
const resultsCount = await this.db
- .select({ count: count() })
+ .select({ count: count(sql`distinct ${articles.id}`) })
.from(countQuery.as('count_query'));
🤖 Prompt for AI Agents
In src/articles/articles.repository.ts around lines 157 to 159, the count query
uses LEFT JOINs to articleTags and favoriteArticles, which causes duplicate rows
and inflates the article count. To fix this, rewrite the count query to exclude
these JOINs or use a distinct count on the article ID to avoid duplicates.
Ensure the count query only counts unique articles by simplifying the query or
applying DISTINCT on the article identifier.

@coderabbitai coderabbitai bot mentioned this pull request Jan 19, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Code changes that do not alter end functionality, or fix a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant