Repository: Space2Study-UA-1355-01/BackEnd-01
Pull request #14: Feature/98 implement get categories endpoint
Author: login: VoroninaMaria
Created: 2025-04-24T11:54:20Z | Merged: 2025-04-25T11:36:26Z

 Implement get categories endpoint. Add route/controller/service, swagger docs and test.

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

- **New Features**
  - Introduced a new endpoint to retrieve a paginated and filterable list of categories, accessible to authenticated users with student or tutor roles.
- **Documentation**
  - Added OpenAPI documentation for the categories endpoint, including details on authentication, query parameters, response structure, and error responses.
- **Tests**
  - Added unit tests to verify correct retrieval and pagination of categories.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/docs/categories.yaml b/docs/categories.yaml
index a7036e5..63693b6 100644
--- a/docs/categories.yaml
+++ b/docs/categories.yaml
@@ -1,4 +1,9 @@
 components:
+  securitySchemes:
+    bearerAuth:
+      type: http
+      scheme: bearer
+      bearerFormat: JWT
   schemas:
     Category:
       type: object
@@ -24,3 +29,64 @@ components:
               type: string
               example: "#66C42C"
               description: Color of the category icon. Defaults to '#66C42C'.
+paths:
+  /categories:
+    get:
+      tags:
+        - Categories
+      summary: Get a paginated and filtered list of categories
+      description: Returns categories with optional filtering by name and pagination.
+      operationId: getCategories
+      security:
+        - bearerAuth: []
+      parameters:
+        - name: search
+          in: query
+          required: false
+          schema:
+            type: string
+          description: Filter categories by name starting with this string.
+        - name: page
+          in: query
+          required: false
+          schema:
+            type: integer
+            default: 1
+            minimum: 1
+          description: Page number for pagination.
+        - name: limit
+          in: query
+          required: false
+          schema:
+            type: integer
+            default: 20
+            minimum: 1
+          description: Number of categories per page.
+      responses:
+        '200':
+          description: List of categories
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    type: array
+                    items:
+                      $ref: '#/components/schemas/Category'
+                  total:
+                    type: integer
+                    example: 57
+                  page:
+                    type: integer
+                    example: 1
+                  limit:
+                    type: integer
+                    example: 20
+                  totalPages:
+                    type: integer
+                    example: 3
+        '401':
+          description: Unauthorized – JWT token is missing or invalid
+        '403':
+          description: Forbidden – User doesn't have access
\ No newline at end of file
diff --git a/src/controllers/category.js b/src/controllers/category.js
new file mode 100644
index 0000000..e61929b
--- /dev/null
+++ b/src/controllers/category.js
@@ -0,0 +1,17 @@
+const categoryService = require('~/services/category')
+
+const getCategories = async (req, res) => {
+  const { search = '', page = 1, limit = 20 } = req.query;
+
+  const parsedPage = Math.max(1, parseInt(page, 10) || 1);
+  const parsedLimit = Math.min(100, Math.max(1, parseInt(limit, 10) || 20));
+
+  const categories = await categoryService.getCategories({
+    search,
+    page: parsedPage,
+    limit: parsedLimit,
+  });
+
+  res.status(200).json(categories);
+};
+
diff --git a/src/routes/category.js b/src/routes/category.js
new file mode 100644
index 0000000..60314df
--- /dev/null
+++ b/src/routes/category.js
@@ -0,0 +1,14 @@
+const router = require('express').Router()
+const asyncWrapper = require('~/middlewares/asyncWrapper')
+const { authMiddleware, restrictTo } = require('~/middlewares/auth')
+
+const categoryController = require('~/controllers/category')
+
+router.get(
+  '/',
+  authMiddleware,
+  restrictTo('student', 'tutor'),
+  asyncWrapper(categoryController.getCategories)
+)
+
+module.exports = router
diff --git a/src/routes/index.js b/src/routes/index.js
index ab501f6..96dc1d1 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -7,6 +7,7 @@ const adminInvitation = require('~/routes/adminInvitation')
 const question = require('~/routes/question')
 const resourcesCategory = require('~/routes/resourcesCategory')
 const offer = require('~/routes/offer')
+const category = require('~/routes/category')
 const country = require('~/routes/countries')
 
 router.use('/auth', auth)
