Repository: Space2Study-UA-1355-02/BackEnd-02
Pull request #21: Subject
Author: login: ruslansymonenko
Created: 2025-05-07T10:44:16Z | Merged: 2025-05-07T17:17:10Z

Summary
This pull request adds CRUD API endpoints for the Subject model. Additionally, the Swagger documentation has been updated to reflect these changes.

Changes Made:
* Implemented Create, Read, Update, and Delete endpoints for Subject
* Updated Swagger documentation accordingly

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

## Summary by CodeRabbit

- **New Features**
  - Introduced full CRUD support for "Subject" entities, allowing users to create, view, update, and delete subjects via new API endpoints.
  - Added support for listing subjects with optional pagination and search filtering.
- **Documentation**
  - Added comprehensive API documentation for subject management, including endpoint details and request/response schemas.
- **Tests**
  - Added unit tests to ensure correct retrieval behavior and error handling for subject entities.

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


Diff:
diff --git a/src/controllers/subject.js b/src/controllers/subject.js
new file mode 100644
index 0000000..ae9ca6d
--- /dev/null
+++ b/src/controllers/subject.js
@@ -0,0 +1,49 @@
+const subjectService = require('~/services/subject')
+
+const getSubjectById = async (req, res) => {
+  const { id } = req.params
+
+  const subject = await subjectService.getSubjectById(id)
+
+  res.status(200).json(subject)
+}
+
+const getSubjects = async (req, res) => {
+  const { limit, offset, search } = req.query
+  const subjects = await subjectService.getSubjects(limit, offset, search)
+
+  res.status(200).json(subjects)
+}
+
+const createSubject = async (req, res) => {
+  const { name, categoryId } = req.body
+
+  const newSubject = await subjectService.createSubject(name, categoryId)
+
+  res.status(201).json(newSubject)
+}
+
+const updateSubject = async (req, res) => {
+  const { id } = req.params
+  const updateData = req.body
+
+  await subjectService.updateSubject(id, updateData)
+
+  res.status(204).end()
+}
+
+const deleteSubject = async (req, res) => {
+  const { id } = req.params
+
+  await subjectService.deleteSubject(id)
+
+  res.status(204).end()
+}
+
+module.exports = {
+  getSubjectById,
+  getSubjects,
+  createSubject,
+  updateSubject,
+  deleteSubject
+}
diff --git a/src/models/subject.js b/src/models/subject.js
new file mode 100644
index 0000000..6957ce8
--- /dev/null
+++ b/src/models/subject.js
@@ -0,0 +1,24 @@
+const { Schema, model, Types } = require('mongoose')
+const { CATEGORY, SUBJECT } = require('~/consts/models')
+const { FIELD_CANNOT_BE_EMPTY } = require('~/consts/errors')
+
+const subjectSchema = new Schema(
+  {
+    name: {
+      type: String,
+      required: [true, FIELD_CANNOT_BE_EMPTY('name')]
+    },
+    category: {
+      type: Types.ObjectId,
+      ref: CATEGORY,
+      required: [true, FIELD_CANNOT_BE_EMPTY('category')]
+    },
+    totalOffers: {
+      student: { type: Number, default: 0 },
+      tutor: { type: Number, default: 0 }
+    }
+  },
+  { timestamps: true, versionKey: false }
+)
+
+module.exports = model(SUBJECT, subjectSchema)
diff --git a/src/routes/index.js b/src/routes/index.js
index 2de036b..62fd9a0 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 subject = require('~/models/subject')
 const locations = require('~/routes/locations')
 
 router.use('/auth', auth)
@@ -16,6 +17,7 @@ router.use('/admin-invitations', adminInvitation)
 router.use('/questions', question)
 router.use('/resources-categories', resourcesCategory)
 router.use('/offers', offer)
+router.use('subject', subject)
 router.use('/locations', locations)
 
 module.exports = router
