diff --git a/docs/cooperation/cooperation-schema.yaml b/docs/cooperation/cooperation-schema.yaml index 6c8fc989..20ec1f4b 100644 --- a/docs/cooperation/cooperation-schema.yaml +++ b/docs/cooperation/cooperation-schema.yaml @@ -6,17 +6,52 @@ definitions: properties: _id: type: string + proficiencyLevel: + type: array + items: + type: string + subject: + type: object + properties: + name: + type: string + category: + type: object + properties: + appearance: + type: object + properties: + color: + type: string + icon: + type: string + user: + type: object + properties: + _id: + type: string + firstName: + type: string + lastName: + type: string + photo: + type: string + role: + type: string + description: + type: string + languages: + type: array + items: + type: string offer: type: string - ref: '#components/offer' initiator: type: string - ref: '#/components/user' initiatorRole: type: string receiver: type: string - ref: '#components/user' receiverRole: type: string price: @@ -185,28 +220,11 @@ definitions: properties: _id: type: string - proficiencyLevel: - type: array - items: - type: string - title: - type: string - description: - type: string - languages: - type: array - items: - type: string author: type: object properties: - totalReviews: - type: object - properties: - student: - type: integer - tutor: - type: integer + _id: + type: string averageRating: type: object properties: @@ -214,8 +232,6 @@ definitions: type: number tutor: type: number - _id: - type: string firstName: type: string lastName: @@ -224,246 +240,57 @@ definitions: type: string professionalSummary: type: string - subject: - type: object - properties: - _id: - type: string - name: - type: string - category: - type: object - properties: - appearance: + totalReviews: type: object properties: - color: - type: string - icon: - type: string - _id: - type: string - name: - type: string + student: + type: number + tutor: + type: number initiator: + type: string + initiatorRole: + type: string + proficiencyLevel: + type: array + items: + type: string + subject: type: object properties: - address: - type: object - properties: - country: - type: string - city: - type: string - mainSubjects: - type: object - properties: - student: - type: array - items: - type: string - tutor: - type: array - items: - type: string - totalReviews: - type: object - properties: - student: - type: integer - tutor: - type: integer - averageRating: - type: object - properties: - student: - type: number - tutor: - type: number - status: - type: object - properties: - student: - type: string - tutor: - type: string - admin: - type: string - videoLink: - type: object - properties: - student: - type: string - professionalBlock: - type: object - properties: - awards: - type: string - scientificActivities: - type: string - workExperience: - type: string - education: - type: string - notificationSettings: - type: object - properties: - isOfferStatusNotification: - type: boolean - isChatNotification: - type: boolean - isSimilarOffersNotification: - type: boolean - isEmailNotification: - type: boolean - _id: - type: string - role: - type: array - items: - type: string - firstName: - type: string - lastName: - type: string - email: - type: string - nativeLanguage: - type: string - lastLogin: - type: string - createdAt: - type: string - updatedAt: - type: string - professionalSummary: + name: type: string - initiatorRole: - type: string - receiver: + category: type: object properties: - address: + appearance: type: object properties: - country: + color: type: string - city: + icon: type: string - mainSubjects: - type: object - properties: - student: - type: array - items: - type: string - tutor: - type: array - items: - type: object - properties: - category: - type: string - subjects: - type: array - items: - type: string - _id: - type: string - totalReviews: - type: object - properties: - student: - type: integer - tutor: - type: integer - averageRating: - type: object - properties: - student: - type: number - tutor: - type: number - status: - type: object - properties: - student: - type: string - tutor: - type: string - admin: - type: string - videoLink: - type: object - properties: - tutor: - type: string - professionalBlock: - type: object - properties: - awards: - type: string - scientificActivities: - type: string - workExperience: - type: string - education: - type: string - notificationSettings: - type: object - properties: - isOfferStatusNotification: - type: boolean - isChatNotification: - type: boolean - isSimilarOffersNotification: - type: boolean - isEmailNotification: - type: boolean - _id: - type: string - role: - type: array - items: - type: string - firstName: - type: string - lastName: - type: string - email: - type: string - nativeLanguage: - type: string - lastLogin: - type: string - createdAt: - type: string - updatedAt: - type: string - photo: - type: string - professionalSummary: - type: string - receiverRole: + description: type: string - title: + languages: + type: array + items: + type: string + receiver: type: string - proficiencyLevel: + receiverRole: type: string price: - type: integer - status: + type: number + title: type: string - needAction: + status: type: string - availableQuizzes: - type: array - items: - type: string - finishedQuizzes: - type: array - items: - type: string + enum: + - pending + - active + - declined + - closed sections: type: array items: @@ -501,7 +328,14 @@ definitions: type: string format: date-time default: null + needAction: + type: string + enum: + - student + - tutor createdAt: type: string + format: date-time updatedAt: type: string + format: date-time diff --git a/docs/cooperation/cooperation.yaml b/docs/cooperation/cooperation.yaml index f68472de..e8fe656d 100644 --- a/docs/cooperation/cooperation.yaml +++ b/docs/cooperation/cooperation.yaml @@ -24,7 +24,19 @@ paths: receiver: 6255bc080a75adf9223df100 receiverRole: student title: 'Violin lessons' - proficiencyLevel: Intermediate + proficiencyLevel: ['Intermediate'] + subject: { name: 'Music' } + category: { appearance: { color: '#E3B21C', icon: 'ScienceRoundedIcon' } } + user: + { + _id: '66b0aecdadd1fe775238c7d5', + firstName: 'Pavlo', + lastName: 'Dolia', + photo: '1726302778023-pexels-olly-3769706.jpg', + role: 'student' + } + description: "I'll teach you how to play violin" + languages: ['English'] price: 200 status: active needAction: student @@ -40,7 +52,19 @@ paths: receiver: 6255bc080a75adf9223df100 receiverRole: student title: 'Violin lessons' - proficiencyLevel: Intermediate + proficiencyLevel: ['Intermediate'] + subject: { name: 'Music' } + category: { appearance: { color: '#E3B21C', icon: 'ScienceRoundedIcon' } } + user: + { + _id: '66b0aecdadd1fe775238c7d5', + firstName: 'Pavlo', + lastName: 'Dolia', + photo: '1726302778023-pexels-olly-3769706.jpg', + role: 'student' + } + description: "I'll teach you how to play violin" + languages: ['English'] price: 300 status: closed needAction: tutor @@ -77,9 +101,18 @@ paths: $ref: '#/definitions/cooperation' example: offer: 63ebc6fbd2f34037d0aba791 + initiator: 6255bc080a75adf9223df212 + initiatorRole: tutor receiver: 6255bc080a75adf9223df100 receiverRole: student + title: 'Violin lessons' + proficiencyLevel: ['Intermediate'] price: 300 + needAction: tutor + subject: 6255bc080a75adf9223df103 + category: 6255bc080a75adf9223df104 + description: "I'll teach you how to play violin" + languages: ['English'] responses: 201: description: Created @@ -89,13 +122,21 @@ paths: $ref: '#/definitions/cooperation' example: _id: 8755bc080a00adr9243df104 - offer: 63ebc6fbd2f34037d0aba791 + offer: + { + _id: '66ec53d40d9d9983a9525421', + price: 400, + title: 'Violin lessons', + subject: { _id: '656605be8bebd72f0be56a3d', name: 'Violin' }, + category: + { appearance: { icon: 'StarRoundedIcon', color: '#607D8B' }, _id: '6566040f8bebd72f0be55a28' } + } initiator: 6255bc080a75adf9223df212 initiatorRole: tutor receiver: 6255bc080a75adf9223df100 receiverRole: student title: 'Violin lessons' - proficiencyLevel: Intermediate + proficiencyLevel: ['Intermediate'] price: 300 status: pending needAction: student @@ -104,6 +145,18 @@ paths: sections: [] createdAt: 2021-04-09T11:34:53.243+00:00 updatedAt: 2022-09-02T11:59:53.243+00:00 + category: '6566040f8bebd72f0be55a28' + description: "I'll teach you how to play violin" + languages: ['English'] + subject: { _id: '656605be8bebd72f0be56a3d', name: 'Violin' } + user: + { + _id: '66b0aecdadd1fe775238c7d5', + firstName: 'John', + lastName: 'Doe', + photo: '1726302778023-pexels-olly-3769706.jpg', + role: 'student' + } 400: description: Bad Request content: @@ -150,38 +203,24 @@ paths: $ref: '#/definitions/cooperationResponse' example: _id: 8755bc080a00adr9243df106 + description: "I'll teach you how to play violin" + languages: ['English'] + subject: { name: 'Music' } + category: { appearance: { color: '#E3B21C', icon: 'ScienceRoundedIcon' } } offer: - _id: 63ebc6fbd2f34037d0aba791 - proficiencyLevel: - - Intermediate - - Advanced - - Test Preparation - - Professional - title: 'Understanding Quadratic Equations' - description: "In this lesson, we will delve into the world of quadratic equations. We'll explore the standard form of a quadratic equation, learn how to identify the coefficients, and understand the significance of the discriminant. Students will also practice solving quadratic equations using various methods, including factoring, completing the square, and the quadratic formula. By the end of the lesson, students will be able to solve quadratic equations confidently and understand their graphical representations." - languages: - - English - author: - totalReviews: - student: 0 - tutor: 0 - averageRating: - student: 0 - tutor: 0 - _id: 6658f73f93885febb491e08b - firstName: 'John' - lastName: 'Doe' - photo: '6658f73f93885-test_student.png' - professionalSummary: 'I am student of the University of Cambridge. I am passionate about learning mathematics.' - subject: - _id: 6566133a2bccdd3e18dbe943 - name: 'Algebra' - category: - appearance: - color: '#E3B21C' - icon: 'TagRoundedIcon' - _id: '64884f33fdc2d1a130c24ac2' - name: 'Mathematics' + { + _id: 63ebc6fbd2f34037d0aba791, + author: + { + _id: '66a7abbab3168fa64a8f5af1', + averageRating: { student: 0, tutor: 0 }, + firstName: 'John', + lastName: 'Doe', + photo: '1726302583778-pexels-vanessa-garcia-6326377.jpg', + professionalSummary: 'I have 12 years of experience', + totalReviews: { student: 0, tutor: 0 } + } + } initiator: address: country: 'USA' @@ -279,7 +318,7 @@ paths: professionalSummary: 'Recently graduated from the University of Cambridge with a degree in Mathematics. I have been tutoring students in Mathematics for over 5 years and have helped many students achieve their academic goals. I am passionate about teaching and enjoy helping students understand complex mathematical concepts. I believe that every student has the potential to succeed and I am committed to helping them reach their full potential.' receiverRole: 'tutor' title: 'Understanding Quadratic Equations' - proficiencyLevel: 'Intermediate' + proficiencyLevel: ['Intermediate'] price: 150 status: 'active' needAction: 'tutor' diff --git a/docs/coursesCooperations/coursesCooperations-schema.yaml b/docs/coursesCooperations/coursesCooperations-schema.yaml index 959b54ee..171b226b 100644 --- a/docs/coursesCooperations/coursesCooperations-schema.yaml +++ b/docs/coursesCooperations/coursesCooperations-schema.yaml @@ -100,7 +100,19 @@ definitions: price: type: number proficiencyLevel: + type: array + items: + type: string + subject: + type: string + category: type: string + description: + type: string + languages: + type: array + items: + type: string receiver: type: string receiverRole: diff --git a/docs/coursesCooperations/coursesCooperations.yaml b/docs/coursesCooperations/coursesCooperations.yaml index ea8885f1..fa1a0f12 100644 --- a/docs/coursesCooperations/coursesCooperations.yaml +++ b/docs/coursesCooperations/coursesCooperations.yaml @@ -28,7 +28,11 @@ paths: needAction: 'tutor', offer: '66ec53d40d9d9983a9525421', price: 500, - proficiencyLevel: 'Beginner', + proficiencyLevel: ['Beginner'], + description: "I'll teach you how to follow healthy diet", + languages: ['English'], + subject: '656605be8bebd72f0be55a3d', + category: '6566040f8bebd72f0be55a28', receiver: '66a7abbab3168fa64a8f5af1', receiverRole: 'tutor', sections: diff --git a/jest.config.js b/jest.config.js index e4d74660..dc7157fe 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,8 @@ module.exports = { roots: ['/src/test'], moduleNameMapper: { - '^~/(.*)$': '/src/$1' + '^~/(.*)$': '/src/$1', + '@root/(.*)': '/$1' }, verbose: true, testEnvironment: 'node', @@ -27,6 +28,10 @@ module.exports = { coverageReporters: ['html', 'lcov'], coverageDirectory: '/src/test/coverage', testTimeout: 12000, - testMatch: ['/src/test/integration/**/*.spec.js', '/src/test/unit/**/*.spec.js'], + testMatch: [ + '/src/test/integration/**/*.spec.js', + '/src/test/unit/**/*.spec.js', + '/src/test/migrations/*.spec.js' + ], testResultsProcessor: 'jest-sonar-reporter' } diff --git a/migrate-mongo-config.js b/migrate-mongo-config.js new file mode 100644 index 00000000..f400aba6 --- /dev/null +++ b/migrate-mongo-config.js @@ -0,0 +1,18 @@ +const config = { + mongodb: { + url: 'mongodb://127.0.0.1:27017/s2s', + + options: { + useNewUrlParser: true, + useUnifiedTopology: true + } + }, + + migrationsDir: 'migrations', + changelogCollectionName: 'changelog', + migrationFileExtension: '.js', + useFileHash: false, + moduleSystem: 'commonjs' +} + +module.exports = config diff --git a/migrations/20241106160527-add-fields-to-cooperations.js b/migrations/20241106160527-add-fields-to-cooperations.js new file mode 100644 index 00000000..c738d02e --- /dev/null +++ b/migrations/20241106160527-add-fields-to-cooperations.js @@ -0,0 +1,48 @@ +module.exports = { + async up(db) { + await db + .collection('cooperation') + .aggregate([ + { + $lookup: { + from: 'offers', + localField: 'offer', + foreignField: '_id', + as: '_tmp_offers_array' + } + }, + { $set: { _tmp_offer: { $first: '$_tmp_offers_array' } } }, + { + $set: { + subject: '$_tmp_offer.subject', + category: '$_tmp_offer.category', + description: '$_tmp_offer.description', + languages: '$_tmp_offer.languages', + proficiencyLevel: '$_tmp_offer.proficiencyLevel' + } + }, + { + $project: { + subject: 1, + category: 1, + description: 1, + languages: 1, + proficiencyLevel: 1 + } + }, + { $merge: { into: 'cooperation', on: '_id' } } + ]) + .toArray() + }, + + async down(db) { + await db.collection('cooperation').updateMany({}, [ + { + $unset: ['subject', 'category', 'description', 'languages'] + }, + { + $set: { proficiencyLevel: { $first: '$proficiencyLevel' } } + } + ]) + } +} diff --git a/package-lock.json b/package-lock.json index 183d8c9c..13887253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "azure-storage": "^2.10.7", "bcrypt": "^5.0.1", + "cookie": "^0.6.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "cron": "^2.1.0", @@ -26,6 +27,7 @@ "multer": "^1.4.5-lts.1", "nodemailer": "^6.7.7", "pug": "^3.0.2", + "socket.io": "^4.7.5", "swagger-jsdoc": "^6.1.0", "swagger-ui-express": "^4.3.0", "winston": "^3.8.0", @@ -38,6 +40,7 @@ "jest": "^28.1.3", "jest-sonar-reporter": "^2.0.0", "lint-staged": "^13.0.2", + "migrate-mongo": "^11.0.0", "nodemon": "^2.0.15", "prettier": "2.5.1", "supertest": "^6.2.4" @@ -1370,6 +1373,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", @@ -2750,6 +2765,11 @@ "node": ">=14.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2791,6 +2811,19 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3320,6 +3353,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -3792,6 +3833,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-truncate": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", @@ -4053,9 +4148,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -4072,6 +4167,14 @@ "node": ">= 0.8.0" } }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -4160,6 +4263,22 @@ "node": ">=0.10" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4490,6 +4609,42 @@ "node": ">=8.10.0" } }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -5093,6 +5248,15 @@ "integrity": "sha512-noqGuLw158+DuD9UPRKHpJ2hGxpFyDlYYrfM0mWt4XhT4n0lwzTLh70Tkdyy4kyTmyTT9Bv7bWAJqw7cgkEXDg==", "dev": true }, + "node_modules/fn-args": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", + "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -5150,6 +5314,20 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -6864,6 +7042,18 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsonparse": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", @@ -7721,6 +7911,39 @@ "node": ">=8.6" } }, + "node_modules/migrate-mongo": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-11.0.0.tgz", + "integrity": "sha512-GB/gHzUwp/fL1w6ksNGihTyb+cSrm6NbVLlz1OSkQKaLlzAXMwH7iKK2ZS7W5v+I8vXiY2rL58WTUZSAL6QR+A==", + "dev": true, + "dependencies": { + "cli-table3": "^0.6.1", + "commander": "^9.1.0", + "date-fns": "^2.28.0", + "fn-args": "^5.0.0", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", + "p-each-series": "^2.2.0" + }, + "bin": { + "migrate-mongo": "bin/migrate-mongo.js" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "mongodb": "^4.4.1 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/migrate-mongo/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -8305,6 +8528,18 @@ "node": ">= 0.8.0" } }, + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", @@ -9007,6 +9242,12 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -9613,6 +9854,44 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socks": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.3.tgz", @@ -10330,6 +10609,15 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -10815,6 +11103,26 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", diff --git a/package.json b/package.json index 588a4e36..dcd054a8 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "jest": "^28.1.3", "jest-sonar-reporter": "^2.0.0", "lint-staged": "^13.0.2", + "migrate-mongo": "^11.0.0", "nodemon": "^2.0.15", "prettier": "2.5.1", "supertest": "^6.2.4" diff --git a/sonar-project.properties b/sonar-project.properties index 6b55c105..e82b52e1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,7 +8,7 @@ sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=. -sonar.exclusions=**/src/test/**/*.js, node_modules/**, jest.config.js, swagger-settings.js, docs/**, src/emails/** +sonar.exclusions=**/src/test/**/*.js, node_modules/**, jest.config.js, swagger-settings.js, docs/**, src/emails/**, migrate-mongo-config.js sonar.language=js diff --git a/src/models/cooperation.js b/src/models/cooperation.js index f6834f10..c1e5a86a 100644 --- a/src/models/cooperation.js +++ b/src/models/cooperation.js @@ -7,14 +7,15 @@ const { FIELD_MUST_BE_SELECTED, VALUE_MUST_BE_ABOVE } = require('~/consts/errors') -const { USER, OFFER, COOPERATION, FINISHED_QUIZ, QUIZ } = require('~/consts/models') +const { USER, OFFER, COOPERATION, FINISHED_QUIZ, QUIZ, SUBJECT, CATEGORY } = require('~/consts/models') const { enums: { COOPERATION_STATUS_ENUM, PROFICIENCY_LEVEL_ENUM, MAIN_ROLE_ENUM, RESOURCES_TYPES_ENUM, - RESOURCE_AVAILABILITY_STATUS_ENUM + RESOURCE_AVAILABILITY_STATUS_ENUM, + SPOKEN_LANG_ENUM } } = require('~/consts/validation') const { REQUESTED, UPDATED } = require('~/consts/notificationTypes') @@ -66,7 +67,7 @@ const cooperationSchema = new Schema( maxLength: [1000, FIELD_CANNOT_BE_LONGER('additional info', 1000)] }, proficiencyLevel: { - type: String, + type: [String], enum: { values: PROFICIENCY_LEVEL_ENUM, message: ENUM_CAN_BE_ONE_OF('proficiency level', PROFICIENCY_LEVEL_ENUM) @@ -102,6 +103,30 @@ const cooperationSchema = new Schema( type: [Schema.Types.ObjectId], ref: FINISHED_QUIZ }, + subject: { + type: Schema.Types.ObjectId, + ref: SUBJECT, + required: true + }, + category: { + type: Schema.Types.ObjectId, + ref: CATEGORY, + required: true + }, + description: { + type: String, + minLength: [1, FIELD_CANNOT_BE_SHORTER('description', 1)], + maxLength: [1000, FIELD_CANNOT_BE_LONGER('description', 1000)], + required: [true, FIELD_CANNOT_BE_EMPTY('description')] + }, + languages: { + type: [String], + enum: { + values: SPOKEN_LANG_ENUM, + message: ENUM_CAN_BE_ONE_OF('language', SPOKEN_LANG_ENUM) + }, + required: [true, FIELD_MUST_BE_SELECTED('language(s)')] + }, sections: [ { title: { diff --git a/src/services/cooperation.js b/src/services/cooperation.js index 26219301..f1bc98d9 100644 --- a/src/services/cooperation.js +++ b/src/services/cooperation.js @@ -16,23 +16,23 @@ const cooperationService = { await Cooperation.findById(id) ).populate([ { path: 'sections.resources.resource', select: '-createdAt -updatedAt' }, + { + path: 'category', + select: ['name', 'appearance'] + }, + { + path: 'subject', + select: 'name' + }, { path: 'offer', populate: [ - { - path: 'category', - select: ['name', 'appearance'] - }, - { - path: 'subject', - select: 'name' - }, { path: 'author', select: ['firstName', 'lastName', 'photo', 'professionalSummary', 'totalReviews', 'FAQ', 'averageRating'] } ], - select: ['id', 'author', 'category', 'subject', 'title', 'languages', 'proficiencyLevel', 'description'] + select: ['id', 'author'] }, { path: 'initiator' @@ -44,7 +44,20 @@ const cooperationService = { }, createCooperation: async (initiator, initiatorRole, data) => { - const { offer, proficiencyLevel, additionalInfo, receiver, receiverRole, price, title, sections } = data + const { + offer, + proficiencyLevel, + additionalInfo, + receiver, + receiverRole, + price, + title, + sections, + subject, + category, + description, + languages + } = data return await Cooperation.create({ initiator, @@ -57,6 +70,10 @@ const cooperationService = { price, proficiencyLevel, additionalInfo, + subject, + category, + description, + languages, needAction: receiverRole }) }, diff --git a/src/test/integration/controllers/cooperation.spec.js b/src/test/integration/controllers/cooperation.spec.js index 6c27263b..efa18596 100644 --- a/src/test/integration/controllers/cooperation.spec.js +++ b/src/test/integration/controllers/cooperation.spec.js @@ -10,6 +10,7 @@ const { serverCleanup, serverInit, stopServer } = require('~/test/setup') const { expectError } = require('~/test/helpers') const testUserAuthentication = require('~/utils/testUserAuth') const TokenService = require('~/services/token') +const { testCooperationData } = require('~/test/test-constants') const { DOCUMENT_NOT_FOUND, UNAUTHORIZED, VALIDATION_ERROR, FORBIDDEN } = require('~/consts/errors') const { @@ -54,34 +55,6 @@ const anotherUserData = { lastLogin: new Date().toJSON() } -const testCooperationData = { - price: 99, - receiverRole: 'tutor', - proficiencyLevel: 'Beginner', - title: 'First-class teacher. Director of the Hogwarts school of magic', - sections: [ - { - title: 'Solving Quadratic Equations Using the Quadratic Formula', - description: 'Solving Quadratic Equations Using the Quadratic Formula', - resources: [ - { - resource: { - _id: '6684179479e5232bce4579fa', - author: '6658f73f93885febb491e08b', - content: '

Solving Quadratic Equations Using the Quadratic Formula

', - description: 'The quadratic formula', - title: 'Solving Quadratic Equations Using the Quadratic Formula', - category: '6684175179e5232bce4579ed', - resourceType: RESOURCES_TYPES_ENUM[0] - }, - resourceType: RESOURCES_TYPES_ENUM[0], - availability: { status: 'open', date: null } - } - ] - } - ] -} - const testOfferData = { authorRole: 'tutor', price: 99, @@ -216,6 +189,8 @@ describe('Cooperation controller', () => { const updatedTestCooperationData = { ...testCooperationData, + subject: subject._id, + category: category._id, sections: [ { ...testCooperationData.sections[0], @@ -268,9 +243,7 @@ describe('Cooperation controller', () => { expect(Array.isArray(response.body.items)).toBe(true) expect(response.body.items[0]).toMatchObject({ _id: testCooperation._body._id, - offer: { - _id: testOffer._id - }, + offer: testOffer._id, initiator: testStudentUser.id, receiver: testTutorUser._id, proficiencyLevel: testCooperationData.proficiencyLevel, diff --git a/src/test/integration/controllers/coursesCooperations.spec.js b/src/test/integration/controllers/coursesCooperations.spec.js index 059e256e..839a38bf 100644 --- a/src/test/integration/controllers/coursesCooperations.spec.js +++ b/src/test/integration/controllers/coursesCooperations.spec.js @@ -1,5 +1,6 @@ const { serverCleanup, serverInit, stopServer } = require('~/test/setup') const { createUser, getCategory } = require('~/test/test-utils') +const { testCooperationData } = require('~/test/test-constants') const subjectService = require('~/services/subject') const Cooperation = require('~/models/cooperation') const { @@ -62,25 +63,14 @@ const testOffer = { } } -const testCooperation = { - offer: 'offerId', - initiatorRole: 'student', - receiver: 'tutorId', - receiverRole: 'tutor', - title: 'Cooperation title', - proficiencyLevel: 'Beginner', - price: 500, - status: 'active', - availableQuizzes: [], - finishedQuizzes: [], - sections: [ - { - title: 'Section 1', - description: 'description', - resources: [] - } - ] -} +const testCooperation = { ...testCooperationData } +testCooperation.sections = [ + { + title: 'Section 1', + description: 'description', + resources: [] + } +] describe('User controller', () => { let app, server, accessToken, userId, testLessonResponse, testLessonId, category, categoryId, testSubject, offerId @@ -122,6 +112,8 @@ describe('User controller', () => { testCooperation.receiver = userId testCooperation.offer = offerId + testCooperation.subject = testSubject._id + testCooperation.category = categoryId }) afterEach(async () => { diff --git a/src/test/integration/controllers/note.spec.js b/src/test/integration/controllers/note.spec.js index 8038430a..e19c3dc6 100644 --- a/src/test/integration/controllers/note.spec.js +++ b/src/test/integration/controllers/note.spec.js @@ -3,9 +3,13 @@ const { expectError } = require('~/test/helpers') const { UNAUTHORIZED, FORBIDDEN, DOCUMENT_NOT_FOUND } = require('~/consts/errors') const testUserAuthentication = require('~/utils/testUserAuth') const TokenService = require('~/services/token') +const subjectService = require('~/services/subject') +const { getCategory } = require('~/test/test-utils') +const { testCooperationData } = require('~/test/test-constants') const Cooperation = require('~/models/cooperation') const Note = require('~/models/note') +const User = require('~/models/user') const endpointUrl = (id = ':id', noteId = '') => `/cooperations/${id}/notes/${noteId}` @@ -13,19 +17,6 @@ const nonExistingCooperationId = '19cf23e07281224fbbee3241' const mockedInitiatorId = '649c1fc9c75d3e44440e3a15' -const testCooperationData = { - price: 99, - receiverRole: 'tutor', - proficiencyLevel: 'Beginner', - additionalInfo: - "I don't like both Dark Arts and Voldemort that's why i want to learn your subject and became your student", - receiver: '649c147ac75d3e44440e3a12', - offer: '649c148cc75d3e44440e3a13', - title: 'First class teacher. Director of the Hogwarts school witchcraft and wizardry.', - initiatorRole: 'student', - needAction: 'tutor' -} - const testNoteData = { text: 'my comment', isPrivate: false @@ -36,18 +27,67 @@ const updateNoteData = { isPrivate: true } +const subjectBody = { name: 'English' } + +const testOffer = { + price: 330, + proficiencyLevel: ['Beginner'], + title: 'Test Title', + author: '', + authorRole: 'tutor', + FAQ: [{ question: 'question1', answer: 'answer1' }], + description: 'description', + languages: ['Ukrainian'], + enrolledUsers: [], + subject: '', + category: { + _id: '', + appearance: { icon: 'mocked-path-to-icon', color: '#66C42C' } + } +} + +const tutorUserData = { + role: ['tutor'], + firstName: 'albus', + lastName: 'dumbledore', + email: 'lovemagic@gmail.com', + password: 'supermagicpass123', + appLanguage: 'en', + isEmailConfirmed: true, + lastLogin: new Date().toJSON() +} + describe('Note controller', () => { - let app, server, accessToken, testUser, testCooperation, testNote + let app, server, accessToken, testUser, testCooperation, testNote, category, testSubject beforeAll(async () => { - ; ({ app, server } = await serverInit()) + ;({ app, server } = await serverInit()) }) beforeEach(async () => { accessToken = await testUserAuthentication(app) + const testTutorUser = await User.create(tutorUserData) testUser = TokenService.validateAccessToken(accessToken) + category = await getCategory() + + subjectBody.category = category._id + testSubject = await subjectService.addSubject(subjectBody) + + testOffer.category = category._id + testOffer.subject = testSubject._id + const testOfferResponse = await app + .post('/offers/') + .set('Cookie', [`accessToken=${accessToken}`]) + .send(testOffer) + const offerId = testOfferResponse.body._id + + testCooperationData.category = category._id + testCooperationData.subject = testSubject._id + testCooperationData.offer = offerId + testCooperationData.receiver = testTutorUser._id + testCooperation = await Cooperation.create({ initiator: testUser.id, ...testCooperationData diff --git a/src/test/migrations/20241106160527-add-fields-to-cooperations.spec.js b/src/test/migrations/20241106160527-add-fields-to-cooperations.spec.js new file mode 100644 index 00000000..531b8a67 --- /dev/null +++ b/src/test/migrations/20241106160527-add-fields-to-cooperations.spec.js @@ -0,0 +1,89 @@ +const { MongoClient } = require('mongodb') + +const { up, down } = require('@root/migrations/20241106160527-add-fields-to-cooperations.js') + +require('~/initialization/envSetup') +const { + config: { MONGODB_URL } +} = require('~/configs/config') +const { + testCategoryData, + testSubjectData, + collectionNames: { SUBJECTS, CATEGORIES, OFFERS, COOPERATIONS } +} = require('~/test/test-constants') + +const url = MONGODB_URL.slice(0, MONGODB_URL.lastIndexOf('/')) +const databaseName = MONGODB_URL.slice(MONGODB_URL.lastIndexOf('/') + 1) + +const testOfferData = { + subject: 'subjectId', + category: 'categoryId', + description: 'Offer description', + languages: ['English'], + proficiencyLevel: ['Beginner'] +} + +const testCooperationData = { + title: 'Cooperation title', + proficiencyLevel: 'Beginner' +} + +describe('20241106160527-add-fields-to-cooperations:', () => { + let client, database, testSubjectId, testCategoryId, testCooperationId + + beforeAll(() => { + client = new MongoClient(url) + database = client.db(databaseName) + }) + + beforeEach(async () => { + const testCategory = await database.collection(CATEGORIES).insertOne(testCategoryData) + testCategoryId = testCategory.insertedId + + const testSubjectDataCopy = { ...testSubjectData, category: testCategory.insertedId } + const testSubject = await database.collection(SUBJECTS).insertOne(testSubjectDataCopy) + testSubjectId = testSubject.insertedId + + testOfferData.subject = testSubject.insertedId + testOfferData.category = testCategory.insertedId + const testOffer = await database.collection(OFFERS).insertOne(testOfferData) + + const testCooperation = await database + .collection(COOPERATIONS) + .insertOne({ ...testCooperationData, offer: testOffer.insertedId }) + testCooperationId = testCooperation.insertedId + }) + + afterEach(async () => { + await database.dropDatabase() + }) + + afterAll(async () => { + await client.close() + }) + + it('should migrate up', async () => { + await up(database) + + const cooperationData = await database.collection(COOPERATIONS).findOne({ _id: testCooperationId }) + + expect(cooperationData.category.toString()).toBe(testCategoryId.toString()) + expect(cooperationData.subject.toString()).toBe(testSubjectId.toString()) + expect(cooperationData.proficiencyLevel).toEqual(expect.arrayContaining(testOfferData.proficiencyLevel)) + expect(cooperationData.description).toBe(testOfferData.description) + expect(cooperationData.languages).toEqual(expect.arrayContaining(testOfferData.languages)) + }) + + it('should migrate down', async () => { + await up(database) + await down(database) + + const cooperationData = await database.collection(COOPERATIONS).findOne({ _id: testCooperationId }) + + expect(cooperationData.category).toBeUndefined() + expect(cooperationData.subject).toBeUndefined() + expect(cooperationData.proficiencyLevel).toEqual(testOfferData.proficiencyLevel[0]) + expect(cooperationData.description).toBeUndefined() + expect(cooperationData.languages).toBeUndefined() + }) +}) diff --git a/src/test/test-constants.js b/src/test/test-constants.js new file mode 100644 index 00000000..2b722ae0 --- /dev/null +++ b/src/test/test-constants.js @@ -0,0 +1,53 @@ +const { + enums: { RESOURCES_TYPES_ENUM } +} = require('~/consts/validation') + +const collectionNames = { + SUBJECTS: 'subjects', + CATEGORIES: 'categories', + OFFERS: 'offers', + COOPERATIONS: 'cooperation' +} + +const testCategoryData = { + name: 'IT', + appearance: { + icon: 'AccountTreeRoundedIcon', + color: '#47B8B8' + } +} + +const testCooperationData = { + price: 400, + receiverRole: 'tutor', + proficiencyLevel: ['Beginner'], + title: 'Test cooperation title', + description: 'Some description...', + languages: ['English'], + needAction: 'tutor', + sections: [ + { + title: 'Section 1', + description: 'Section 1 description', + resources: [ + { + resource: { + _id: '6684179479e5232bce4579fa', + author: '6658f73f93885febb491e08b', + content: '

Solving Quadratic Equations Using the Quadratic Formula

', + description: 'The quadratic formula', + title: 'Solving Quadratic Equations Using the Quadratic Formula', + category: '6684175179e5232bce4579ed', + resourceType: RESOURCES_TYPES_ENUM[0] + }, + resourceType: RESOURCES_TYPES_ENUM[0], + availability: { status: 'open', date: null } + } + ] + } + ] +} + +const testSubjectData = { name: 'Web Development' } + +module.exports = { testCategoryData, testSubjectData, testCooperationData, collectionNames } diff --git a/src/utils/cooperations/coopsAggregateOptions.js b/src/utils/cooperations/coopsAggregateOptions.js index 4eacc4a0..b4523dbb 100644 --- a/src/utils/cooperations/coopsAggregateOptions.js +++ b/src/utils/cooperations/coopsAggregateOptions.js @@ -65,43 +65,41 @@ const coopsAggregateOptions = (query, params = {}) => { }, { $lookup: { - from: 'offers', - localField: 'offer', + from: 'subjects', + localField: 'subject', foreignField: '_id', pipeline: [ - { $project: { title: 1, subject: 1, category: 1, price: 1 } }, { - $lookup: { - from: 'subjects', - let: { subjectId: '$subject' }, - pipeline: [{ $match: { $expr: { $eq: ['$_id', '$$subjectId'] } } }, { $project: { name: 1 } }], - as: 'subject' - } - }, - { - $lookup: { - from: 'categories', - let: { categoryId: '$category' }, - pipeline: [{ $match: { $expr: { $eq: ['$_id', '$$categoryId'] } } }, { $project: { appearance: 1 } }], - as: 'category' + $project: { + name: 1 } - }, - { $unwind: '$subject' }, - { $unwind: '$category' } + } ], - as: 'offer' + as: 'subject' } }, { - $unwind: { - path: '$user' + $lookup: { + from: 'categories', + localField: 'category', + foreignField: '_id', + pipeline: [ + { + $project: { + appearance: 1 + } + } + ], + as: 'category' } }, { $unwind: { - path: '$offer' + path: '$user' } }, + { $unwind: '$category' }, + { $unwind: '$subject' }, { $unset: 'sections' },