@@ -16,6 +17,8 @@ router.use('/admin-invitations', adminInvitation)
 router.use('/questions', question)
 router.use('/resources-categories', resourcesCategory)
 router.use('/offers', offer)
+router.use('/categories', category)
 router.use('/countries', country)
 
+
 module.exports = router
diff --git a/src/services/category.js b/src/services/category.js
new file mode 100644
index 0000000..2e79b5d
--- /dev/null
+++ b/src/services/category.js
@@ -0,0 +1,33 @@
+const Category = require('~/models/category')
+
+const categoryService = {
+  getCategories: async ({ search = '', page = 1, limit = 20 } = {}) => {
+    const query = {};
+
+    if (search) {
+      query.name = { $regex: `^${search}`, $options: 'i' };
+    }
+ 
+    const skip = (page - 1) * limit;
+
+    const [categories, total] = await Promise.all([
+      Category.find(query).skip(skip).limit(limit).lean().exec(),
+      Category.countDocuments(query)
+    ]);
+
+    return {
+      data: categories,
+      total,
+      page,
+      limit,
+      totalPages: Math.ceil(total / limit)
+    };
+  }
+};
+
+
+module.exports = categoryService
+
+
+
+
diff --git a/src/test/unit/services/category.spec.js b/src/test/unit/services/category.spec.js
new file mode 100644
index 0000000..55c90fc
--- /dev/null
+++ b/src/test/unit/services/category.spec.js
@@ -0,0 +1,49 @@
+const categoryService = require('~/services/category')
+const Category = require('~/models/category')
+
+jest.mock('~/models/category', () => ({
+  find: jest.fn(),
+  countDocuments: jest.fn(),
+  aggregate: jest.fn(),
+  findByIdAndRemove: jest.fn(),
+}))
+
+describe('categoryService', () => {
+  describe('getCategories', () => {
+    it('should return all categories without pagination and search', async () => {
+      const mockCategories = [
+        { _id: '1', name: 'Math', appearance: { icon: 'icon1', color: '#FF0000' } },
+        { _id: '2', name: 'Science', appearance: { icon: 'icon2', color: '#00FF00' } },
+        { _id: '3', name: 'Math1', appearance: { icon: 'icon1', color: '#FF0000' } },
+        { _id: '4', name: 'Science1', appearance: { icon: 'icon2', color: '#00FF00' } },
+      ]
+      const mockTotal = 4
+
+      Category.find.mockReturnValue({
+        skip: jest.fn().mockReturnThis(),
+        limit: jest.fn().mockReturnThis(),  
+        lean: jest.fn().mockReturnThis(),  
+        exec: jest.fn().mockResolvedValue(mockCategories),  
+      })
+
+      Category.countDocuments.mockResolvedValue(mockTotal)
+
+      const result = await categoryService.getCategories({
+        search: '',  
+        page: 1,  
+        limit: 10,
+      })
+
+      console.log(result)
+
+      expect(Category.find).toHaveBeenCalledWith({})
+      expect(result).toEqual({
+        data: mockCategories,
+        total: mockTotal,
+        page: 1,
+        limit: 10,
+        totalPages: 1,
+      })
+    })
+  })
+})


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>src/models/category.js (2)</summary><blockquote>

`7-32`: **Fix indentation in the appearance object**

The schema structure is correct, but there are inconsistent indentation levels in the appearance object properties. This is also flagged by ESLint.

Apply this diff to fix the indentation:

```diff
const categorySchema = new Schema(
  {
    name: {
      type: String,
      required: [true, FIELD_CANNOT_BE_EMPTY('name')],
      unique: true,
      trim: true
    },
    appearance: {
-        icon: {
-          type: String,
-          default: 'mocked-path-to-icon',
-          required: [true, FIELD_CANNOT_BE_EMPTY('icon')]
-        },
-        color: {
-          type: String,
-          default: '#66C42C',
-          required: [true, FIELD_CANNOT_BE_EMPTY('color')]
-        }
-      }
+      icon: {
+        type: String,
+        default: 'mocked-path-to-icon',
+        required: [true, FIELD_CANNOT_BE_EMPTY('icon')]
+      },
+      color: {
+        type: String,
+        default: '#66C42C',
+        required: [true, FIELD_CANNOT_BE_EMPTY('color')]
+      }
+    }
  },
  {
    timestamps: true,
    versionKey: false
  }
);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-17: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-18: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-19: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-20: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-21: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-22: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-23: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-24: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 25-25: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 25-26: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 26-26: Expected indentation of 4 spaces but found 6.

(indent)

---

[error] 26-27: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 27-27: Expected indentation of 2 spaces but found 4.

(indent)

---

[error] 27-28: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 32-32: Extra semicolon.

(semi)

</details>

</details>

---

`18-18`: **Consider using a configurable default for the icon path**

The hard-coded mock path `'mocked-path-to-icon'` suggests this might be temporary. Consider using a constant or environment variable for better maintainability.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-18: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</blockquote></details>
<details>
<summary>src/test/unit/services/category.spec.js (2)</summary><blockquote>

`37-37`: **Remove console.log statement from test.**

This debugging statement should be removed from the production code.


```diff
-      console.log(result)
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 36-37: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 37-38: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