diff --git a/src/routes/subject.js b/src/routes/subject.js
new file mode 100644
index 0000000..2ba0136
--- /dev/null
+++ b/src/routes/subject.js
@@ -0,0 +1,27 @@
+const router = require('express').Router()
+
+const idValidation = require('~/middlewares/idValidation')
+const asyncWrapper = require('~/middlewares/asyncWrapper')
+const isEntityValid = require('~/middlewares/entityValidation')
+
+const subjectController = require('~/controllers/subject')
+const Subject = require('~/models/subject')
+const Category = require('~/models/category')
+
+const body = [{ model: Category, idName: 'categoryId' }]
+
+const params = [{ model: Subject, idName: 'id' }]
+
+router.param('id', idValidation)
+
+router.get('/:id', isEntityValid({ params }), asyncWrapper(subjectController.getSubjectById))
+
+router.get('/', asyncWrapper(subjectController.getSubjects))
+
+router.post('/', isEntityValid({ body }), asyncWrapper(subjectController.createSubject))
+
+router.patch('/:id', isEntityValid({ params }), asyncWrapper(subjectController.updateOffer))
+
+router.delete('/:id', isEntityValid({ params }), asyncWrapper(subjectController.deleteSubject))
+
+module.exports = router
diff --git a/src/services/subject.js b/src/services/subject.js
new file mode 100644
index 0000000..e2445d9
--- /dev/null
+++ b/src/services/subject.js
@@ -0,0 +1,77 @@
+const Subject = require('~/models/subject')
+const { createError } = require('~/utils/errorsHelper')
+const { NOT_FOUND } = require('~/consts/errors')
+const filterAllowedFields = require('~/src/utils/filterAllowedFields')
+const { allowedSubjectFieldsForUpdate } = require('~/src/validation/services/subject')
+const { paginate } = require('~/utils/paginate')
+const { filterBySearch } = require('~/utils/filterBySearch')
+
+const subjectService = {
+  getSubjectById: async (id) => {
+    const subject = await Subject.findById(id)
+
+    if (!subject) {
+      throw createError(404, NOT_FOUND)
+    }
+
+    return subject
+  },
+
+  getSubjects: async (limit, offset, search) => {
+    const limitNumber = parseInt(limit)
+    const offsetNumber = parseInt(offset)
+
+    const paginationLimit = !isNaN(limitNumber) && limitNumber > 0 ? limitNumber : 10
+    const paginationOffset = !isNaN(offsetNumber) && offsetNumber >= 0 ? offsetNumber : 0
+
+    const subjects = await Subject.find().lean()
+    const filteredSubjects = filterBySearch(subjects, search)
+
+    return {
+      data: {
+        subjects: paginate(filteredSubjects, paginationLimit, paginationOffset),
+        total: subjects.length,
+        limit: limit,
+        offset: offset || 0
+      }
+    }
+  },
+
+  createSubject: async (name, categoryId) => {
+    const subject = new Subject({
+      name,
+      category: categoryId
+    })
+
+    await subject.save()
+
+    return subject
+  },
+
+  updateSubject: async (id, updateData) => {
+    const filteredUpdateData = filterAllowedFields(updateData, allowedSubjectFieldsForUpdate)
+
+    const subject = await subjectService.getSubjectById(id)
+
+    for (let field in filteredUpdateData) {
+      subject[field] = filteredUpdateData[field]
+    }
+
+    await subject.validate()
+    await subject.save()
+
+    return subject
+  },
+
+  deleteSubject: async (id) => {
+    const subject = await Subject.findByIdAndRemove(id).exec()
+
+    if (!subject) {
+      throw createError(404, NOT_FOUND)
+    }
+
+    return subject
+  }
+}
+
+module.exports = subjectService
diff --git a/src/test/unit/services/subject.spec.js b/src/test/unit/services/subject.spec.js
new file mode 100644
index 0000000..d7f8660
--- /dev/null
+++ b/src/test/unit/services/subject.spec.js
@@ -0,0 +1,24 @@
+const subjectService = require('~/services/subject')
+const SubjectSpec = require('~/models/subject')
+const { NOT_FOUND } = require('~/consts/errors')
+
+jest.mock('~/models/subject')
+
+describe('subjectService.getSubjectById', () => {
+  afterEach(() => jest.clearAllMocks())
+
+  it('should return subject if it is exist', async () => {
+    const mockSubject = { _id: '123', name: 'Math' }
+    SubjectSpec.findById.mockResolvedValue(mockSubject)
+
+    const result = await subjectService.getSubjectById('123')
+    expect(result).toEqual(mockSubject)
+    expect(SubjectSpec.findById).toHaveBeenCalledWith('123')
+  })
+
+  it('should return error if subject was not found', async () => {
+    SubjectSpec.findById.mockResolvedValue(null)
+
+    await expect(subjectService.getSubjectById('123')).rejects.toThrow(NOT_FOUND)
+  })
+})
diff --git a/src/utils/docs/subject.yaml b/src/utils/docs/subject.yaml
new file mode 100644
index 0000000..da55f6a
--- /dev/null
+++ b/src/utils/docs/subject.yaml
@@ -0,0 +1,144 @@
+tags:
+  - name: Subject
+    description: Endpoints for managing subjects
+
+paths:
+  /subjects:
+    get:
+      summary: Get a list of subjects
+      tags: [Subject]
+      parameters:
+        - name: limit
+          in: query
+          schema:
+            type: integer
+            minimum: 1
+          description: Number of items to return
+        - name: offset
+          in: query
+          schema:
+            type: integer
+            minimum: 0
+          description: Number of items to skip
+        - name: search
+          in: query
+          schema:
+            type: string
+          description: Search term to filter subjects by name
+      responses:
+        '200':
+          description: A list of subjects
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    type: object
+                    properties:
+                      countries:
+                        type: array
+                        items:
+                          $ref: '#/components/schemas/Subject'
+                      total:
+                        type: integer
+                      limit:
+                        type: integer
+                      offset:
+                        type: integer
+
+    post:
+      summary: Create a new subject
+      tags: [Subject]
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              required: [name, categoryId]
+              properties:
+                name:
+                  type: string
+                categoryId:
+                  type: string
+      responses:
+        '201':
+          description: Subject created
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Subject'
+
+  /subjects/{id}:
+    get:
+      summary: Get subject by ID
+      tags: [Subject]
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: string
+          description: Subject ID
+      responses:
+        '200':
+          description: Success
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Subject'
+        '404':
+          description: Subject was not found
+
+    put:
+      summary: Update subject by ID
+      tags: [Subject]
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: string
+          description: Subject ID
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              description: Fields to update (filtered internally)
+              additionalProperties: true
+      responses:
+        '204':
+          description: Subject updated successfully
+        '404':
+          description: Subject not found
+
+    delete:
+      summary: Delete subject by ID
+      tags: [Subject]
+      parameters:
+        - name: id
+          in: path
+          required: true
+          schema:
+            type: string
+          description: Subject ID
+      responses:
+        '204':
+          description: Subject deleted successfully
+        '404':
+          description: Subject not found
+
+components:
+  schemas:
+    Subject:
+      type: object
+      properties:
+        _id:
+          type: string
+        name:
+          type: string
+        category:
+          type: string
diff --git a/src/validation/services/subject.js b/src/validation/services/subject.js
new file mode 100644
index 0000000..4f784b9
--- /dev/null
+++ b/src/validation/services/subject.js
@@ -0,0 +1,9 @@
+const allowedSubjectFieldsForUpdate = {
+  name: true,
+  category: true,
+  totalOffers: true
+}
+
+module.exports = {
+  allowedSubjectFieldsForUpdate
+}


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

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

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