---

`11-49`: **Add test case for searching by name.**

The current test only covers the scenario where no search term is provided. Since the service supports filtering by name prefix, it would be beneficial to add a test case that verifies this functionality.


Consider adding a test like this:

```javascript
it('should filter categories by name when search is provided', async () => {
  const mockCategories = [
    { _id: '1', name: 'Math', appearance: { icon: 'icon1', color: '#FF0000' } },
    { _id: '3', name: 'Math1', appearance: { icon: 'icon1', color: '#FF0000' } },
  ]
  const mockTotal = 2
  
  Category.find.mockReturnValue({
    skip: jest.fn().mockReturnThis(),
    limit: jest.fn().mockReturnThis(),  
    lean: jest.fn().mockReturnThis(),  
    exec: jest.fn().mockResolvedValue(mockCategories),  
  })
  
  Category.countDocuments.mockResolvedValue(mockTotal)
  
  const result = await categoryService.getCategories({
    search: 'Math',  
    page: 1,  
    limit: 10,
  })
  
  expect(Category.find).toHaveBeenCalledWith({ name: { $regex: '^Math', $options: 'i' } })
  expect(result).toEqual({
    data: mockCategories,
    total: mockTotal,
    page: 1,
    limit: 10,
    totalPages: 1,
  })
})
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 25-26: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 26-27: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 27-28: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 32-33: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 33-34: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 34-35: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 35-36: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 36-37: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 37-38: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 38-39: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 39-40: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 40-41: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 41-42: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 42-43: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 43-44: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 44-45: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 45-46: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 46-47: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 47-48: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 48-49: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</blockquote></details>
<details>
<summary>src/services/category.js (2)</summary><blockquote>

`28-34`: **Remove extra blank lines at the end of the file.**

There are multiple unnecessary blank lines at the end of the file.


```diff
module.exports = categoryService

-
-
-
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-34: Too many blank lines at the end of file. Max of 1 allowed.

(no-multiple-empty-lines)

---

[error] 32-33: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 33-34: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

---

`7-9`: **Consider sanitizing the search input for regex safety.**

While the current implementation uses anchored regex which reduces risk, it's still a best practice to sanitize or escape regex special characters in user input to prevent potential ReDoS (Regular Expression Denial of Service) attacks.


Consider adding a helper function to escape regex special characters:

```diff
  if (search) {
-    query.name = { $regex: `^${search}`, $options: 'i' };
+    const sanitizedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+    query.name = { $regex: `^${sanitizedSearch}`, $options: 'i' };
  }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-8: Extra semicolon.

(semi)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</blockquote></details>
<details>
<summary>docs/categories.yaml (1)</summary><blockquote>

`92-92`: **Add newline at the end of the file.**

YAML files should end with a newline character as per common conventions and as flagged by YAMLlint.


```diff
          description: Forbidden – User doesn't have access
+
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.35.1)</summary>

[error] 92-92: no new line character at the end of file

(new-line-at-end-of-file)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1f61dcec2913b3131b96a351465a8f9b7fd54188 and 4bf59c8e7f3aea32d9a3c382b5803e3f36983840.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `docs/categories.yaml` (1 hunks)
* `src/consts/models.js` (1 hunks)
* `src/controllers/category.js` (1 hunks)
* `src/models/category.js` (1 hunks)
* `src/routes/category.js` (1 hunks)
* `src/routes/index.js` (2 hunks)
* `src/services/category.js` (1 hunks)
* `src/test/unit/services/category.spec.js` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>src/routes/category.js (2)</summary><blockquote>