`16-19`: **Consider adding validation for totalOffers counts**

The totalOffers fields allow any number values. Consider adding validation to ensure these values can't be negative.

```diff
 totalOffers: {
-      student: { type: Number, default: 0 },
-      tutor: { type: Number, default: 0 }
+      student: { type: Number, default: 0, min: [0, 'Student offers count cannot be negative'] },
+      tutor: { type: Number, default: 0, min: [0, 'Tutor offers count cannot be negative'] }
 }
```

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

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

[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)

</details>

</details>

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

`27-28`: **Consider optimizing search implementation for larger datasets**

The current implementation loads all subjects into memory before applying search filtering, which could lead to performance issues with large datasets.


Consider using MongoDB's built-in text search capabilities to filter at the database level:

```diff
-    const subjects = await Subject.find().lean()
-    const filteredSubjects = filterBySearch(subjects, search)
+    let query = {}
+    if (search) {
+      query.name = { $regex: `^${search}`, $options: 'i' }
+    }
+    const subjects = await Subject.find(query).lean()
+    const total = await Subject.countDocuments(query)
```

Then update the return statement to use this total and the subjects directly.

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

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

[error] 27-28: 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 3a6bdf5229e7ec82af5dc9523f6c3f81b02d8b75 and 86b76bc9a82724fe01dd1a6a26b8bb711e001ad8.