<details>
<summary>src/routes/index.js (1)</summary>

* `router` (1-1)

</details>
<details>
<summary>src/middlewares/auth.js (2)</summary>

* `authMiddleware` (4-10)
* `restrictTo` (12-19)

</details>

</blockquote></details>
<details>
<summary>src/routes/index.js (1)</summary><blockquote>

<details>
<summary>src/routes/category.js (2)</summary>

* `require` (3-3)
* `router` (1-1)

</details>

</blockquote></details>
<details>
<summary>src/controllers/category.js (1)</summary><blockquote>

<details>
<summary>src/services/category.js (2)</summary>

* `categoryService` (3-26)
* `categories` (13-16)

</details>

</blockquote></details>
<details>
<summary>src/services/category.js (3)</summary><blockquote>

<details>
<summary>src/test/unit/services/category.spec.js (2)</summary>

* `Category` (2-2)
* `categoryService` (1-1)

</details>
<details>
<summary>src/routes/category.js (1)</summary>

* `require` (3-3)

</details>
<details>
<summary>src/controllers/category.js (2)</summary>

* `categoryService` (1-1)
* `categories` (6-10)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>src/consts/models.js</summary>

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>
<details>
<summary>src/routes/category.js</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>
<details>
<summary>src/routes/index.js</summary>

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>
<details>
<summary>src/models/category.js</summary>

[error] 1-1: Extra semicolon.

(semi)

---

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-5: Extra semicolon.

(semi)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-17: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-18: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-19: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-20: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-21: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-22: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-23: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-24: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 25-25: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 25-26: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 26-26: Expected indentation of 4 spaces but found 6.

(indent)

---

[error] 26-27: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 27-27: Expected indentation of 2 spaces but found 4.

(indent)

---

[error] 27-28: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 32-32: Extra semicolon.

(semi)

---

[error] 32-33: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 33-34: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 34-34: Extra semicolon.

(semi)

</details>
<details>
<summary>src/controllers/category.js</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-10: Extra semicolon.

(semi)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-12: Extra semicolon.

(semi)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-13: Extra semicolon.

(semi)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-17: Newline required at end of file but not found.

(eol-last)

</details>
<details>
<summary>src/test/unit/services/category.spec.js</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 25-26: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 26-27: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 27-28: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 32-33: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 33-34: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 34-35: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 35-36: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 36-37: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 37-38: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 38-39: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 39-40: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 40-41: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 41-42: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 42-43: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 43-44: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 44-45: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 45-46: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 46-47: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 47-48: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 48-49: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>
<details>
<summary>src/services/category.js</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-5: Extra semicolon.

(semi)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-8: Extra semicolon.

(semi)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-11: Extra semicolon.

(semi)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Extra semicolon.

(semi)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-24: Extra semicolon.

(semi)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 25-26: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 26-26: Extra semicolon.

(semi)

---

[error] 26-27: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 27-28: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 32-33: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>
<details>
<summary>🪛 YAMLlint (1.35.1)</summary>

<details>
<summary>docs/categories.yaml</summary>

[error] 92-92: no new line character at the end of file

(new-line-at-end-of-file)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>src/consts/models.js (1)</summary>

`15-16`: **LGTM: Good addition of the CATEGORY constant**

The addition of the `CATEGORY` constant follows the existing naming convention and the trailing comma after `RESOURCES_CATEGORY` is a good practice for cleaner diffs in future changes.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</details>
<details>
<summary>src/routes/index.js (2)</summary>

`10-10`: **LGTM: Category route import added correctly**

The import of the category route module follows the same pattern as other route imports in the file.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

---

`19-19`: **LGTM: Route mounted correctly**

The category route is properly mounted at the `/categories` path following the established pattern.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</details>
<details>
<summary>src/models/category.js (1)</summary>

`1-34`: **LGTM: Overall schema structure is well-defined**

The Category schema is well-structured with appropriate validation rules. The use of required fields, unique constraints, and default values is good practice for data integrity.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-1: Extra semicolon.

(semi)

---

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-5: Extra semicolon.

(semi)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-17: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-18: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-19: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-20: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-21: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-22: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-23: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-24: Expected indentation of 8 spaces but found 10.

(indent)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 25-25: Expected indentation of 6 spaces but found 8.

(indent)

---

[error] 25-26: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 26-26: Expected indentation of 4 spaces but found 6.

(indent)

---

[error] 26-27: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 27-27: Expected indentation of 2 spaces but found 4.

(indent)

---

[error] 27-28: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 28-29: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 29-30: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 30-31: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 31-32: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 32-32: Extra semicolon.

(semi)

---

[error] 32-33: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 33-34: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 34-34: Extra semicolon.

(semi)

</details>

</details>

</details>
<details>
<summary>src/controllers/category.js (1)</summary>

`15-17`: **LGTM: Clean module exports**

The module exports are clean and well-defined.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-17: Newline required at end of file but not found.

(eol-last)

</details>

</details>

</details>
<details>
<summary>src/routes/category.js (1)</summary>

`1-14`: **Good implementation of the category router with proper authorization.**

The route is correctly set up with authentication and role-based access control, restricting access to only students and tutors. The use of `asyncWrapper` to handle potential async errors is a good practice.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</details>
<details>
<summary>src/services/category.js (1)</summary>

`4-25`: **Well-implemented pagination and search for category retrieval.**

The service correctly implements pagination and search functionality with appropriate defaults. The use of Promise.all for concurrent queries is efficient.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-5: Extra semicolon.

(semi)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-8: Extra semicolon.

(semi)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-11: Extra semicolon.

(semi)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Extra semicolon.

(semi)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 17-18: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 18-19: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 24-24: Extra semicolon.

(semi)

---

[error] 24-25: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</details>
<details>
<summary>docs/categories.yaml (1)</summary>

`1-92`: **Well-defined OpenAPI specification for the categories endpoint.**

The specification properly documents the GET endpoint with all necessary query parameters, response structure, and security requirements. It aligns well with the implemented service and controller.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 YAMLlint (1.35.1)</summary>

[error] 92-92: no new line character at the end of file

(new-line-at-end-of-file)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-24T11:57:32Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>src/controllers/category.js (1)</summary><blockquote>

`1-17`: _⚠️ Potential issue_

**Missing error handling and module exports**

The controller doesn't handle potential errors from the service layer and is missing the module exports statement at the end of the file.


Add error handling and the exports statement:

```diff
const categoryService = require('~/services/category')

const getCategories = async (req, res) => {
  const { search = '', page = 1, limit = 20 } = req.query;

  const parsedPage = Math.max(1, parseInt(page, 10) || 1);
  const parsedLimit = Math.min(100, Math.max(1, parseInt(limit, 10) || 20));

+  try {
    const categories = await categoryService.getCategories({
      search,
      page: parsedPage,
      limit: parsedLimit,
    });

    res.status(200).json(categories);
+  } catch (error) {
+    console.error('Error fetching categories:', error);
+    res.status(500).json({ message: 'Failed to fetch categories' });
+  }
};

+module.exports = {
+  getCategories
+};
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-3: 'getCategories' is assigned a value but never used.

(no-unused-vars)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-6: Extra semicolon.

(semi)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-7: Extra semicolon.

(semi)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-13: Extra semicolon.

(semi)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-15: Extra semicolon.

(semi)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Extra semicolon.

(semi)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/routes/index.js (2)</summary><blockquote>

`20-20`: **Properly mounted the category routes**

The category routes are correctly mounted at the '/categories' path, consistent with the established pattern in the codebase.


Note: There are ESLint errors about linebreak style (CRLF vs LF). Consider normalizing line endings to match the project's configuration.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 19-20: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

---

`23-23`: **Empty line should be removed**

This appears to be an extra empty line that's not necessary.


```diff
 router.use('/categories', category)
 router.use('/countries', country)
 