</details>

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

* `src/controllers/subject.js` (1 hunks)
* `src/models/subject.js` (1 hunks)
* `src/routes/index.js` (2 hunks)
* `src/routes/subject.js` (1 hunks)
* `src/services/subject.js` (1 hunks)
* `src/test/unit/services/subject.spec.js` (1 hunks)
* `src/utils/docs/subject.yaml` (1 hunks)
* `src/validation/services/subject.js` (1 hunks)

</details>

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

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

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

<details>
<summary>src/services/subject.js (9)</summary>

* `subject` (11-11)
* `subject` (41-44)
* `subject` (54-54)
* `subject` (67-67)
* `require` (2-2)
* `require` (3-3)
* `require` (5-5)
* `require` (6-6)
* `require` (7-7)

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

* `router` (1-1)

</details>

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

<details>
<summary>src/services/subject.js (6)</summary>

* `subjectService` (9-75)
* `subject` (11-11)
* `subject` (41-44)
* `subject` (54-54)
* `subject` (67-67)
* `subjects` (27-27)

</details>

</blockquote></details>
<details>
<summary>src/services/subject.js (5)</summary><blockquote>

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

* `require` (3-3)

</details>
<details>
<summary>src/utils/errorsHelper.js (1)</summary>

* `createError` (3-9)

</details>
<details>
<summary>src/utils/filterBySearch.js (1)</summary>

* `filterBySearch` (1-7)

</details>
<details>
<summary>src/utils/paginate.js (1)</summary>

* `paginate` (1-5)

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

* `allowedSubjectFieldsForUpdate` (1-5)

</details>

</blockquote></details>

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

<details>
<summary>src/test/unit/services/subject.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)

</details>
<details>
<summary>src/validation/services/subject.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)

</details>
<details>
<summary>src/models/subject.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)

</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] 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>
<summary>src/controllers/subject.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/routes/subject.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)

</details>
<details>
<summary>src/services/subject.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)

---

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

(linebreak-style)

---

[error] 50-51: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 51-52: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 52-53: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 53-54: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 54-55: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 55-56: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 56-57: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 57-58: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 58-59: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 59-60: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 60-61: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 61-62: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 62-63: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 63-64: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 64-65: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 65-66: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 66-67: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 67-68: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 68-69: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 69-70: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 70-71: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 71-72: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 72-73: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 73-74: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 74-75: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 75-76: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

---

[error] 76-77: Expected linebreaks to be 'CRLF' but found 'LF'.

(linebreak-style)

</details>

</details>
<details>
<summary>🪛 GitHub Actions: CI</summary>

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

[error] 4-4: Test suite failed to run due to module resolution error: Could not locate module '~/src/utils/filterAllowedFields'. Check moduleNameMapper configuration.

</details>

</details>

</details>

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

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

`1-5`: **Well-structured validation module**

This validation module correctly defines the allowed fields for subject updates, which will be used to filter request payloads. This is a good security practice to prevent unwanted field updates.

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

</details>

</details>

---

`7-9`: **Cleanly exported configuration object**

The export is well-structured and follows the project's module pattern.

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

</details>

</details>

</details>
<details>
<summary>src/test/unit/services/subject.spec.js (4)</summary>

`1-6`: **Test setup looks good**

The test module correctly imports the necessary dependencies and mocks the Subject model to isolate the service layer for testing.

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

</details>
<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 4-4: Test suite failed to run due to module resolution error: Could not locate module '~/src/utils/filterAllowedFields'. Check moduleNameMapper configuration.

</details>

</details>

---

`7-9`: **Good testing practice with afterEach**

Using `afterEach` to clear mocks prevents test pollution between test cases.

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

</details>

</details>

---

`10-17`: **Well-structured success test case**

This test case properly verifies the successful path for retrieving a subject by ID, including checking that the model's findById method is called with the correct parameter.

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

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