-
 module.exports = router
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 23-24: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4bf59c8e7f3aea32d9a3c382b5803e3f36983840 and 08ff51ee1aabbd702a06e75a51690201942da041.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/controllers/category.js` (1 hunks)
* `src/routes/index.js` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>src/controllers/category.js (2)</summary><blockquote>

<details>
<summary>src/services/category.js (2)</summary>

* `categoryService` (3-26)
* `categories` (13-16)

</details>
<details>
<summary>src/test/unit/services/category.spec.js (1)</summary>

* `categoryService` (1-1)

</details>

</blockquote></details>
<details>
<summary>src/routes/index.js (2)</summary><blockquote>

<details>
<summary>src/routes/category.js (2)</summary>

* `require` (3-3)
* `router` (1-1)

</details>
<details>
<summary>src/routes/countries.js (1)</summary>

* `router` (2-2)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>src/controllers/category.js</summary>

[error] 1-2: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 2-3: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 3-3: 'getCategories' is assigned a value but never used.

(no-unused-vars)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-6: Extra semicolon.

(semi)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-7: Extra semicolon.

(semi)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-13: Extra semicolon.

(semi)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-15: Extra semicolon.

(semi)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Extra semicolon.

(semi)

---

[error] 16-17: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>
<details>
<summary>src/routes/index.js</summary>

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 20-21: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 21-22: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 22-23: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>src/routes/index.js (1)</summary>

`10-10`: **Properly added new category route module**

The import of the category route is correctly added following the existing pattern in the file.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

</details>
<details>
<summary>src/controllers/category.js (2)</summary>

`3-16`: **Validation of pagination parameters is implemented correctly**

The controller properly validates and sanitizes the pagination parameters, ensuring that:
- `page` is at least 1
- `limit` is between 1 and 100
- Both parameters are correctly parsed to integers with radix

This addresses one of the previous review comments.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 3-3: 'getCategories' is assigned a value but never used.

(no-unused-vars)

---

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-6: Extra semicolon.

(semi)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-7: Extra semicolon.

(semi)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-13: Extra semicolon.

(semi)

---

[error] 13-14: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 14-15: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 15-15: Extra semicolon.

(semi)

---

[error] 15-16: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 16-16: Extra semicolon.

(semi)

</details>

</details>

---

`6-7`: **Good practice: Using explicit radix parameter in parseInt**

Using the radix parameter (10) in parseInt is a good practice to avoid unexpected behavior when parsing numbers that may start with '0'.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 6-6: Extra semicolon.

(semi)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-7: Extra semicolon.

(semi)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-24T12:05:55Z
user: login: ssashayurchenko
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:44:50Z
user: login: NataliiaDanchenko
state: APPROVED
author_association: NONE
submitted_at: 2025-04-24T21:12:54Z
user: login: yrudikov
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T07:21:01Z
user: login: vlad-khrychov
state: APPROVED
author_association: NONE
submitted_at: 2025-04-25T11:09:27Z

Review comments:
diff_hunk: @@ -0,0 +1,17 @@
+const categoryService = require('~/services/category')
+
+const getCategories = async (req, res) => {
+  const { search = '', page = 1, limit = 20 } = req.query;
+
+  const categories = await categoryService.getCategories({
+    search,
+    page: parseInt(page),
+    limit: parseInt(limit),
path: src/controllers/category.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add validation for page and limit parameters**

The controller converts `page` and `limit` to integers without validating if they're valid numbers. Invalid inputs like "abc" would be converted to NaN, potentially causing issues in the service layer.

```diff
const getCategories = async (req, res) => {
  const { search = '', page = 1, limit = 20 } = req.query;

+  // Validate and parse pagination parameters
+  const parsedPage = Math.max(1, parseInt(page) || 1);
+  const parsedLimit = Math.min(100, Math.max(1, parseInt(limit) || 20));
+
  const categories = await categoryService.getCategories({
    search,
-    page: parseInt(page),
-    limit: parseInt(limit),
+    page: parsedPage,
+    limit: parsedLimit,
  });

  res.status(200).json(categories);
};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
const getCategories = async (req, res) => {
  const { search = '', page = 1, limit = 20 } = req.query;

  // Validate and parse pagination parameters
  const parsedPage = Math.max(1, parseInt(page) || 1);
  const parsedLimit = Math.min(100, Math.max(1, parseInt(limit) || 20));

  const categories = await categoryService.getCategories({
    search,
    page: parsedPage,
    limit: parsedLimit,
  });

  res.status(200).json(categories);
};
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>

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

✅ Addressed in commits 9b466ce to 08ff51e
created_at: 2025-04-24T11:57:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,17 @@
+const categoryService = require('~/services/category')
+
+const getCategories = async (req, res) => {
+  const { search = '', page = 1, limit = 20 } = req.query;
+
+  const categories = await categoryService.getCategories({
+    search,
+    page: parseInt(page),
+    limit: parseInt(limit),
+  });
+
+  res.status(200).json(categories);
+};
path: src/controllers/category.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling to the controller function**

The controller doesn't handle potential errors that could occur during the service call. Consider adding a try-catch block to gracefully handle exceptions.

```diff
const getCategories = async (req, res) => {
  const { search = '', page = 1, limit = 20 } = req.query;

+  try {
    const categories = await categoryService.getCategories({
      search,
      page: parseInt(page),
      limit: parseInt(limit),
    });

    res.status(200).json(categories);
+  } catch (error) {
+    console.error('Error fetching categories:', error);
+    res.status(500).json({ message: 'Failed to fetch categories' });
+  }
};
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
const getCategories = async (req, res) => {
  const { search = '', page = 1, limit = 20 } = req.query;

  try {
    const categories = await categoryService.getCategories({
      search,
      page: parseInt(page),
      limit: parseInt(limit),
    });

    res.status(200).json(categories);
  } catch (error) {
    console.error('Error fetching categories:', error);
    res.status(500).json({ message: 'Failed to fetch categories' });
  }
};
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 3-4: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 4-4: Extra semicolon.

(semi)

---

[error] 4-5: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 5-6: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 6-7: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 7-8: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 8-9: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 9-10: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 10-10: Extra semicolon.

(semi)

---

[error] 10-11: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 11-12: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 12-12: Extra semicolon.

(semi)

---

[error] 12-13: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 13-13: Extra semicolon.

(semi)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-24T11:57:30Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-24T11:54:26Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change introduces a complete implementation for a new `/categories` API endpoint. It adds OpenAPI documentation, a service for retrieving categories from the database with search and pagination, a controller to handle incoming HTTP requests, routing setup with authentication and role-based access control, and a unit test for the service logic. The endpoint is secured with JWT-based bearer authentication and is accessible to users with 'student' or 'tutor' roles. The response includes paginated category data, and the implementation is covered by automated tests.

## Changes

| File(s)                                             | Change Summary                                                                                                                      |
|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| docs/categories.yaml                                | Added OpenAPI spec for `/categories` endpoint, including security, schema, query parameters, and responses.                         |
| src/controllers/category.js                         | New controller with `getCategories` handler extracting query params, validating them, and returning service results as JSON.         |
| src/services/category.js                            | New service with `getCategories` method: searches, paginates, and counts categories from the database.                              |
| src/routes/category.js                              | New Express router: defines GET `/` with authentication, role restriction, and async error handling, using the controller.          |
| src/routes/index.js                                 | Registers the new `/categories` route in the main router.                                                                           |
| src/test/unit/services/category.spec.js             | Adds unit tests for `categoryService.getCategories`, mocking database calls and verifying correct logic and response structure.      |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant Router
    participant AuthMiddleware
    participant RestrictTo
    participant Controller
    participant Service
    participant Database

    Client->>Router: GET /categories?search=...&page=...&limit=...
    Router->>AuthMiddleware: Authenticate JWT
    AuthMiddleware-->>Router: Success/Failure
    Router->>RestrictTo: Check roles (student, tutor)
    RestrictTo-->>Router: Success/Failure
    Router->>Controller: getCategories(req, res)
    Controller->>Service: getCategories({search, page, limit})
    Service->>Database: Query categories (with filters & pagination)
    Database-->>Service: Categories data, total count
    Service-->>Controller: { data, total, page, limit, totalPages }
    Controller-->>Client: 200 OK with JSON response
```

## Possibly related PRs

- Space2Study-UA-1355-01/BackEnd-01#10: Adds the Category schema and model, which this PR extends by implementing the API, controller, service, route, and documentation for categories.

## Suggested reviewers

- ssashayurchenko
- yrudikov
- NataliiaDanchenko
- pavlo1377
- vlad-khrychov

## Poem

> In burrows deep, new routes appear,  
> Categories now hop in, clear!  
> With search and page, you’ll find your way,  
> Secured by JWT—no rabbit’s play.  
> Tests are written, docs are neat,  
> This change is truly quite a treat!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Space2Study-UA-1355-01/BackEnd-01&utm_content=14):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `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 generate unit testing code for this file.`
  -	`@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 generate unit testing code.`
  - `@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.`

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](https://docs.coderabbit.ai/finishing-touches/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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-24T12:04:24Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Space2Study-UA-1355-01_BackEnd-01&pullRequest=14)


author_association: NONE