[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)

</details>

</details>

---

`19-23`: **Good error handling test**

This test correctly verifies that a NOT_FOUND error is thrown when the subject doesn't exist.

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

---

[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>src/models/subject.js (2)</summary>

`1-4`: **Good model imports and constant usage**

The imports follow the project's patterns and use of constants for model names and error messages improves consistency.

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

</details>

</details>

---

`5-22`: **Well-structured subject schema**

The schema is well-designed with:
- Required fields with appropriate error messages
- Proper relationship to category through ObjectId reference
- A nested structure for tracking different types of offers
- Appropriate schema options for timestamps and version control

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

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

[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)

</details>

</details>

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

`1-49`: **LGTM: Well-structured controller with clean separation of concerns**

The controller implementation is clean and follows a good separation of concerns pattern where each function:
1. Extracts parameters from the request
2. Calls the appropriate service method
3. Responds with the correct status code and data

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

---

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

</details>
<details>
<summary>src/utils/docs/subject.yaml (1)</summary>

`94-117`: **Align OpenAPI spec with actual implementation**

The OpenAPI spec defines a PUT endpoint, but the router implementation uses PATCH. These should be consistent to avoid confusing API consumers.


Either change this to PATCH in the OpenAPI spec to match the router implementation, or update the router to use PUT if that's what you intended.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-07T10:49:48Z

Review comments:
diff_hunk: @@ -0,0 +1,24 @@
+const subjectService = require('~/services/subject')
+const SubjectSpec = require('~/models/subject')
+const { NOT_FOUND } = require('~/consts/errors')
+
+jest.mock('~/models/subject')
+
+describe('subjectService.getSubjectById', () => {
+  afterEach(() => jest.clearAllMocks())
+
+  it('should return subject if it is exist', async () => {
+    const mockSubject = { _id: '123', name: 'Math' }
+    SubjectSpec.findById.mockResolvedValue(mockSubject)
+
+    const result = await subjectService.getSubjectById('123')
+    expect(result).toEqual(mockSubject)
+    expect(SubjectSpec.findById).toHaveBeenCalledWith('123')
+  })
+
+  it('should return error if subject was not found', async () => {
+    SubjectSpec.findById.mockResolvedValue(null)
+
+    await expect(subjectService.getSubjectById('123')).rejects.toThrow(NOT_FOUND)
+  })
+})
path: src/test/unit/services/subject.spec.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix module import path in subject service**

The CI pipeline is failing due to an incorrect import path in the subject service file.

```shell
#!/bin/bash
# Check the import path in the subject service file
cat src/services/subject.js | grep -n "require('~/src/"
```

Based on the pipeline error, the subject service is trying to import from '~/src/validation/services/subject' but should be importing from '~/validation/services/subject'. The correct pattern appears to be using '~/' to represent the source root.

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

---

[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)

</details>
<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 4-4: Test suite failed to run due to module resolution error: Could not locate module '~/src/utils/filterAllowedFields'. Check moduleNameMapper configuration.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:46Z
author_association: NONE
diff_hunk: @@ -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 subject = require('~/models/subject')
path: src/routes/index.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix incorrect import path for subject routes**

You're importing the subject model instead of the subject router. All other route imports follow the pattern of importing from '~/routes/'.

```diff
-const subject = require('~/models/subject')
+const subject = require('~/routes/subject')
```


<!-- 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 subject = require('~/models/subject')
+ const subject = require('~/routes/subject')
`````

</details>

<!-- suggestion_end -->

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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:46Z
author_association: NONE
diff_hunk: @@ -16,6 +17,7 @@
 router.use('/questions', question)
 router.use('/resources-categories', resourcesCategory)
 router.use('/offers', offer)
+router.use('subject', subject)
path: src/routes/index.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing leading slash in subject route path**

All other route paths include a leading slash, but the subject route path does not. This inconsistency will cause problems with route matching.

```diff
-router.use('subject', subject)
+router.use('/subject', subject)
```


<!-- 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
router.use('/subject', subject)
`````

</details>

<!-- suggestion_end -->

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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,27 @@
+const router = require('express').Router()
+
+const idValidation = require('~/middlewares/idValidation')
+const asyncWrapper = require('~/middlewares/asyncWrapper')
+const isEntityValid = require('~/middlewares/entityValidation')
+
+const subjectController = require('~/controllers/subject')
+const Subject = require('~/models/subject')
+const Category = require('~/models/category')
+
+const body = [{ model: Category, idName: 'categoryId' }]
+
+const params = [{ model: Subject, idName: 'id' }]
+
+router.param('id', idValidation)
+
+router.get('/:id', isEntityValid({ params }), asyncWrapper(subjectController.getSubjectById))
+
+router.get('/', asyncWrapper(subjectController.getSubjects))
+
+router.post('/', isEntityValid({ body }), asyncWrapper(subjectController.createSubject))
+
+router.patch('/:id', isEntityValid({ params }), asyncWrapper(subjectController.updateOffer))
path: src/routes/subject.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix incorrect controller method reference**

The PATCH route is referencing `subjectController.updateOffer` instead of `subjectController.updateSubject`, which will cause this endpoint to fail.


```diff
-router.patch('/:id', isEntityValid({ params }), asyncWrapper(subjectController.updateOffer))
+router.patch('/:id', isEntityValid({ params }), asyncWrapper(subjectController.updateSubject))
```


<!-- 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
router.patch('/:id', isEntityValid({ params }), asyncWrapper(subjectController.updateSubject))
`````

</details>

<!-- suggestion_end -->

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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+tags:
+  - name: Subject
+    description: Endpoints for managing subjects
+
+paths:
+  /subjects:
+    get:
+      summary: Get a list of subjects
+      tags: [Subject]
+      parameters:
+        - name: limit
+          in: query
+          schema:
+            type: integer
+            minimum: 1
+          description: Number of items to return
+        - name: offset
+          in: query
+          schema:
+            type: integer
+            minimum: 0
+          description: Number of items to skip
+        - name: search
+          in: query
+          schema:
+            type: string
+          description: Search term to filter subjects by name
+      responses:
+        '200':
+          description: A list of subjects
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    type: object
+                    properties:
+                      countries:
+                        type: array
+                        items:
+                          $ref: '#/components/schemas/Subject'
path: src/utils/docs/subject.yaml
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix property name in OpenAPI schema**

The response schema property incorrectly uses "countries" instead of "subjects", which appears to be a copy-paste error.


```diff
-                      countries:
+                      subjects:
                         type: array
                         items:
                           $ref: '#/components/schemas/Subject'
```


<!-- 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
                      subjects:
                        type: array
                        items:
                          $ref: '#/components/schemas/Subject'
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,77 @@
+const Subject = require('~/models/subject')
+const { createError } = require('~/utils/errorsHelper')
+const { NOT_FOUND } = require('~/consts/errors')
+const filterAllowedFields = require('~/src/utils/filterAllowedFields')
+const { allowedSubjectFieldsForUpdate } = require('~/src/validation/services/subject')
path: src/services/subject.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Fix inconsistent import paths**

The import paths here include redundant `src/` segment, unlike other imports that use the `~/` alias correctly.


```diff
-const filterAllowedFields = require('~/src/utils/filterAllowedFields')
-const { allowedSubjectFieldsForUpdate } = require('~/src/validation/services/subject')
+const filterAllowedFields = require('~/utils/filterAllowedFields')
+const { allowedSubjectFieldsForUpdate } = require('~/validation/services/subject')
```


<!-- 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 filterAllowedFields = require('~/utils/filterAllowedFields')
const { allowedSubjectFieldsForUpdate } = require('~/validation/services/subject')
`````

</details>

<!-- suggestion_end -->

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

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

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

(linebreak-style)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,77 @@
+const Subject = require('~/models/subject')
+const { createError } = require('~/utils/errorsHelper')
+const { NOT_FOUND } = require('~/consts/errors')
+const filterAllowedFields = require('~/src/utils/filterAllowedFields')
+const { allowedSubjectFieldsForUpdate } = require('~/src/validation/services/subject')
+const { paginate } = require('~/utils/paginate')
+const { filterBySearch } = require('~/utils/filterBySearch')
+
+const subjectService = {
+  getSubjectById: async (id) => {
+    const subject = await Subject.findById(id)
+
+    if (!subject) {
+      throw createError(404, NOT_FOUND)
+    }
+
+    return subject
+  },
+
+  getSubjects: async (limit, offset, search) => {
+    const limitNumber = parseInt(limit)
+    const offsetNumber = parseInt(offset)
+
+    const paginationLimit = !isNaN(limitNumber) && limitNumber > 0 ? limitNumber : 10
+    const paginationOffset = !isNaN(offsetNumber) && offsetNumber >= 0 ? offsetNumber : 0
+
+    const subjects = await Subject.find().lean()
+    const filteredSubjects = filterBySearch(subjects, search)
+
+    return {
+      data: {
+        subjects: paginate(filteredSubjects, paginationLimit, paginationOffset),
+        total: subjects.length,
path: src/services/subject.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use filtered results count for total**

The total count should be based on the filtered results, not the original subjects array.


```diff
-        total: subjects.length,
+        total: filteredSubjects.length,
```

This ensures the pagination metadata correctly reflects the number of items that match the search criteria.


<!-- 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
        total: filteredSubjects.length,
`````

</details>

<!-- suggestion_end -->

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

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

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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-07T10:49:47Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-07T10:44:23Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change introduces full CRUD support for "subject" entities in the application. It adds a Mongoose model, service layer, controller, Express router, OpenAPI documentation, and validation for subjects. Unit tests for the service layer are included. The main routes and logic for managing subjects are now fully implemented and documented.

## Changes

| File(s)                                                         | Change Summary |
|-----------------------------------------------------------------|---------------|
| src/controllers/subject.js                                      | New controller module for "subject" entities, exporting async CRUD functions (get, list, create, update, delete), each handling HTTP requests and responses. |
| src/models/subject.js                                           | New Mongoose schema/model for "Subject" with fields: name, category (ref to Category), and totalOffers (student, tutor). Exports the compiled model. |
| src/routes/subject.js                                           | New Express router for subject endpoints. Sets up validation, middleware, and routes for CRUD operations, connecting to controller functions. |
| src/services/subject.js                                         | New service module implementing async CRUD and list operations for Subject, with error handling, filtering, and pagination. Exports service object. |
| src/validation/services/subject.js                              | New module exporting allowed fields for subject updates as a whitelist object. |
| src/utils/docs/subject.yaml                                     | New OpenAPI spec file documenting the Subject entity's REST API endpoints, parameters, responses, and schemas. |
| src/test/unit/services/subject.spec.js                          | New unit test file for subjectService.getSubjectById, testing found and not-found cases using mocks. |
| src/routes/index.js                                             | Changes import and router registration for subject; mistakenly uses model instead of route handler and omits leading slash in route path. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant Router
    participant Controller
    participant Service
    participant Model

    Client->>Router: HTTP request (GET/POST/PATCH/DELETE /subjects or /subjects/:id)
    Router->>Controller: Calls controller function
    Controller->>Service: Calls service method (get/create/update/delete)
    Service->>Model: Interacts with Mongoose Subject model
    Model-->>Service: Returns DB result or error
    Service-->>Controller: Returns processed data or error
    Controller-->>Router: Sends HTTP response
    Router-->>Client: Response (JSON data, status code)
```

## Suggested reviewers

- Sergij95
- olena-kuziaieva

## Poem

> In the warren of code, a subject appears,  
> With CRUD for its burrow, and tests for its peers.  
> Models and routes, all neatly aligned,  
> A hop, skip, and jump—API well-defined!  
> 🐇✨  
> Now rabbits can query, update, and cheer,  
> For subject support is finally here!

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


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

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

<details open="true">
<summary>✨ Finishing Touches</summary>

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

</details>

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

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<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-02/BackEnd-02&utm_content=21):

- 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.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) 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](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-05-07T10:44:43Z
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-02_BackEnd-02&pullRequest=21) **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-02_BackEnd-02&pullRequest=21&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-02_BackEnd-02&pullRequest=21&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-02_BackEnd-02&pullRequest=21&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-02_BackEnd-02&pullRequest=21&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-02_BackEnd-02&pullRequest=21&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Space2Study-UA-1355-02_BackEnd-02&pullRequest=21)


author_association: NONE