├── src ├── coverage │ ├── lcov.info │ ├── coverage-final.json │ ├── lcov-report │ │ ├── favicon.png │ │ ├── sort-arrow-sprite.png │ │ ├── prettify.css │ │ ├── block-navigation.js │ │ └── index.html │ └── clover.xml ├── UserModule │ ├── dto │ │ ├── photo.dto.ts │ │ ├── city.dto.ts │ │ ├── school.dto.ts │ │ ├── forgot-password.ts │ │ ├── state.dto.ts │ │ ├── change-password-request-id.dto.ts │ │ ├── change-password-forgot-flow.dto.ts │ │ ├── change-password.dto.ts │ │ ├── admin-change-password.dto.ts │ │ ├── certificate-user.dto.ts │ │ ├── new-student.dto.ts │ │ ├── self-update.dto.ts │ │ ├── new-user.dto.ts │ │ ├── user-update.dto.ts │ │ └── user.dto.ts │ ├── enum │ │ ├── gender.enum.ts │ │ ├── user-profile.enum.ts │ │ └── escolarity.enum.ts │ ├── repository │ │ ├── change-password.repository.ts │ │ └── user.repository.ts │ ├── entity │ │ ├── school.entity.ts │ │ └── change-password.entity.ts │ ├── swagger │ │ ├── user-updated-info.swagger.ts │ │ └── new-user.swagger.ts │ ├── service │ │ ├── state.service.ts │ │ ├── school.service.ts │ │ ├── city.service.ts │ │ ├── change-password.service.ts │ │ └── semear.service.ts │ ├── mapper │ │ └── user.mapper.ts │ ├── controller │ │ ├── state.controller.ts │ │ ├── city.controller.ts │ │ └── school.controller.ts │ └── user.module.ts ├── CommonsModule │ ├── enum │ │ └── order.enum.ts │ ├── guard │ │ ├── student-metadata.guard.ts │ │ ├── student.guard.ts │ │ └── role.guard.ts │ ├── entity │ │ └── audit.entity.ts │ ├── decorator │ │ └── role-guard-metadata.decorator.ts │ ├── httpFilter │ │ └── http-exception.filter.ts │ ├── dto │ │ ├── pageable.dto.ts │ │ └── user-jwt.dto.ts │ ├── constants.ts │ └── mapper │ │ └── mapper.ts ├── SecurityModule │ ├── enum │ │ ├── social-media.enum.ts │ │ ├── role.enum.ts │ │ ├── grant-type.enum.ts │ │ └── client-credentials.enum.ts │ ├── dto │ │ ├── refresh-token-user.dto.ts │ │ ├── login-password.dto.ts │ │ ├── new-user.dto.ts │ │ ├── google-auth-user.dto.ts │ │ ├── facebook-auth-user.dto.ts │ │ ├── policy.dto.ts │ │ ├── generated-token.dto.ts │ │ ├── role.dto.ts │ │ └── auth.dto.ts │ ├── exception │ │ ├── user-not-found.error.ts │ │ └── invalid-client-credentials.error.ts │ ├── repository │ │ ├── role.repository.ts │ │ └── client-credentials.repository.ts │ ├── entity │ │ ├── policy.entity.ts │ │ ├── role.entity.ts │ │ └── client-credentials.entity.ts │ ├── service │ │ └── role.service.ts │ └── security.module.ts ├── DashboardModule │ ├── dto │ │ ├── user-quantity.dto.ts │ │ ├── course-taken-users.dto.ts │ │ └── certificate-quantity.dto.ts │ ├── enum │ │ └── UserStatusEnum.ts │ ├── interfaces │ │ └── getCoursesByFinished.ts │ ├── dashboard.module.ts │ ├── service │ │ └── dashboard.service.ts │ └── controller │ │ └── dashboard.controller.ts ├── GameficationModule │ ├── enum │ │ ├── channel-event.enum.ts │ │ ├── time-range.enum.ts │ │ ├── start-event.enum.ts │ │ └── event-name.enum.ts │ ├── dto │ │ ├── course-nps-reward.dto.ts │ │ ├── share-app-reward-data.dto.ts │ │ ├── complete-course-reward.dto.ts │ │ ├── ranking.dto.ts │ │ ├── badge-with-quantity.dto.ts │ │ ├── ranking-query.dto.ts │ │ ├── challenge.dto.ts │ │ ├── start-event-rate-app.dto.ts │ │ ├── start-event-share-app.dto.ts │ │ ├── start-event-share-course.dto.ts │ │ ├── start-event-rules.dto.ts │ │ └── start-event.dto.ts │ ├── swagger │ │ └── pageable-ranking.swagger.ts │ ├── repository │ │ └── badge.repository.ts │ ├── service │ │ ├── achievement.service.ts │ │ ├── gamefication.service.ts │ │ └── publisher.service.ts │ ├── entity │ │ ├── achievement.entity.ts │ │ └── badge.entity.ts │ ├── subscriber │ │ └── achievement.subscriber.ts │ └── gamefication.module.ts ├── CourseModule │ ├── enum │ │ ├── course-taken-status.enum.ts │ │ └── step.enum.ts │ ├── dto │ │ ├── like-comment.dto.ts │ │ ├── new-course.taken.dto.ts │ │ ├── clap-comment.dto.ts │ │ ├── add-comment.dto.ts │ │ ├── nps-course-taken.dto.ts │ │ ├── advance-course.dto.ts │ │ ├── check-test.dto.ts │ │ ├── current-step.dto.ts │ │ ├── comment.dto.ts │ │ ├── cms-test.dto.ts │ │ ├── response.dto.ts │ │ ├── cms-part.dto.ts │ │ ├── cms-pilar.dto.ts │ │ ├── cms-trail.dto.ts │ │ ├── cms-highlight.dto.ts │ │ ├── course-taken.dto.ts │ │ ├── cms-trail-order.dto.ts │ │ ├── cms-course.dto.ts │ │ └── cms-lesson.dto.ts │ ├── repository │ │ ├── user-liked-comment.repository.ts │ │ └── comment.repository.ts │ ├── service │ │ ├── v2 │ │ │ ├── cms.service.ts │ │ │ ├── pilar.service.ts │ │ │ ├── highlight.service.ts │ │ │ ├── trail.service.ts │ │ │ ├── course-v2.service.ts │ │ │ ├── part-v2.service.ts │ │ │ ├── lesson-v2.service.ts │ │ │ └── test-v2.service.ts │ │ └── v1 │ │ │ └── course-taken.service.ts │ ├── mapper │ │ ├── comment.mapper.ts │ │ └── course-taken.mapper.ts │ ├── entity │ │ ├── user-liked-comment.entity.ts │ │ ├── comment.entity.ts │ │ └── course-taken.entity.ts │ ├── controllers │ │ ├── v2 │ │ │ ├── pilar.controller.ts │ │ │ ├── trail.controller.ts │ │ │ ├── highlight.controller.ts │ │ │ ├── course-v2.controller.ts │ │ │ ├── part-v2.controller.ts │ │ │ ├── lesson-v2.controller.ts │ │ │ └── test-v2.controller.ts │ │ └── comment.controller.ts │ └── course.module.ts ├── NotificationModule │ ├── enum │ │ └── notification-type.enum.ts │ ├── dto │ │ └── create-notification.dto.ts │ ├── service │ │ ├── pusher.service.ts │ │ └── notification.service.ts │ ├── notification.module.ts │ ├── subscriber │ │ └── notification.subscriber.ts │ ├── entity │ │ └── notification.entity.ts │ ├── repository │ │ └── notification.repository.ts │ └── controller │ │ └── notification.controller.ts ├── config │ └── templates │ │ └── change-password.hbs ├── ConfigModule │ └── config.module.ts ├── MessageModule │ ├── swagger │ │ ├── sms.swagger.ts │ │ ├── email.swagger.ts │ │ └── sendmessage.swagger.ts │ ├── dto │ │ ├── sms.dto.ts │ │ ├── email.dto.ts │ │ ├── contactus.dto.ts │ │ └── templates.dto.ts │ ├── repository │ │ └── template.repository.ts │ ├── message.module.ts │ ├── entity │ │ └── templates.entity.ts │ └── mapper │ │ └── template.mapper.ts ├── ormconfig.json ├── UploadModule │ ├── upload.module.ts │ └── controllers │ │ └── upload.controller.ts ├── scripts │ └── dbScripts │ │ ├── role_table_creation#2.sql │ │ ├── change_password_creation#5.sql │ │ ├── course_table_creation#3.sql │ │ ├── client_credentials_creation#4.sql │ │ ├── lesson_table_creation#6.sql │ │ ├── part_table_creation#7.sql │ │ └── user_table_creation#1.sql ├── migration │ ├── 1614853279741-NotificationType.ts │ ├── 1608148654499-CourseTaken.ts │ └── 1607548287894-Notification.ts ├── ormconfig.ts ├── main.ts └── app.module.ts ├── Procfile ├── test ├── jest.setup.ts └── jest-e2e.config.js ├── .eslintignore ├── .platform ├── nginx │ └── conf.d │ │ └── proxy.conf └── 00_myconf.config ├── .prettierrc ├── renovate.json ├── tsconfig.build.json ├── upload ├── 0313c948f05461f9de8ce318343debf3 ├── 073c95ec27f17130ef90ab1fa8d591c6 ├── 258c219a4ba3d1682f519a0bd0dd1bd8 ├── 3eaab504a2698c06c106e11359b0a29b ├── 4099a27ea701a0507469f5836c2755dc ├── 42fe15df5cac26c3752e59ddc44b7f5f ├── 5284737e732745b2ea806dbae456536c ├── 6029e045456f5fbf814563f1d9ed8ec6 ├── 99579e495d88be76829f26dd52bb327b ├── c3b2dbe17c3c61f23a2e7de5ba4be9d8 └── c722efb178252ad4290e59b76c64d173 ├── .vscode ├── settings.json └── extensions.json ├── templates ├── contact-us.hbs └── change-password.hbs ├── .gitpod.yml ├── .ebextensions └── 01_max_body_size.config ├── .editorconfig ├── .gitpod.Dockerfile ├── nest-cli.json ├── tsconfig.json ├── tslint.json ├── .gitignore ├── .eslintrc ├── .github ├── PULL_REQUEST_TEMPLATE │ └── pull_request_feature.md └── workflows │ └── ci.yml ├── docker-compose.yml ├── .env.example ├── GUIDELINES.md ├── ENVCONFIG.md ├── Dockerfile ├── README.md ├── CONTRIBUTING.md └── package.json /src/coverage/lcov.info: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start:prod -------------------------------------------------------------------------------- /src/coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/jest.setup.ts: -------------------------------------------------------------------------------- 1 | jest.setTimeout(6000000); 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | **/e2e-spec.ts 3 | -------------------------------------------------------------------------------- /.platform/nginx/conf.d/proxy.conf: -------------------------------------------------------------------------------- 1 | client_max_body_size 50M; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /src/UserModule/dto/photo.dto.ts: -------------------------------------------------------------------------------- 1 | export class PhotoDTO { 2 | photo: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/UserModule/dto/city.dto.ts: -------------------------------------------------------------------------------- 1 | export class CityDTO { 2 | id: string; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /.platform/00_myconf.config: -------------------------------------------------------------------------------- 1 | container_commands: 2 | 01_reload_nginx: 3 | command: "service nginx reload" 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "baseBranches": ["develop"] 6 | } 7 | -------------------------------------------------------------------------------- /src/CommonsModule/enum/order.enum.ts: -------------------------------------------------------------------------------- 1 | export enum OrderEnum { 2 | ASC = 'ASC', 3 | DESC = 'DESC', 4 | } 5 | -------------------------------------------------------------------------------- /src/SecurityModule/enum/social-media.enum.ts: -------------------------------------------------------------------------------- 1 | export enum SocialMediaEnum { 2 | FACEBOOK = 'EXTERNAL', 3 | } 4 | -------------------------------------------------------------------------------- /src/DashboardModule/dto/user-quantity.dto.ts: -------------------------------------------------------------------------------- 1 | export class UserQuantityDTO { 2 | totalElements: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/DashboardModule/dto/course-taken-users.dto.ts: -------------------------------------------------------------------------------- 1 | export class CourseTakenUsersDTO { 2 | totalElements: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/DashboardModule/dto/certificate-quantity.dto.ts: -------------------------------------------------------------------------------- 1 | export class CertificateQuantityDTO { 2 | totalElements: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/GameficationModule/enum/channel-event.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ChannelEventEnum { 2 | GAMEFICATION = 'GAMEFICATION', 3 | } 4 | -------------------------------------------------------------------------------- /src/GameficationModule/enum/time-range.enum.ts: -------------------------------------------------------------------------------- 1 | export enum TimeRangeEnum { 2 | MONTH = 'MONTH', 3 | YEAR = 'YEAR', 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/DashboardModule/enum/UserStatusEnum.ts: -------------------------------------------------------------------------------- 1 | export enum UserStatusEnum { 2 | ACTIVE = 'ACTIVE', 3 | INACTIVE = 'INACTIVE', 4 | } 5 | -------------------------------------------------------------------------------- /src/coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/src/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /src/GameficationModule/dto/course-nps-reward.dto.ts: -------------------------------------------------------------------------------- 1 | export class CourseNpsRewardDTO { 2 | userId: string; 3 | courseId: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/share-app-reward-data.dto.ts: -------------------------------------------------------------------------------- 1 | export class ShareAppRewardDataDTO { 2 | userId: string; 3 | platform: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/SecurityModule/enum/role.enum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | EXTERNAL = 'EXTERNAL', 3 | STUDENT = 'STUDENT', 4 | ADMIN = 'ADMIN', 5 | } 6 | -------------------------------------------------------------------------------- /upload/0313c948f05461f9de8ce318343debf3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/0313c948f05461f9de8ce318343debf3 -------------------------------------------------------------------------------- /upload/073c95ec27f17130ef90ab1fa8d591c6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/073c95ec27f17130ef90ab1fa8d591c6 -------------------------------------------------------------------------------- /upload/258c219a4ba3d1682f519a0bd0dd1bd8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/258c219a4ba3d1682f519a0bd0dd1bd8 -------------------------------------------------------------------------------- /upload/3eaab504a2698c06c106e11359b0a29b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/3eaab504a2698c06c106e11359b0a29b -------------------------------------------------------------------------------- /upload/4099a27ea701a0507469f5836c2755dc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/4099a27ea701a0507469f5836c2755dc -------------------------------------------------------------------------------- /upload/42fe15df5cac26c3752e59ddc44b7f5f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/42fe15df5cac26c3752e59ddc44b7f5f -------------------------------------------------------------------------------- /upload/5284737e732745b2ea806dbae456536c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/5284737e732745b2ea806dbae456536c -------------------------------------------------------------------------------- /upload/6029e045456f5fbf814563f1d9ed8ec6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/6029e045456f5fbf814563f1d9ed8ec6 -------------------------------------------------------------------------------- /upload/99579e495d88be76829f26dd52bb327b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/99579e495d88be76829f26dd52bb327b -------------------------------------------------------------------------------- /upload/c3b2dbe17c3c61f23a2e7de5ba4be9d8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/c3b2dbe17c3c61f23a2e7de5ba4be9d8 -------------------------------------------------------------------------------- /upload/c722efb178252ad4290e59b76c64d173: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/upload/c722efb178252ad4290e59b76c64d173 -------------------------------------------------------------------------------- /src/CourseModule/enum/course-taken-status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CourseTakenStatusEnum { 2 | TAKEN = 'TAKEN', 3 | COMPLETED = 'COMPLETED', 4 | } 5 | -------------------------------------------------------------------------------- /src/CourseModule/enum/step.enum.ts: -------------------------------------------------------------------------------- 1 | export enum StepEnum { 2 | NEW_LESSON = 'NEW_LESSON', 3 | NEW_PART = 'NEW_PART', 4 | NEW_TEST = 'NEW_TEST', 5 | } 6 | -------------------------------------------------------------------------------- /src/UserModule/dto/school.dto.ts: -------------------------------------------------------------------------------- 1 | export interface School { 2 | school: string; 3 | uf: string; 4 | } 5 | export type Schools = [number, School[]]; 6 | -------------------------------------------------------------------------------- /src/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewSchoolApp/newschool-backend/HEAD/src/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /src/GameficationModule/dto/complete-course-reward.dto.ts: -------------------------------------------------------------------------------- 1 | export class CompleteCourseRewardDTO { 2 | public userId: string; 3 | public courseId: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/UserModule/enum/gender.enum.ts: -------------------------------------------------------------------------------- 1 | export enum GenderEnum { 2 | MALE = 'MALE', 3 | FEMALE = 'FEMALE', 4 | NB = 'NOT BINARY', 5 | ND = 'NOT DEFINED', 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "jest.autoEnable": false 7 | } 8 | -------------------------------------------------------------------------------- /src/DashboardModule/interfaces/getCoursesByFinished.ts: -------------------------------------------------------------------------------- 1 | export interface getCoursesByFinished { 2 | courses: Array; 3 | frequency: string; 4 | title: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/GameficationModule/enum/start-event.enum.ts: -------------------------------------------------------------------------------- 1 | export enum StartEventEnum { 2 | SHARE_COURSE = 'SHARE_COURSE', 3 | RATE_APP = 'RATE_APP', 4 | SHARE_APP = 'SHARE_APP', 5 | } 6 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/ranking.dto.ts: -------------------------------------------------------------------------------- 1 | export class RankingDTO { 2 | userId: string; 3 | userName: string; 4 | points: string; 5 | photo?: string; 6 | rank: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/NotificationModule/enum/notification-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationTypeEnum { 2 | GAMEFICATION = 'GAMEFICATION', 3 | MARKETPLACE = 'MARKETPLACE', 4 | OTHER = 'OTHER', 5 | } 6 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/badge-with-quantity.dto.ts: -------------------------------------------------------------------------------- 1 | import { Badge } from '../entity/badge.entity'; 2 | 3 | export class BadgeWithQuantityDTO extends Badge { 4 | quantity: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/SecurityModule/enum/grant-type.enum.ts: -------------------------------------------------------------------------------- 1 | export enum GrantTypeEnum { 2 | CLIENT_CREDENTIALS = 'client_credentials', 3 | PASSWORD = 'password', 4 | REFRESH_TOKEN = 'refresh_token', 5 | } 6 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/ranking-query.dto.ts: -------------------------------------------------------------------------------- 1 | export class RankingQueryDTO { 2 | userId: string; 3 | userName: string; 4 | points: string; 5 | photoPath: string; 6 | rank: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/CourseModule/dto/like-comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class LikeCommentDTO { 4 | @IsNotEmpty() 5 | @IsString() 6 | userId: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/refresh-token-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../UserModule/entity/user.entity'; 2 | 3 | export class RefreshTokenUserDTO extends User { 4 | isRefreshToken: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/UserModule/dto/forgot-password.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class ForgotPasswordDTO { 4 | @IsNotEmpty() 5 | @IsString() 6 | email: string; 7 | } 8 | -------------------------------------------------------------------------------- /templates/contact-us.hbs: -------------------------------------------------------------------------------- 1 |

Plataforma New School - Fale Conosco.

2 | 3 |

Nome:{{ name }}

4 |

Celular:{{ cellphone }}

5 |

Mensagem: {{ message }}

6 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/challenge.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class ChallengeDTO { 4 | @IsString() 5 | @IsNotEmpty() 6 | challenge: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/UserModule/dto/state.dto.ts: -------------------------------------------------------------------------------- 1 | export class StateDTO { 2 | id: number; 3 | sigla: string; 4 | nome: string; 5 | regiao: { 6 | id: number; 7 | sigla: string; 8 | nome: string; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | ports: 5 | - port: 8080 6 | onOpen: open-preview 7 | 8 | tasks: 9 | - init: cp ./.env.example ./.env && npm install 10 | command: npm run start:dev 11 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/login-password.dto.ts: -------------------------------------------------------------------------------- 1 | export class LoginPasswordDTO { 2 | grant_type: 'password' | 'client_credentials' | 'refresh_token'; 3 | username?: string; 4 | password?: string; 5 | refresh_token?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/SecurityModule/enum/client-credentials.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ClientCredentialsEnum { 2 | 'NEWSCHOOL@EXTERNAL' = 'NEWSCHOOL@EXTERNAL', 3 | 'NEWSCHOOL@FRONT' = 'NEWSCHOOL@FRONT', 4 | 'NEWSCHOOL@ADMIN' = 'NEWSCHOOL@ADMIN', 5 | } 6 | -------------------------------------------------------------------------------- /src/CommonsModule/guard/student-metadata.guard.ts: -------------------------------------------------------------------------------- 1 | import { CustomDecorator, SetMetadata } from '@nestjs/common'; 2 | 3 | export const UserIdParam = (userIdParam: string): CustomDecorator => 4 | SetMetadata('userIdParam', userIdParam); 5 | -------------------------------------------------------------------------------- /.ebextensions/01_max_body_size.config: -------------------------------------------------------------------------------- 1 | files: 2 | "/etc/nginx/conf.d/01_proxy.conf": 3 | mode: "000755" 4 | owner: root 5 | group: root 6 | content: | 7 | client_max_body_size 200M; 8 | client_body_buffer_size 16k; 9 | -------------------------------------------------------------------------------- /src/UserModule/enum/user-profile.enum.ts: -------------------------------------------------------------------------------- 1 | export enum UserProfileEnum { 2 | STUDENT = 'STUDENT', 3 | EX_STUDENT = 'EX_STUDENT', 4 | UNIVERSITY = 'UNIVERSITY', 5 | FATHER = 'FATHER', 6 | INVESTOR = 'INVESTOR', 7 | OTHERS = 'OTHERS', 8 | } 9 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/new-user.dto.ts: -------------------------------------------------------------------------------- 1 | export class NewUserDTO { 2 | id: string; 3 | 4 | username: string; 5 | 6 | password: string; 7 | 8 | roleName: string; 9 | 10 | facebookId?: string; 11 | 12 | googleSub?: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/SecurityModule/exception/user-not-found.error.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class UserNotFoundError extends HttpException { 4 | constructor() { 5 | super('User not found', HttpStatus.NOT_FOUND); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/google-auth-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class GoogleAuthUserDTO { 4 | @IsNotEmpty() 5 | @IsString() 6 | sub: string; 7 | 8 | @IsNotEmpty() 9 | @IsString() 10 | email: string; 11 | } 12 | -------------------------------------------------------------------------------- /templates/change-password.hbs: -------------------------------------------------------------------------------- 1 |

Olá {{ name }}

2 | 3 |

Recebemos uma solicitação para trocar a senha

4 |

Caso não tenha sido você que pediu, desconsidere esse email

5 |

Se você realmente quer trocar a senha, clique aqui

6 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/facebook-auth-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class FacebookAuthUserDTO { 4 | @IsNotEmpty() 5 | @IsString() 6 | id: string; 7 | 8 | @IsNotEmpty() 9 | @IsString() 10 | email: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/config/templates/change-password.hbs: -------------------------------------------------------------------------------- 1 |

Olá {{ name }}

2 | 3 |

Recebemos uma solicitação para trocar a senha

4 |

Caso não tenha sido você que pediu, desconsidere esse email

5 |

Se você realmente quer trocar a senha, clique aqui

6 | -------------------------------------------------------------------------------- /src/CourseModule/dto/new-course.taken.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 | 3 | export class NewCourseTakenDTO { 4 | @IsString() 5 | @IsNotEmpty() 6 | userId: string; 7 | 8 | @IsNumber() 9 | @IsNotEmpty() 10 | courseId: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/UserModule/dto/change-password-request-id.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class ChangePasswordRequestIdDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | id: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/CommonsModule/entity/audit.entity.ts: -------------------------------------------------------------------------------- 1 | import { CreateDateColumn, UpdateDateColumn, VersionColumn } from 'typeorm'; 2 | 3 | export abstract class Audit { 4 | @CreateDateColumn() 5 | createdAt; 6 | 7 | @UpdateDateColumn() 8 | updatedAt; 9 | 10 | @VersionColumn() 11 | version; 12 | } 13 | -------------------------------------------------------------------------------- /src/SecurityModule/exception/invalid-client-credentials.error.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class InvalidClientCredentialsError extends HttpException { 4 | constructor() { 5 | super('Client credentials not found', HttpStatus.NOT_FOUND); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/UserModule/repository/change-password.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { ChangePassword } from '../entity/change-password.entity'; 3 | 4 | @EntityRepository(ChangePassword) 5 | export class ChangePasswordRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | indent_style = space 11 | indent_size = 2 12 | quote_type = single -------------------------------------------------------------------------------- /src/CourseModule/repository/user-liked-comment.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { UserLikedComment } from '../entity/user-liked-comment.entity'; 3 | 4 | @EntityRepository(UserLikedComment) 5 | export class UserLikedCommentRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/start-event-rate-app.dto.ts: -------------------------------------------------------------------------------- 1 | import { StartEventDTO } from './start-event.dto'; 2 | 3 | export class StartEventRateAppRuleDTO { 4 | userId: string; 5 | rate: number; 6 | } 7 | 8 | export class StartEventRateAppDTO extends StartEventDTO { 9 | rule: StartEventRateAppRuleDTO; 10 | } 11 | -------------------------------------------------------------------------------- /src/CourseModule/dto/clap-comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString, Max, Min } from 'class-validator'; 2 | 3 | export class ClapCommentDTO { 4 | @IsNotEmpty() 5 | @IsString() 6 | userId: string; 7 | 8 | @IsNotEmpty() 9 | @IsNumber() 10 | @Min(0) 11 | @Max(50) 12 | claps: number; 13 | } 14 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | # Install custom tools, runtimes, etc. 4 | # For example "bastet", a command-line tetris clone: 5 | # RUN brew install bastet 6 | # 7 | # More information: https://www.gitpod.io/docs/config-docker/ 8 | 9 | ENV IS_GITPOD=true 10 | 11 | RUN npm install -g npm@7.5.2 12 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/start-event-share-app.dto.ts: -------------------------------------------------------------------------------- 1 | import { StartEventDTO } from './start-event.dto'; 2 | 3 | export class StartEventShareAppRuleDTO { 4 | userId: string; 5 | platform: string; 6 | } 7 | 8 | export class StartEventShareAppDTO extends StartEventDTO { 9 | rule: StartEventShareAppRuleDTO; 10 | } 11 | -------------------------------------------------------------------------------- /src/CourseModule/dto/add-comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 | 3 | export class AddCommentDTO { 4 | @IsNotEmpty() 5 | @IsNumber() 6 | partId: number; 7 | 8 | @IsNotEmpty() 9 | @IsString() 10 | userId: string; 11 | 12 | @IsNotEmpty() 13 | @IsString() 14 | text: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/CourseModule/dto/nps-course-taken.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsOptional, IsString, Max, Min } from 'class-validator'; 2 | 3 | export class NpsCourseTakenDTO { 4 | @IsNotEmpty() 5 | @IsNumber() 6 | @Min(0) 7 | @Max(10) 8 | rating: number; 9 | 10 | @IsOptional() 11 | @IsString() 12 | feedback?: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/CommonsModule/decorator/role-guard-metadata.decorator.ts: -------------------------------------------------------------------------------- 1 | import { CustomDecorator, SetMetadata } from '@nestjs/common'; 2 | 3 | export const NeedRoles = (...roles: string[]): CustomDecorator => 4 | SetMetadata('roles', roles); 5 | 6 | export const NeedPolicies = (...policies: string[]): CustomDecorator => 7 | SetMetadata('policies', policies); 8 | -------------------------------------------------------------------------------- /src/GameficationModule/swagger/pageable-ranking.swagger.ts: -------------------------------------------------------------------------------- 1 | import { RankingQueryDTO } from '../dto/ranking-query.dto'; 2 | 3 | export class PageableRankingSwagger { 4 | content: RankingQueryDTO[]; 5 | size: number; 6 | totalElements: number; 7 | totalPages: number; 8 | limit: number; 9 | page: number; 10 | offset: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/ConfigModule/config.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { AppConfigService as ConfigService } from './service/app-config.service'; 3 | 4 | @Global() 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [ConfigService], 9 | exports: [ConfigService], 10 | }) 11 | export class ConfigModule {} 12 | -------------------------------------------------------------------------------- /src/CourseModule/dto/advance-course.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class AdvanceCourseDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | userId: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | courseId: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/start-event-share-course.dto.ts: -------------------------------------------------------------------------------- 1 | import { StartEventDTO } from './start-event.dto'; 2 | 3 | export class StartEventShareCourseRuleDTO { 4 | courseId: number; 5 | userId: string; 6 | platform: string; 7 | } 8 | 9 | export class StartEventShareCourseDTO extends StartEventDTO { 10 | rule: StartEventShareCourseRuleDTO; 11 | } 12 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/policy.dto.ts: -------------------------------------------------------------------------------- 1 | import slugify from 'slugify'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class PolicyDTO { 5 | @Expose() 6 | id: string; 7 | 8 | @Expose() 9 | name: string; 10 | 11 | @Expose() 12 | get slug(): string { 13 | return slugify(this.name); 14 | } 15 | 16 | set slug(name: string) {} 17 | } 18 | -------------------------------------------------------------------------------- /src/MessageModule/swagger/sms.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class SMSSwagger { 4 | @ApiProperty({ type: String }) 5 | name: string; 6 | 7 | @ApiProperty({ type: String }) 8 | phone: string; 9 | 10 | @ApiProperty({ type: String }) 11 | message: string; 12 | 13 | @ApiProperty({ type: String }) 14 | title: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/cms.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AxiosResponse } from 'axios'; 3 | import { CmsIntegration } from '../../integration/cms.integration'; 4 | import { CMSTrailDTO } from '../../dto/cms-trail.dto'; 5 | 6 | @Injectable() 7 | export class CmsService { 8 | constructor(private readonly integration: CmsIntegration) {} 9 | } 10 | -------------------------------------------------------------------------------- /src/MessageModule/swagger/email.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class EmailSwagger { 4 | @ApiProperty({ type: String }) 5 | name: string; 6 | 7 | @ApiProperty({ type: String }) 8 | email: string; 9 | 10 | @ApiProperty({ type: String }) 11 | title: string; 12 | 13 | @ApiProperty({ type: String }) 14 | message: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/UserModule/dto/change-password-forgot-flow.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class ChangePasswordForgotFlowDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | newPassword: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | confirmNewPassword: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/MessageModule/dto/sms.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class SMSDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | name: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | phone: string; 14 | 15 | @IsNotEmpty() 16 | @Expose() 17 | message?: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/UserModule/entity/school.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | @Entity() 5 | export class School { 6 | @PrimaryGeneratedColumn() 7 | @Expose() 8 | id: string; 9 | 10 | @Column({ nullable: false }) 11 | @Expose() 12 | school: string; 13 | 14 | @Column() 15 | @Expose() 16 | uf: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/UserModule/swagger/user-updated-info.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { User } from '../entity/user.entity'; 3 | 4 | export class UserUpdatedInfoSwagger { 5 | @ApiProperty({ type: String }) 6 | name: User['name']; 7 | 8 | @ApiProperty({ type: String }) 9 | email: User['email']; 10 | 11 | @ApiProperty({ type: String }) 12 | password: User['password']; 13 | } 14 | -------------------------------------------------------------------------------- /src/ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "mysql", 3 | "host": "0.0.0.0", 4 | "port": 3306, 5 | "username": "mysql", 6 | "password": "t^ECX@i.@Im4", 7 | "database": "mysql", 8 | "entities": ["src/**/entity/**/*.ts"], 9 | "migrationsTableName": "migration", 10 | "migrations": ["src/migrations/*.ts"], 11 | "synchronize": true, 12 | "cli": { 13 | "migrationsDir": "src/migration" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/start-event-rules.dto.ts: -------------------------------------------------------------------------------- 1 | import { StartEventShareCourseRuleDTO } from './start-event-share-course.dto'; 2 | import { StartEventRateAppRuleDTO } from './start-event-rate-app.dto'; 3 | import { StartEventShareAppRuleDTO } from './start-event-share-app.dto'; 4 | 5 | export type StartEventRules = 6 | | StartEventShareCourseRuleDTO 7 | | StartEventRateAppRuleDTO 8 | | StartEventShareAppRuleDTO; 9 | -------------------------------------------------------------------------------- /src/GameficationModule/dto/start-event.dto.ts: -------------------------------------------------------------------------------- 1 | import { StartEventEnum } from '../enum/start-event.enum'; 2 | import { StartEventRules } from './start-event-rules.dto'; 3 | import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; 4 | 5 | export class StartEventDTO { 6 | @IsNotEmpty() 7 | @IsEnum(StartEventEnum) 8 | event: StartEventEnum; 9 | 10 | @IsOptional() 11 | rule?: StartEventRules; 12 | } 13 | -------------------------------------------------------------------------------- /src/SecurityModule/repository/role.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Role } from '../entity/role.entity'; 3 | import { RoleEnum } from '../enum/role.enum'; 4 | 5 | @EntityRepository(Role) 6 | export class RoleRepository extends Repository { 7 | public async findByRoleName(name: RoleEnum): Promise { 8 | return this.findOne({ name }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/UploadModule/upload.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule, Module } from '@nestjs/common'; 2 | import { UploadController } from './controllers/upload.controller'; 3 | import { UploadService } from './service/upload.service'; 4 | 5 | @Module({ 6 | imports: [HttpModule], 7 | controllers: [UploadController], 8 | providers: [UploadService], 9 | exports: [UploadService], 10 | }) 11 | export class UploadModule {} 12 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/generated-token.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 | 3 | export class GeneratedTokenDTO { 4 | @IsNotEmpty() 5 | @IsString() 6 | accessToken: string; 7 | 8 | @IsString() 9 | refreshToken?: string; 10 | 11 | @IsNotEmpty() 12 | @IsString() 13 | tokenType: string; 14 | 15 | @IsNotEmpty() 16 | @IsNumber() 17 | expiresIn: number; 18 | } 19 | -------------------------------------------------------------------------------- /src/coverage/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "plugins": [ 6 | { 7 | "name": "@nestjs/swagger/plugin", 8 | "options": { 9 | "dtoFileNameSuffix": [".dto.ts", ".swagger.ts"], 10 | "controllerFileNameSuffix": [".controller.ts"], 11 | "classValidatorShim": true 12 | } 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/CourseModule/dto/check-test.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEnum, IsNotEmpty } from 'class-validator'; 2 | 3 | export enum ChosenAlternativeEnum { 4 | A = 'A', 5 | B = 'B', 6 | C = 'C', 7 | D = 'D', 8 | } 9 | 10 | export class CheckTestBodyDTO { 11 | @IsNotEmpty() 12 | @IsEnum(ChosenAlternativeEnum) 13 | chosenAlternative: ChosenAlternativeEnum; 14 | } 15 | 16 | export class CheckTestResponseDTO { 17 | isCorrect: boolean; 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "ES2019", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true, 13 | "resolveJsonModule": true 14 | }, 15 | "exclude": ["node_modules", "dist"] 16 | } 17 | -------------------------------------------------------------------------------- /src/CourseModule/dto/current-step.dto.ts: -------------------------------------------------------------------------------- 1 | import { CMSPartDTO } from './cms-part.dto'; 2 | import { CMSTestDTO } from './cms-test.dto'; 3 | 4 | export enum CurrentStepDoingEnum { 5 | COMPLETED = 'COMPLETED', 6 | PART = 'PART', 7 | TEST = 'TEST', 8 | CHALLENGE = 'CHALLENGE', 9 | } 10 | 11 | export class CurrentStepDTO { 12 | doing: CurrentStepDoingEnum; 13 | part?: CMSPartDTO; 14 | test?: Omit; 15 | } 16 | -------------------------------------------------------------------------------- /src/scripts/dbScripts/role_table_creation#2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `role` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `version` int(11) NOT NULL, 5 | `id` varchar(36) NOT NULL, 6 | `name` ENUM('ADMIN', 'STUDENT', 'EXTERNAL') NOT NULL, 7 | CONSTRAINT `PK_ROLE` PRIMARY KEY (`id`), 8 | constraint IDX_ae4578dcaed5adff96595e6166 UNIQUE (name) 9 | ); 10 | -------------------------------------------------------------------------------- /src/UserModule/dto/change-password.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class ChangePasswordDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | password: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | newPassword: string; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | confirmNewPassword: string; 19 | } 20 | -------------------------------------------------------------------------------- /src/UserModule/dto/admin-change-password.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class AdminChangePasswordDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | oldPassword: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | newPassword: string; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | confirmNewPassword: string; 19 | } 20 | -------------------------------------------------------------------------------- /src/NotificationModule/dto/create-notification.dto.ts: -------------------------------------------------------------------------------- 1 | import { NotificationTypeEnum } from '../enum/notification-type.enum'; 2 | import { IsBoolean, IsEnum, IsNotEmpty, IsObject } from 'class-validator'; 3 | 4 | export class CreateNotificationDTO { 5 | @IsBoolean() 6 | @IsNotEmpty() 7 | important = false; 8 | 9 | @IsEnum(NotificationTypeEnum) 10 | @IsNotEmpty() 11 | type: NotificationTypeEnum; 12 | 13 | @IsObject() 14 | content?: Record = {}; 15 | } 16 | -------------------------------------------------------------------------------- /src/UserModule/service/state.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService, Injectable } from '@nestjs/common'; 2 | import { StateDTO } from '../dto/state.dto'; 3 | 4 | @Injectable() 5 | export class StateService { 6 | constructor(private http: HttpService) {} 7 | 8 | public async getStates(): Promise { 9 | const response = await this.http 10 | .get(`https://servicodados.ibge.gov.br/api/v1/localidades/estados`) 11 | .toPromise(); 12 | return response.data; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MessageModule/repository/template.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Templates } from '../entity/templates.entity'; 3 | 4 | @EntityRepository(Templates) 5 | export class TemplateRepository extends Repository { 6 | public async findById(id: string): Promise { 7 | return this.findOne({ id }); 8 | } 9 | 10 | public async findByName(name: string): Promise { 11 | return this.findOne({ where: { name } }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/role.dto.ts: -------------------------------------------------------------------------------- 1 | import { Expose, Type } from 'class-transformer'; 2 | import { IsArray, IsNotEmpty, IsString } from 'class-validator'; 3 | import { PolicyDTO } from './policy.dto'; 4 | 5 | export class RoleDTO { 6 | @IsNotEmpty() 7 | @IsString() 8 | @Expose() 9 | id: string; 10 | 11 | @IsNotEmpty() 12 | @IsString() 13 | @Expose() 14 | name: string; 15 | 16 | @Type(() => PolicyDTO) 17 | @IsArray() 18 | @IsNotEmpty() 19 | @Expose() 20 | policies: PolicyDTO[]; 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false, 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /src/MessageModule/dto/email.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class EmailDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | name: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | email: string; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | title: string; 19 | 20 | @IsOptional() 21 | @IsString() 22 | @Expose() 23 | message?: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/UserModule/entity/change-password.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { User } from './user.entity'; 3 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 4 | 5 | @Entity() 6 | export class ChangePassword extends Audit { 7 | @PrimaryGeneratedColumn('uuid') 8 | id: string; 9 | 10 | @Column() 11 | expirationTime: number; 12 | 13 | @ManyToOne(() => User, (user: User) => user.changePasswordRequests) 14 | user: User; 15 | } 16 | -------------------------------------------------------------------------------- /src/MessageModule/dto/contactus.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; 2 | import { Expose } from 'class-transformer'; 3 | 4 | export class ContactUsDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | name: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | cellphone: string; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | message: string; 19 | 20 | @IsOptional() 21 | @IsString() 22 | @Expose() 23 | email?: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/DashboardModule/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule, Module } from '@nestjs/common'; 2 | import { DashboardService } from './service/dashboard.service'; 3 | import { DashboardController } from './controller/dashboard.controller'; 4 | import { UserModule } from '../UserModule/user.module'; 5 | import { CourseModule } from '../CourseModule/course.module'; 6 | 7 | @Module({ 8 | imports: [HttpModule, UserModule, CourseModule], 9 | controllers: [DashboardController], 10 | providers: [DashboardService], 11 | }) 12 | export class DashboardModule {} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Enviroment Variables 6 | .env 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | 16 | # OS 17 | .DS_Store 18 | 19 | # Tests 20 | /coverage 21 | /.nyc_output 22 | 23 | # IDEs and editors 24 | /.idea 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | 32 | # IDE - VSCode 33 | .vscode 34 | 35 | # IDE - Theia 36 | .theia 37 | 38 | # env file 39 | .env 40 | /upload 41 | /test/coverage 42 | -------------------------------------------------------------------------------- /src/SecurityModule/repository/client-credentials.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { ClientCredentials } from '../entity/client-credentials.entity'; 3 | 4 | @EntityRepository(ClientCredentials) 5 | export class ClientCredentialsRepository extends Repository { 6 | async findByNameAndSecret( 7 | name: string, 8 | secret: string, 9 | ): Promise { 10 | return this.findOne( 11 | { name, secret }, 12 | { relations: ['role', 'role.policies'] }, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/scripts/dbScripts/change_password_creation#5.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `change_password` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `version` int(11) NOT NULL, 5 | `id` varchar(36) NOT NULL, 6 | `expirationTime` int(11) NOT NULL, 7 | `userId` varchar(36) DEFAULT NULL, 8 | CONSTRAINT `PK_CHG_PWD` PRIMARY KEY (`id`), 9 | KEY `FK_CHG_PWD_USR` (`userId`), 10 | CONSTRAINT `FK_CHG_PWD_USR` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION 11 | ); -------------------------------------------------------------------------------- /src/CommonsModule/httpFilter/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; 2 | import { Response } from 'express'; 3 | 4 | @Catch(HttpException) 5 | export class HttpExceptionFilter implements ExceptionFilter { 6 | catch(exception: HttpException, host: ArgumentsHost): void { 7 | const ctx = host.switchToHttp(); 8 | const response = ctx.getResponse(); 9 | const status = exception.getStatus(); 10 | const error = exception.getResponse(); 11 | response.status(status).json(error); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/GameficationModule/repository/badge.repository.ts: -------------------------------------------------------------------------------- 1 | import { Badge } from '../entity/badge.entity'; 2 | import { EntityRepository, Repository } from 'typeorm'; 3 | import { EventNameEnum } from '../enum/event-name.enum'; 4 | 5 | @EntityRepository(Badge) 6 | export class BadgeRepository extends Repository { 7 | public async findByEventNameAndOrder( 8 | eventName: EventNameEnum, 9 | eventOrder: number, 10 | ): Promise { 11 | const queryResponse = await this.find({ 12 | eventName, 13 | eventOrder, 14 | }); 15 | return queryResponse[0]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/GameficationModule/service/achievement.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AchievementRepository } from '../repository/achievement.repository'; 3 | import { User } from '../../UserModule/entity/user.entity'; 4 | import { BadgeWithQuantityDTO } from '../dto/badge-with-quantity.dto'; 5 | 6 | @Injectable() 7 | export class AchievementService { 8 | constructor(private readonly repository: AchievementRepository) {} 9 | 10 | findBadgesWithQuantityByUser(user: User): Promise { 11 | return this.repository.findBadgesCountByUserId(user.id); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/UserModule/dto/certificate-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { Expose } from 'class-transformer'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class CertificateUserDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | id: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | title: string; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | text: string; 19 | 20 | @IsNotEmpty() 21 | @IsString() 22 | @Expose() 23 | userName: string; 24 | 25 | @IsNotEmpty() 26 | @IsString() 27 | @Expose() 28 | certificateBackgroundName: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/UserModule/enum/escolarity.enum.ts: -------------------------------------------------------------------------------- 1 | export enum EscolarityEnum { 2 | ENSINO_FUNDAMENTAL_INCOMPLETO = 'ENSINO_FUNDAMENTAL_INCOMPLETO', 3 | ENSINO_FUNDAMENTAL_CURSANDO = 'ENSINO_FUNDAMENTAL_CURSANDO', 4 | ENSINO_FUNDAMENTAL_COMPLETO = 'ENSINO_FUNDAMENTAL_COMPLETO', 5 | ENSINO_MEDIO_INCOMPLETO = 'ENSINO_MEDIO_INCOMPLETO', 6 | ENSINO_MEDIO_CURSANDO = 'ENSINO_MEDIO_CURSANDO', 7 | ENSINO_MEDIO_COMPLETO = 'ENSINO_MEDIO_COMPLETO', 8 | TERCEIRO_ANO = 'TERCEIRO_ANO', 9 | FACULDADE_INCOMPLETO = 'FACULDADE_INCOMPLETO', 10 | FACULDADE_CURSANDO = 'FACULDADE_CURSANDO', 11 | FACULDADE_COMPLETO = 'FACULDADE_COMPLETO', 12 | } 13 | -------------------------------------------------------------------------------- /src/GameficationModule/enum/event-name.enum.ts: -------------------------------------------------------------------------------- 1 | export enum EventNameEnum { 2 | COURSE_REWARD_TEST_ON_FIRST_TAKE = 'CourseReward::TestOnFirstTake', 3 | COURSE_REWARD_COMPLETE_COURSE = 'CourseReward::CompleteCourse', 4 | USER_REWARD_SHARE_COURSE = 'UserReward::ShareCourse', 5 | USER_REWARD_RATE_APP = 'UserReward::RateApp', 6 | USER_REWARD_INVITE_USER = 'UserReward::InviteUser', 7 | USER_REWARD_COMPLETE_REGISTRATION = 'UserReward::CompleteRegistration', 8 | COURSE_REWARD_COURSE_NPS = 'CourseReward::CourseNPS', 9 | USER_REWARD_SHARE_APP = 'UserReward::ShareApp', 10 | USER_REWARD_TOP_MONTH = 'UserReward::TopMonth', 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2018, 5 | "sourceType": "module" 6 | }, 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "node": true, 13 | "es6": true, 14 | "jest": true 15 | }, 16 | "extends": [ 17 | "eslint:recommended", 18 | "plugin:@typescript-eslint/recommended", 19 | "prettier", 20 | "prettier/@typescript-eslint" 21 | ], 22 | "rules": { 23 | "camelcase": "error", 24 | "rest-spread-spacing": "error", 25 | "@typescript-eslint/ban-ts-comment": "off" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "dbaeumer.vscode-eslint", 7 | "esbenp.prettier-vscode", 8 | "steoates.autoimport", 9 | "editorconfig.editorconfig", 10 | "orta.vscode-jest" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [ 14 | 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_feature.md: -------------------------------------------------------------------------------- 1 | # COISA NOVA 2 | 3 | Fala o que cê fez 4 | 5 | ## Mudança (selecione apenas um) 6 | 7 | Selecione o que você fez (selecione apenas um) 8 | 9 | - [ ] Nova funcionalidade sem breaking-change 10 | - [ ] Nova funcionalidade com breaking-change 11 | - [ ] Arrumei alguma coisa sem breaking-change 12 | - [ ] Arrumei alguma coisa com breaking-change 13 | 14 | # Checklist: 15 | 16 | Todas as alterações precisam ter esses requisitos (todos precisam estar marcados) 17 | 18 | - [ ] Meu código segue as guidelines do projeto 19 | - [ ] Eu fiz um review próprio 20 | - [ ] FIZ TESTES! 21 | - [ ] Não quebrei nenhum teste 22 | -------------------------------------------------------------------------------- /src/scripts/dbScripts/course_table_creation#3.sql: -------------------------------------------------------------------------------- 1 | create TABLE `course` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `version` int(11) NOT NULL, 5 | `id` varchar(36) NOT NULL, 6 | `title` varchar(255) NOT NULL, 7 | `description` varchar(255) NOT NULL, 8 | `thumbUrl` varchar(255) DEFAULT NULL, 9 | `userId` varchar(36) NOT NULL, 10 | CONSTRAINT PK_CRS PRIMARY KEY (`id`) 11 | CONSTRAINT `FK_USER_ID` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ON delete SET NULL ON update set NULL, 12 | constraint IDX_ae45785eved5adff96595e6166 unique (title) 13 | ); 14 | -------------------------------------------------------------------------------- /src/MessageModule/message.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { TemplateRepository } from './repository/template.repository'; 4 | import { TemplateMapper } from './mapper/template.mapper'; 5 | import { MessageController } from './controller/message.controller'; 6 | import { MessageService } from './service/message.service'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([TemplateRepository]), HttpModule], 10 | controllers: [MessageController], 11 | providers: [MessageService, TemplateMapper], 12 | exports: [MessageService], 13 | }) 14 | export class MessageModule {} 15 | -------------------------------------------------------------------------------- /src/MessageModule/entity/templates.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; 2 | import { Expose } from 'class-transformer'; 3 | import { IsNotEmpty, IsString } from 'class-validator'; 4 | 5 | @Entity() 6 | export class Templates { 7 | @PrimaryGeneratedColumn('uuid') 8 | @Expose() 9 | id: string; 10 | 11 | @Column() 12 | @Expose() 13 | @IsNotEmpty() 14 | @IsString() 15 | @Unique(['name']) 16 | name: string; 17 | 18 | @Column() 19 | @Expose() 20 | @IsNotEmpty() 21 | @IsString() 22 | title: string; 23 | 24 | @Column() 25 | @Expose() 26 | @IsNotEmpty() 27 | @IsString() 28 | template: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/MessageModule/swagger/sendmessage.swagger.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class SendMessageSwagger { 4 | @ApiProperty({ 5 | required: true, 6 | description: 'Free to upload any data needed for emailing', 7 | }) 8 | data: any; 9 | 10 | @ApiProperty({ 11 | type: String, 12 | required: true, 13 | description: 'Email that will be sent the message', 14 | example: 'email@email.com', 15 | }) 16 | contactEmail: string; 17 | 18 | @ApiProperty({ 19 | type: String, 20 | required: true, 21 | description: 'Name of template to use.', 22 | example: 'example-template', 23 | }) 24 | templateName: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /src/SecurityModule/dto/auth.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEnum, IsOptional, IsString } from 'class-validator'; 2 | import { GrantTypeEnum } from '../enum/grant-type.enum'; 3 | import { ApiProperty } from '@nestjs/swagger'; 4 | 5 | export class AuthDTO { 6 | @ApiProperty({ enum: ['client_credentials', 'password', 'refresh_token'] }) 7 | @IsEnum(GrantTypeEnum) 8 | // tslint:disable-next-line:variable-name 9 | grant_type: GrantTypeEnum; 10 | 11 | @IsOptional() 12 | @IsString() 13 | username?: string; 14 | 15 | @IsOptional() 16 | @IsString() 17 | password?: string; 18 | 19 | @IsOptional() 20 | @IsString() 21 | // eslint-disable-next-line camelcase 22 | refresh_token?: string; 23 | } 24 | -------------------------------------------------------------------------------- /src/scripts/dbScripts/client_credentials_creation#4.sql: -------------------------------------------------------------------------------- 1 | create TABLE `client-credentials` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `version` int(11) NOT NULL, 5 | `id` varchar(36) NOT NULL, 6 | `name` ENUM('NEWSCHOOL@EXTERNAL', 'NEWSCHOOL@FRONT') NOT NULL, 7 | `secret` varchar(255) NOT NULL, 8 | `roleId` varchar(36) DEFAULT NULL, 9 | PRIMARY KEY (`id`), 10 | KEY `FK_CLNT_CRDTLS` (`roleId`), 11 | CONSTRAINT `FK_CLNT_CRDTLS` FOREIGN KEY (`roleId`) REFERENCES `role` (`id`) ON delete NO ACTION ON update NO ACTION, 12 | CONSTRAINT IDX_c6528d2d9ab22e6802915fd9f9 UNIQUE (name) 13 | ); 14 | -------------------------------------------------------------------------------- /src/NotificationModule/service/pusher.service.ts: -------------------------------------------------------------------------------- 1 | import * as Pusher from 'pusher'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { AppConfigService as ConfigService } from '../../ConfigModule/service/app-config.service'; 4 | import { Notification } from '../entity/notification.entity'; 5 | 6 | @Injectable() 7 | export class PusherService { 8 | private pusher: Pusher = null; 9 | 10 | constructor(private readonly configService: ConfigService) { 11 | this.pusher = new Pusher(this.configService.getPusherOptions()); 12 | } 13 | 14 | public postMessageToUser(notification: Notification): void { 15 | this.pusher.trigger(notification.user.id, notification.type, notification); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SecurityModule/entity/policy.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | import slugify from 'slugify'; 3 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 4 | import { Role } from './role.entity'; 5 | 6 | @Entity() 7 | export class Policy extends Audit { 8 | @PrimaryGeneratedColumn('uuid') 9 | id: string; 10 | 11 | @Column() 12 | name: string; 13 | 14 | @Column({ 15 | nullable: false, 16 | unique: true, 17 | }) 18 | get slug(): string { 19 | return slugify(this.name); 20 | } 21 | 22 | set slug(name: string) {} 23 | 24 | @ManyToMany(() => Role, (role: Role) => role.policies) 25 | roles: Role[]; 26 | } 27 | -------------------------------------------------------------------------------- /src/UserModule/service/school.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService, Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Like, Repository } from 'typeorm'; 4 | import { School } from '../dto/school.dto'; 5 | import { School as SchoolEntity } from '../entity/school.entity'; 6 | 7 | @Injectable() 8 | export class SchoolService { 9 | constructor( 10 | private http: HttpService, 11 | @InjectRepository(SchoolEntity) 12 | private readonly repository: Repository, 13 | ) {} 14 | 15 | public async getUserSchool(name: string): Promise { 16 | return await this.repository.find({ where: { school: Like(`%${name}%`) } }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/CourseModule/dto/comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { UserDTO } from '../../UserModule/dto/user.dto'; 3 | 4 | class ClappedBy { 5 | @Type(() => UserDTO) 6 | user: UserDTO; 7 | 8 | claps: number; 9 | } 10 | 11 | class ResponseInsideCommentDTO { 12 | id: string; 13 | 14 | text: string; 15 | 16 | clappedByTotalCount: number; 17 | 18 | clappedBy: ClappedBy[]; 19 | } 20 | 21 | export class CommentDTO { 22 | id: string; 23 | 24 | text: string; 25 | 26 | user: UserDTO; 27 | 28 | partId: number; 29 | 30 | @Type(() => ResponseInsideCommentDTO) 31 | responses: ResponseInsideCommentDTO[]; 32 | 33 | clappedByTotalCount: number; 34 | 35 | clappedBy: ClappedBy[]; 36 | } 37 | -------------------------------------------------------------------------------- /src/scripts/dbScripts/lesson_table_creation#6.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `lesson` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `id` varchar(36) NOT NULL, 5 | `title` varchar(255) NOT NULL, 6 | `description` varchar(255) NOT NULL, 7 | `course_id` varchar(255) DEFAULT NULL, 8 | `nxt_lsn_id` varchar(255) DEFAULT NULL, 9 | CONSTRAINT PK_LSN PRIMARY KEY (`id`), 10 | CONSTRAINT FK_LSN_CRS FOREIGN KEY (`course_id`) REFERENCES course (`id`), 11 | CONSTRAINT CHK_NXT_LSN_ID CHECK ((`nxt_lsn_id` IN (SELECT id FROM lesson)) OR (nxt_lsn_id IS NULL)), 12 | CONSTRAINT UNQ_NXT_LSN_ID UNIQUE (`nxt_lsn_id`, `course_id`) 13 | ); -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-test.dto.ts: -------------------------------------------------------------------------------- 1 | export class CMSTestDTO { 2 | id: number; 3 | titulo: string; 4 | pergunta: string; 5 | 'primeira_alternativa': string; 6 | 'segunda_alternativa': string; 7 | 'terceira_alternativa': string; 8 | 'quarta_alternativa': string; 9 | 'alternativa_certa': string; 10 | 'published_at': Date; 11 | 'created_at': Date; 12 | 'updated_at': Date; 13 | ordem: number; 14 | parte: Part; 15 | } 16 | 17 | export class Part { 18 | id: number; 19 | titulo: string; 20 | descricao: string; 21 | ordem: number; 22 | 'published_at': Date; 23 | 'created_at': Date; 24 | 'updated_at': Date; 25 | aula: number; 26 | exercicio: string; 27 | video: string; 28 | videoUrl: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/UploadModule/controllers/upload.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, Res } from '@nestjs/common'; 2 | import { Constants } from '../../CommonsModule/constants'; 3 | import { ApiTags } from '@nestjs/swagger'; 4 | import { UploadService } from '../service/upload.service'; 5 | 6 | @ApiTags('Upload') 7 | @Controller( 8 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.UPLOAD_ENDPOINT}`, 9 | ) 10 | export class UploadController { 11 | constructor(private readonly service: UploadService) {} 12 | 13 | @Get(':fileName') 14 | async serveFile( 15 | @Param('fileName') fileName: string, 16 | @Res() response, 17 | ): Promise { 18 | return this.service.sendFile(fileName, response); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SecurityModule/service/role.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { RoleEnum } from '../enum/role.enum'; 3 | import { RoleRepository } from '../repository/role.repository'; 4 | import { Role } from '../entity/role.entity'; 5 | import { Transactional } from 'typeorm-transactional-cls-hooked'; 6 | 7 | @Injectable() 8 | export class RoleService { 9 | constructor(private readonly repository: RoleRepository) {} 10 | 11 | @Transactional() 12 | public async findByRoleName(name: RoleEnum): Promise { 13 | const role: Role = await this.repository.findByRoleName(name); 14 | if (!role) { 15 | throw new NotFoundException('Role not found'); 16 | } 17 | return role; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/migration/1614853279741-NotificationType.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class NotificationType1614853279741 implements MigrationInterface { 4 | name = 'NotificationType1614853279741'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | "ALTER TABLE `notification` CHANGE `type` `type` enum ('GAMEFICATION', 'MARKETPLACE', 'OTHER') NOT NULL", 9 | ); 10 | } 11 | 12 | public async down(queryRunner: QueryRunner): Promise { 13 | await queryRunner.query( 14 | 'ALTER TABLE `notification` CHANGE `type` `type` enum (\'GAMEFICATION\', \'OTHER\') CHARACTER SET "latin1" COLLATE "latin1_swedish_ci" NOT NULL', 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/CommonsModule/dto/pageable.dto.ts: -------------------------------------------------------------------------------- 1 | export class PageableDTO { 2 | content: T[]; 3 | size: number; 4 | totalElements: number; 5 | totalPages: number; 6 | limit: number; 7 | page: number; 8 | offset: number; 9 | 10 | constructor({ 11 | content, 12 | totalElements, 13 | limit, 14 | page, 15 | }: { 16 | content: T[]; 17 | totalElements: number; 18 | limit: number; 19 | page: number; 20 | }) { 21 | this.content = content; 22 | this.totalElements = totalElements; 23 | this.limit = limit; 24 | this.page = page; 25 | this.size = content.length; 26 | this.totalPages = Math.floor(totalElements / page); 27 | this.offset = limit * (page - 1); 28 | } 29 | } 30 | 31 | export type Pageable = PageableDTO; 32 | -------------------------------------------------------------------------------- /src/UserModule/service/city.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService, Injectable } from '@nestjs/common'; 2 | import { CityDTO } from '../dto/city.dto'; 3 | 4 | @Injectable() 5 | export class CityService { 6 | constructor(private http: HttpService) {} 7 | 8 | public async getCitiesByUf(uf: string): Promise { 9 | const response = await this.http 10 | .get(`http://educacao.dadosabertosbr.com/api/cidades/${uf.toLowerCase()}`) 11 | .toPromise(); 12 | const body: string[] = response.data; 13 | // body example: ['123412:SANTOS'] 14 | 15 | let mappedBody: CityDTO[] = []; 16 | for (const city of body) { 17 | const [id, name] = city.split(':'); 18 | mappedBody = [...mappedBody, { id, name }]; 19 | } 20 | return mappedBody; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/scripts/dbScripts/part_table_creation#7.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `part` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `id` varchar(36) NOT NULL, 5 | `title` varchar(255) NOT NULL, 6 | `description` varchar(255) NOT NULL, 7 | `vimeo_url` varchar(255) DEFAULT NULL, 8 | `youtube_url` varchar(255) DEFAULT NULL, 9 | `lesson_id` varchar(36) NOT NULL, 10 | `nxt_prt_id` varchar(36) NOT NULL, 11 | CONSTRAINT PK_PRT PRIMARY KEY (`id`), 12 | CONSTRAINT FK_PRT_LSN FOREIGN KEY (`lesson_id`) REFERENCES lesson (`id`), 13 | CONSTRAINT CHK_NXT_PRT_ID CHECK ((nxt_prt_id IN (SELECT id FROM part)) OR (nxt_prt_id IS NULL)), 14 | CONSTRAINT UNQ_NXT_PRT_ID UNIQUE (`nxt_prt_id`, `lesson_id`) 15 | ); -------------------------------------------------------------------------------- /src/scripts/dbScripts/user_table_creation#1.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `user` ( 2 | `createdAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 3 | `updatedAt` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), 4 | `version` int(11) NOT NULL, 5 | `id` varchar(36) NOT NULL, 6 | `name` varchar(255) NOT NULL, 7 | `email` varchar(255) NOT NULL, 8 | `password` varchar(255) NOT NULL, 9 | `url_facebook` varchar(255) NOT NULL, 10 | `url_instagram` varchar(255) NOT NULL, 11 | `salt` varchar(255) NOT NULL DEFAULT '', 12 | `roleId` varchar(36) DEFAULT NULL, 13 | CONSTRAINT PK_USER PRIMARY KEY (`id`), 14 | KEY `FK_USR_ROLE` (`roleId`), 15 | CONSTRAINT `FK_USR_ROLE` FOREIGN KEY (`roleId`) REFERENCES `role` (`id`) ON DELETE SET NULL ON UPDATE SET NULL, 16 | constraint IDX_ae45785eved5adex96756e3166 unique (email) 17 | ); 18 | -------------------------------------------------------------------------------- /src/NotificationModule/notification.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Notification } from './entity/notification.entity'; 4 | import { NotificationRepository } from './repository/notification.repository'; 5 | import { NotificationService } from './service/notification.service'; 6 | import { NotificationController } from './controller/notification.controller'; 7 | import { PusherService } from './service/pusher.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | TypeOrmModule.forFeature([Notification, NotificationRepository]), 12 | HttpModule, 13 | ], 14 | controllers: [NotificationController], 15 | providers: [NotificationService, PusherService], 16 | exports: [NotificationService], 17 | }) 18 | export class NotificationModule {} 19 | -------------------------------------------------------------------------------- /src/migration/1608148654499-CourseTaken.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CourseTaken1608148654499 implements MigrationInterface { 4 | name = 'CourseTaken1608148654499'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | 'ALTER TABLE `course_taken` DROP COLUMN `challenge`', 9 | ); 10 | await queryRunner.query( 11 | 'ALTER TABLE `course_taken` ADD `challenge` varchar(500) NULL', 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | 'ALTER TABLE `course_taken` DROP COLUMN `challenge`', 18 | ); 19 | await queryRunner.query( 20 | 'ALTER TABLE `course_taken` ADD `challenge` varchar(255) NULL', 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/UserModule/swagger/new-user.swagger.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entity/user.entity'; 2 | import { UserProfileEnum } from '../enum/user-profile.enum'; 3 | import { GenderEnum } from '../enum/gender.enum'; 4 | import { EscolarityEnum } from '../enum/escolarity.enum'; 5 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 6 | 7 | export class NewUserSwagger { 8 | name: string; 9 | 10 | email: string; 11 | 12 | profile: UserProfileEnum; 13 | 14 | password: string; 15 | 16 | nickname?: string; 17 | 18 | birthday?: Date; 19 | 20 | gender?: GenderEnum; 21 | 22 | schooling?: EscolarityEnum; 23 | 24 | institutionName?: string; 25 | 26 | profession?: string; 27 | 28 | address?: string; 29 | 30 | urlFacebook?: User['urlFacebook']; 31 | 32 | urlInstagram?: User['urlInstagram']; 33 | 34 | role: RoleEnum; 35 | } 36 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | api: 5 | build: 6 | context: . 7 | args: 8 | - NODE_ENV=development 9 | volumes: 10 | # WARNING: If you're using Linux, you should create and change ownership of 11 | # current docker-compose folder to user node and group node 12 | - .:/opt/node_app/app:delegated 13 | - ./package.json:/opt/node_app/package.json 14 | - notused:/opt/node_app/app/node_modules 15 | ports: 16 | - 3000:8080 17 | - 9229:9229 18 | command: npm run start:debug 19 | depends_on: 20 | - db 21 | db: 22 | image: mysql:5.7.34 23 | ports: 24 | - 5432:5432 25 | environment: 26 | - MYSQL_USER=${DATABASE_USER} 27 | - MYSQL_PASSWORD=${DATABASE_PASSWORD} 28 | - MYSQL_NAME=${DATABASE_NAME} 29 | 30 | volumes: 31 | notused: 32 | -------------------------------------------------------------------------------- /src/CourseModule/mapper/comment.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Mapper } from '../../CommonsModule/mapper/mapper'; 3 | import { Comment } from '../entity/comment.entity'; 4 | import { CommentDTO } from '../dto/comment.dto'; 5 | 6 | @Injectable() 7 | export class CommentMapper extends Mapper { 8 | constructor() { 9 | super(Comment, CommentDTO); 10 | } 11 | 12 | toDto(entityObject: Partial): CommentDTO { 13 | return super.toDto(entityObject); 14 | } 15 | 16 | toDtoList(entityArray: Comment[]): CommentDTO[] { 17 | return super.toDtoList(entityArray); 18 | } 19 | 20 | toEntity(dtoObject: Partial): Comment { 21 | return super.toEntity(dtoObject); 22 | } 23 | 24 | toEntityList(dtoArray: CommentDTO[]): Comment[] { 25 | return super.toEntityList(dtoArray); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CourseModule/dto/response.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { UserDTO } from '../../UserModule/dto/user.dto'; 3 | 4 | class ClappedBy { 5 | @Type(() => UserDTO) 6 | user: UserDTO; 7 | 8 | claps: number; 9 | } 10 | 11 | class CommentInsideResponseDTO { 12 | id: string; 13 | 14 | text: string; 15 | 16 | user: UserDTO; 17 | 18 | @Type(() => ResponseDTO) 19 | responses: Omit[]; 20 | 21 | clappedByTotalCount: number; 22 | 23 | clappedBy: ClappedBy[]; 24 | } 25 | 26 | export class ResponseDTO { 27 | id: string; 28 | 29 | text: string; 30 | 31 | partId: number; 32 | 33 | @Type(() => CommentInsideResponseDTO) 34 | parentComment: CommentInsideResponseDTO; 35 | 36 | @Type(() => UserDTO) 37 | user: UserDTO; 38 | 39 | clappedByTotalCount: number; 40 | 41 | clappedBy: ClappedBy[]; 42 | } 43 | -------------------------------------------------------------------------------- /src/MessageModule/mapper/template.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Templates } from '../entity/templates.entity'; 3 | import { TemplateDTO } from '../dto/templates.dto'; 4 | import { Mapper } from '../../CommonsModule/mapper/mapper'; 5 | 6 | @Injectable() 7 | export class TemplateMapper extends Mapper { 8 | constructor() { 9 | super(Templates, TemplateDTO); 10 | } 11 | 12 | toDto(entityObject: Templates): TemplateDTO { 13 | return super.toDto(entityObject); 14 | } 15 | 16 | toDtoList(entityArray: Templates[]): TemplateDTO[] { 17 | return super.toDtoList(entityArray); 18 | } 19 | 20 | toEntity(dtoObject: TemplateDTO): Templates { 21 | return super.toEntity(dtoObject); 22 | } 23 | 24 | toEntityList(dtoArray: TemplateDTO[]): Templates[] { 25 | return super.toEntityList(dtoArray); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CourseModule/entity/user-liked-comment.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, JoinTable, ManyToOne } from 'typeorm'; 2 | import { User } from '../../UserModule/entity/user.entity'; 3 | import { Comment } from './comment.entity'; 4 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 5 | 6 | @Entity() 7 | export class UserLikedComment extends Audit { 8 | @ManyToOne(() => User, (user: User) => user.likedComments, { 9 | primary: true, 10 | }) 11 | @JoinTable({ name: 'userId' }) 12 | user: User; 13 | 14 | @Column({ primary: true }) 15 | userId: string; 16 | 17 | @ManyToOne(() => Comment, (comment: Comment) => comment.likedBy, { 18 | primary: true, 19 | }) 20 | @JoinTable({ name: 'commentId' }) 21 | comment: Comment; 22 | 23 | @Column({ primary: true }) 24 | commentId: string; 25 | 26 | @Column({ default: 0 }) 27 | claps: number; 28 | } 29 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/pilar.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CmsIntegration } from '../../integration/cms.integration'; 3 | import { CMSPilarDTO } from '../../dto/cms-pilar.dto'; 4 | 5 | @Injectable() 6 | export class PilarService { 7 | constructor(private readonly cmsIntegration: CmsIntegration) {} 8 | 9 | public async getAll(): Promise { 10 | const { data: pilars } = await this.cmsIntegration.getPilars({ 11 | _sort: 'published_at:desc', 12 | }); 13 | 14 | pilars.forEach(function (pilar){ 15 | pilar.cursos = pilar.cursos.sort((a, b) => (a. published_at > b.published_at ? -1 : 1)); 16 | }); 17 | 18 | return pilars; 19 | } 20 | 21 | public async findById(id: number): Promise { 22 | const { data: pilar } = await this.cmsIntegration.findPilarById(id); 23 | return pilar; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/migration/1607548287894-Notification.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class Notification1607548287894 implements MigrationInterface { 4 | name = 'Notification1607548287894'; 5 | 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query( 8 | 'ALTER TABLE `notification` ADD `important` tinyint NOT NULL DEFAULT 0', 9 | ); 10 | await queryRunner.query( 11 | "ALTER TABLE `notification` CHANGE `type` `type` enum ('GAMEFICATION', 'OTHER') NOT NULL", 12 | ); 13 | } 14 | 15 | public async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query( 17 | "ALTER TABLE `notification` CHANGE `type` `type` enum ('GAMEFICATION') NOT NULL", 18 | ); 19 | await queryRunner.query( 20 | 'ALTER TABLE `notification` DROP COLUMN `important`', 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CourseModule/mapper/course-taken.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CourseTaken } from '../entity/course-taken.entity'; 3 | import { Mapper } from '../../CommonsModule/mapper/mapper'; 4 | import { CourseTakenDTO } from '../dto/course-taken.dto'; 5 | 6 | @Injectable() 7 | export class CourseTakenMapper extends Mapper { 8 | constructor() { 9 | super(CourseTaken, CourseTakenDTO); 10 | } 11 | 12 | toDto(entityObject: Partial): CourseTakenDTO { 13 | return super.toDto(entityObject); 14 | } 15 | 16 | toDtoList(entityArray: CourseTaken[]): CourseTakenDTO[] { 17 | return super.toDtoList(entityArray); 18 | } 19 | 20 | toEntity(dtoObject: Partial): CourseTaken { 21 | return super.toEntity(dtoObject); 22 | } 23 | 24 | toEntityList(dtoArray: CourseTakenDTO[]): CourseTaken[] { 25 | return super.toEntityList(dtoArray); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/highlight.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CmsIntegration } from '../../integration/cms.integration'; 3 | import { CMSHighlightDTO } from '../../dto/cms-highlight.dto'; 4 | 5 | @Injectable() 6 | export class HighlightService { 7 | constructor(private readonly cmsIntegration: CmsIntegration) {} 8 | 9 | public async getAll(): Promise { 10 | const { data: highlights } = await this.cmsIntegration.getHighlights({ 11 | _sort: 'published_at:desc', 12 | }); 13 | 14 | highlights.forEach(function (highlight){ 15 | highlight.cursos = highlight.cursos.sort((a, b) => (a. published_at > b.published_at ? -1 : 1)); 16 | }); 17 | 18 | return highlights; 19 | } 20 | 21 | public async findById(id: number): Promise { 22 | const { data: pilar } = await this.cmsIntegration.findHighlightById(id); 23 | return pilar; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/CourseModule/repository/comment.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Comment } from '../entity/comment.entity'; 3 | 4 | @EntityRepository(Comment) 5 | export class CommentRepository extends Repository { 6 | public getCommentIds(partId, { order, orderBy }) { 7 | const orderByTranslation = { 8 | claps: 'totalClaps', 9 | createdAt: 'c.createdAt', 10 | }; 11 | return this.query( 12 | ` 13 | SELECT 14 | id, 15 | SUM(ulc.claps) as totalClaps 16 | FROM 17 | comment c 18 | LEFT JOIN 19 | user_liked_comment ulc 20 | ON 21 | ulc.commentId = c.id 22 | WHERE 23 | c.parentCommentId IS NULL 24 | AND 25 | c.partId = ${partId} 26 | GROUP BY 27 | c.id 28 | ORDER BY ${orderByTranslation[orderBy]} ${order} 29 | `, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/jest-e2e.config.js: -------------------------------------------------------------------------------- 1 | process.env.DATABASE_HOST = process.env.DATABASE_HOST || 'mysql.newschoolapp.com.br'; 2 | process.env.DATABASE_NAME = process.env.DATABASE_NAME || 'newschool_tests'; 3 | process.env.DATABASE_USERNAME = process.env.DATABASE_USERNAME || 'newschool_tests'; 4 | process.env.DATABASE_PASSWORD = process.env.DATABASE_PASSWORD || '2(M@2!E&{G@:'; 5 | process.env.DATABASE_PORT = 3306; 6 | process.env.SYNC_DATABASE = true; 7 | process.env.JWT_SECRET = 'secret'; 8 | process.env.JWT_SECRET = 'TEST'; 9 | process.env.NODE_ENV = 'TEST' 10 | process.env.EXPIRES_IN_ACCESS_TOKEN = '1h'; 11 | process.env.EXPIRES_IN_REFRESH_TOKEN = '2h'; 12 | process.env.CHANGE_PASSWORD_EXPIRATION_TIME = 100000000; 13 | 14 | module.exports = { 15 | moduleFileExtensions: ['js', 'json', 'ts'], 16 | rootDir: '.', 17 | testEnvironment: 'node', 18 | testRegex: '.e2e-spec.ts$', 19 | transform: { 20 | '^.+\\.(t|j)s$': 'ts-jest', 21 | }, 22 | setupFilesAfterEnv: ['./jest.setup.ts'], 23 | }; 24 | -------------------------------------------------------------------------------- /src/UserModule/dto/new-student.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entity/user.entity'; 2 | import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; 3 | import { Expose } from 'class-transformer'; 4 | import { UserProfileEnum } from '../enum/user-profile.enum'; 5 | 6 | export class NewStudentDTO { 7 | @IsNotEmpty() 8 | @IsString() 9 | @Expose() 10 | name: User['name']; 11 | 12 | @IsNotEmpty() 13 | @IsString() 14 | @Expose() 15 | email: User['email']; 16 | 17 | @IsNotEmpty() 18 | @IsEnum(UserProfileEnum) 19 | @Expose() 20 | profile: User['profile']; 21 | 22 | @IsNotEmpty() 23 | @IsString() 24 | @Expose() 25 | password: User['password']; 26 | 27 | @IsOptional() 28 | @IsString() 29 | @Expose() 30 | institutionName?: string; 31 | 32 | @IsOptional() 33 | @IsString() 34 | @Expose() 35 | urlFacebook?: User['urlFacebook']; 36 | 37 | @IsOptional() 38 | @IsString() 39 | @Expose() 40 | urlInstagram?: User['urlInstagram']; 41 | } 42 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/trail.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CmsService } from './cms.service'; 3 | import { CmsIntegration } from '../../integration/cms.integration'; 4 | import { CMSTrailDTO } from '../../dto/cms-trail.dto'; 5 | 6 | @Injectable() 7 | export class TrailService { 8 | constructor( 9 | private readonly cmsService: CmsService, 10 | private readonly cmsIntegration: CmsIntegration, 11 | ) {} 12 | 13 | public async getAll(): Promise { 14 | const { data: trails } = await this.cmsIntegration.getTrails({ 15 | _sort: 'published_at:desc', 16 | }); 17 | 18 | trails.forEach(function (trail){ 19 | trail.cursos = trail.cursos.sort((a, b) => (a. published_at > b.published_at ? -1 : 1)); 20 | }); 21 | 22 | return trails; 23 | } 24 | 25 | public async findById(id: number): Promise { 26 | const { data: pilar } = await this.cmsIntegration.findTrailById(id); 27 | return pilar; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # AMBIENTE DE DEV 2 | 3 | DATABASE_HOST=newschool-back-dev-2.cdp2xk8f9nzx.us-east-2.rds.amazonaws.com 4 | DATABASE_NAME=newschoolbackdev 5 | DATABASE_USERNAME=admin 6 | DATABASE_PASSWORD=newschoolbackdev 7 | DATABASE_PORT=3306 8 | NODE_ENV=development 9 | JWT_SECRET=secret 10 | REFRESH_TOKEN_SECRET=another_secret 11 | EXPIRES_IN_ACCESS_TOKEN=1h 12 | EXPIRES_IN_REFRESH_TOKEN=2h 13 | CHANGE_PASSWORD_EXPIRATION_TIME=100000000 14 | FRONT_URL=https://newschoolbr-dev.herokuapp.com 15 | CHANGE_PASSWORD_URL=/troca-senha 16 | PORT=8080 17 | EMAIL_CONTACTUS=newschoolcontato@gmail.com 18 | 19 | SMTP_HOST="vps-3769045.newschoolapp.com.br" 20 | SMTP_PORT=587 21 | SMTP_SECURE= 22 | SMTP_REQUIRE_TLS=true 23 | SMTP_USER=no-reply-dev@newschoolapp.com.br 24 | SMTP_PASSWORD=D(^o5R|l*-H7 25 | SMTP_FROM=no-reply-dev@newschoolapp.com.br 26 | 27 | SENTRY_URL=https://fd1aea93e15c4692b1d827e31850a3d0@o468568.ingest.sentry.io/5496578 28 | 29 | PUSHER_APP_ID = 1100634 30 | PUSHER_KEY = 641d7105d0e874ba5ae7 31 | PUSHER_SECRET = bfda7197ac5e5aa256f1 32 | PUSHER_CLUSTER = us2 33 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/course-v2.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; 2 | import { CmsIntegration } from '../../integration/cms.integration'; 3 | import { CMSCourseDTO } from '../../dto/cms-course.dto'; 4 | 5 | @Injectable() 6 | export class CourseV2Service { 7 | constructor(private readonly cmsIntegration: CmsIntegration) {} 8 | 9 | public async getAll(): Promise { 10 | const { data } = await this.cmsIntegration.getCourses(); 11 | return data; 12 | } 13 | 14 | public async findById(id: number): Promise { 15 | const errors = { 16 | 404: () => { 17 | throw new NotFoundException('Course not found'); 18 | }, 19 | }; 20 | 21 | try { 22 | const { data } = await this.cmsIntegration.findCourseById(id); 23 | return data; 24 | } catch (e) { 25 | const error = errors[e.response.status]; 26 | if (!error) throw new InternalServerErrorException(); 27 | error(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NotificationModule/subscriber/notification.subscriber.ts: -------------------------------------------------------------------------------- 1 | import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm'; 2 | import { Notification } from '../entity/notification.entity'; 3 | import { InjectConnection } from '@nestjs/typeorm'; 4 | import { PusherService } from '../service/pusher.service'; 5 | 6 | @EventSubscriber() 7 | export class NotificationSubscriber 8 | implements EntitySubscriberInterface { 9 | constructor( 10 | @InjectConnection() readonly connection: Connection, 11 | private readonly pusherService: PusherService, 12 | ) { 13 | connection.subscribers.push(this); 14 | } 15 | 16 | listenTo(): typeof Notification { 17 | return Notification; 18 | } 19 | 20 | async afterInsert(event: InsertEvent): Promise { 21 | this.pusherService.postMessageToUser(event.entity); 22 | } 23 | 24 | async afterUpdate(event: UpdateEvent): Promise { 25 | this.pusherService.postMessageToUser(event.entity); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/GameficationModule/entity/achievement.entity.ts: -------------------------------------------------------------------------------- 1 | import { Expose } from 'class-transformer'; 2 | import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 3 | import { Badge } from './badge.entity'; 4 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 5 | import { User } from '../../UserModule/entity/user.entity'; 6 | import { EventNameEnum } from '../enum/event-name.enum'; 7 | 8 | @Entity() 9 | export class Achievement extends Audit { 10 | @PrimaryGeneratedColumn('uuid') 11 | @Expose() 12 | id: string; 13 | 14 | @Column({ 15 | type: 'enum', 16 | enum: EventNameEnum, 17 | }) 18 | eventName: EventNameEnum; 19 | 20 | @Column({ type: 'json' }) 21 | rule: T; 22 | 23 | @Column() 24 | completed: boolean; 25 | 26 | @Column({ nullable: false }) 27 | points: number; 28 | 29 | @ManyToOne(() => Badge, (badge: Badge) => badge.achievements) 30 | @Expose() 31 | badge: Badge; 32 | 33 | @ManyToOne(() => User, (user: User) => user.achievements) 34 | @Expose() 35 | user: User; 36 | } 37 | -------------------------------------------------------------------------------- /src/CommonsModule/dto/user-jwt.dto.ts: -------------------------------------------------------------------------------- 1 | import { Expose, Type } from 'class-transformer'; 2 | import { IsBoolean, IsNotEmpty, IsString } from 'class-validator'; 3 | 4 | export class UserJWTDTO { 5 | @IsNotEmpty() 6 | @IsString() 7 | @Expose() 8 | id: string; 9 | 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | name: string; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | username: string; 19 | 20 | @IsNotEmpty() 21 | @IsBoolean() 22 | @Expose() 23 | enabled: boolean; 24 | 25 | @Expose() 26 | @IsNotEmpty() 27 | role: RoleJWTDTO; 28 | 29 | @IsString() 30 | @Expose() 31 | phone?: string; 32 | 33 | @IsString() 34 | @Expose() 35 | email?: string; 36 | } 37 | 38 | export class RoleJWTDTO { 39 | @Expose() 40 | id: string; 41 | 42 | @Expose() 43 | name: string; 44 | 45 | @Type(() => PolicyJWTDTO) 46 | @IsNotEmpty() 47 | @Expose() 48 | policies: PolicyJWTDTO[]; 49 | } 50 | 51 | export class PolicyJWTDTO { 52 | @Expose() 53 | id: string; 54 | @Expose() 55 | name: string; 56 | } 57 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-part.dto.ts: -------------------------------------------------------------------------------- 1 | export class CMSPartDTO { 2 | id: number; 3 | titulo: string; 4 | descricao: string; 5 | ordem: number; 6 | 'published_at': Date; 7 | 'created_at': Date; 8 | 'updated_at': Date; 9 | aula: Aula; 10 | video: string; 11 | videoUrl: string; 12 | exercicios: Exercicio[]; 13 | } 14 | 15 | export class Aula { 16 | id: number; 17 | Titulo: string; 18 | 'published_at': Date; 19 | 'created_at': Date; 20 | 'updated_at': Date; 21 | title: string; 22 | description: string; 23 | 'seq_num': number; 24 | curso: number; 25 | ordem: number; 26 | titulo: string; 27 | descricao: string; 28 | } 29 | 30 | export class Exercicio { 31 | id: number; 32 | titulo: string; 33 | pergunta: string; 34 | 'primeira_alternativa': string; 35 | 'segunda_alternativa': string; 36 | 'terceira_alternativa': string; 37 | 'quarta_alternativa': string; 38 | 'alternativa_certa': string; 39 | 'published_at': Date; 40 | 'created_at': Date; 41 | 'updated_at': Date; 42 | ordem: number; 43 | parte: number; 44 | } 45 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/part-v2.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; 2 | import { CmsIntegration } from '../../integration/cms.integration'; 3 | import { CMSPartDTO } from '../../dto/cms-part.dto'; 4 | 5 | @Injectable() 6 | export class PartV2Service { 7 | constructor(private readonly cmsIntegration: CmsIntegration) {} 8 | 9 | public async getAll(lessonId: number): Promise { 10 | const { data } = await this.cmsIntegration.getPartsByLessonId(lessonId); 11 | return data; 12 | } 13 | 14 | public async findById(id: number): Promise { 15 | const errors = { 16 | 404: () => { 17 | throw new NotFoundException('Course not found'); 18 | }, 19 | }; 20 | 21 | try { 22 | const { data } = await this.cmsIntegration.findPartById(id); 23 | return data; 24 | } catch (e) { 25 | const error = errors[e.response.status]; 26 | if (!error) throw new InternalServerErrorException(); 27 | error(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NotificationModule/entity/notification.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { NotificationTypeEnum } from '../enum/notification-type.enum'; 3 | import { User } from '../../UserModule/entity/user.entity'; 4 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 5 | 6 | @Entity() 7 | export class Notification extends Audit { 8 | @PrimaryGeneratedColumn('uuid') 9 | id: string; 10 | 11 | @ManyToOne(() => User, (user: User) => user.notifications) 12 | user: User; 13 | 14 | @Column({ 15 | default: false, 16 | }) 17 | seen: boolean = false; 18 | 19 | @Column({ 20 | default: true, 21 | }) 22 | enabled: boolean = true; 23 | 24 | @Column({ 25 | default: false, 26 | }) 27 | important: boolean = false; 28 | 29 | @Column({ 30 | type: 'enum', 31 | enum: NotificationTypeEnum, 32 | nullable: false, 33 | }) 34 | type: NotificationTypeEnum; 35 | 36 | @Column({ 37 | type: 'json', 38 | nullable: false, 39 | }) 40 | content: T; 41 | } 42 | -------------------------------------------------------------------------------- /src/CommonsModule/constants.ts: -------------------------------------------------------------------------------- 1 | export class Constants { 2 | static API_PREFIX = 'api'; 3 | static API_VERSION_1 = 'v1'; 4 | static API_VERSION_2 = 'v2'; 5 | static OAUTH_ENDPOINT = 'oauth'; 6 | 7 | static USER_ENDPOINT = 'user'; 8 | 9 | static COURSE_TAKEN_ENDPOINT = 'course-taken'; 10 | 11 | static DASHBOARD_ENDPOINT = 'dashboard'; 12 | 13 | static COURSE_ENDPOINT = 'course'; 14 | static COMMENT_ENDPOINT = 'comment'; 15 | static LESSON_ENDPOINT = 'lesson'; 16 | static PART_ENDPOINT = 'part'; 17 | static TEST_ENDPOINT = 'test'; 18 | static SCHOOL_ENDPOINT = 'school'; 19 | static GAMEFICATION_ENDPOINT = 'gamefication'; 20 | static PILAR_ENDPOINT = 'pilar'; 21 | static TRAIL_ENDPOINT = 'trail'; 22 | static HIGHLIGHT_ENDPOINT = 'highlight'; 23 | 24 | static UPLOAD_ENDPOINT = 'upload'; 25 | 26 | static MESSAGE_ENDPOINT = 'message'; 27 | 28 | static NOTIFICATION_ENDPOINT = 'notification'; 29 | static CITY_ENDPOINT = 'city'; 30 | static STATE_ENDPOINT = 'state'; 31 | 32 | static POLICIES_PREFIX = '@EDUCATION_PLATFORM/COURSE_MANAGEMENT'; 33 | } 34 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/lesson-v2.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; 2 | import { CmsIntegration } from '../../integration/cms.integration'; 3 | import { CMSLessonDTO } from '../../dto/cms-lesson.dto'; 4 | 5 | @Injectable() 6 | export class LessonV2Service { 7 | constructor(private readonly cmsIntegration: CmsIntegration) {} 8 | 9 | public async getAll(courseId: number): Promise { 10 | const { data } = await this.cmsIntegration.getLessonsByCourseId(courseId); 11 | return data; 12 | } 13 | 14 | public async findById(id: number): Promise { 15 | const errors = { 16 | 404: () => { 17 | throw new NotFoundException('Course not found'); 18 | }, 19 | }; 20 | 21 | try { 22 | const { data } = await this.cmsIntegration.findLessonById(id); 23 | return data; 24 | } catch (e) { 25 | const error = errors[e.response.status]; 26 | if (!error) throw new InternalServerErrorException(); 27 | error(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NotificationModule/repository/notification.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Notification } from '../entity/notification.entity'; 3 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 4 | import { NotificationTypeEnum } from '../enum/notification-type.enum'; 5 | 6 | @EntityRepository(Notification) 7 | export class NotificationRepository extends Repository { 8 | public async getNotificationsByUserId( 9 | userId: string, 10 | order: OrderEnum, 11 | enabled: boolean, 12 | seen: boolean, 13 | ): Promise { 14 | return this.find({ 15 | where: { user: { id: userId }, enabled, seen }, 16 | order: { createdAt: order }, 17 | }); 18 | } 19 | 20 | public async getOtherNotificationsByUserId( 21 | userId: string, 22 | ): Promise { 23 | return this.find({ 24 | where: { 25 | user: { id: userId }, 26 | enabled: true, 27 | seen: false, 28 | type: NotificationTypeEnum.OTHER, 29 | }, 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CommonsModule/mapper/mapper.ts: -------------------------------------------------------------------------------- 1 | import { plainToClass } from 'class-transformer'; 2 | 3 | export class Mapper { 4 | constructor( 5 | private entityClass: new () => E, 6 | private dtoClass: new () => D, 7 | ) {} 8 | 9 | toDto(entityObject: Partial): D { 10 | return plainToClass>(this.dtoClass, entityObject, { 11 | excludeExtraneousValues: true, 12 | }); 13 | } 14 | 15 | async toDtoAsync(entityObject: Partial): Promise { 16 | return plainToClass>(this.dtoClass, entityObject, { 17 | excludeExtraneousValues: true, 18 | }); 19 | } 20 | 21 | toDtoList(entityArray: E[]): D[] { 22 | return plainToClass(this.dtoClass, entityArray, { 23 | excludeExtraneousValues: true, 24 | }); 25 | } 26 | 27 | toEntity(dtoObject: Partial): E { 28 | return plainToClass>(this.entityClass, dtoObject, { 29 | excludeExtraneousValues: true, 30 | }); 31 | } 32 | 33 | toEntityList(dtoArray: D[]): E[] { 34 | return plainToClass(this.entityClass, dtoArray, { 35 | excludeExtraneousValues: true, 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SecurityModule/entity/role.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { ClientCredentials } from './client-credentials.entity'; 3 | import { User } from '../../UserModule/entity/user.entity'; 4 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 5 | import { Policy } from './policy.entity'; 6 | import slugify from 'slugify'; 7 | 8 | @Entity() 9 | export class Role extends Audit { 10 | @PrimaryGeneratedColumn('uuid') 11 | id: string; 12 | 13 | @Column() 14 | name: string; 15 | 16 | @Column({ 17 | nullable: false, 18 | unique: true, 19 | }) 20 | get slug(): string { 21 | return slugify(this.name); 22 | } 23 | 24 | set slug(name: string) {} 25 | 26 | @ManyToMany(() => Policy, (policy) => policy.roles) 27 | @JoinTable() 28 | policies: Policy[]; 29 | 30 | @OneToMany( 31 | () => ClientCredentials, 32 | (clientCredentials: ClientCredentials) => clientCredentials.role, 33 | ) 34 | clientCredentials: ClientCredentials[]; 35 | 36 | @OneToMany(() => User, (user: User) => user.role) 37 | users: User[]; 38 | } 39 | -------------------------------------------------------------------------------- /src/UserModule/mapper/user.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { UserDTO } from '../dto/user.dto'; 3 | import { User } from '../entity/user.entity'; 4 | import { Mapper } from '../../CommonsModule/mapper/mapper'; 5 | import { UploadService } from '../../UploadModule/service/upload.service'; 6 | 7 | @Injectable() 8 | export class UserMapper extends Mapper { 9 | constructor(private readonly uploadService: UploadService) { 10 | super(User, UserDTO); 11 | } 12 | 13 | async toDtoAsync(entityObject: User): Promise { 14 | const user = super.toDto(entityObject); 15 | return { 16 | ...user, 17 | photo: entityObject.photoPath 18 | ? await this.uploadService.getUserPhoto(entityObject.photoPath) 19 | : null, 20 | }; 21 | } 22 | 23 | toDto(entityObject: User): UserDTO { 24 | return super.toDto(entityObject); 25 | } 26 | 27 | toDtoList(entityArray: User[]): UserDTO[] { 28 | return super.toDtoList(entityArray); 29 | } 30 | 31 | toEntity(dtoObject: UserDTO): User { 32 | return super.toEntity(dtoObject); 33 | } 34 | 35 | toEntityList(dtoArray: UserDTO[]): User[] { 36 | return super.toEntityList(dtoArray); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /GUIDELINES.md: -------------------------------------------------------------------------------- 1 | #GUIDELINES 2 | 3 | 4 | ## Estrutura do projeto 5 | O projeto é dividido em módulos, cada módulo sendo responsável por uma parte 6 | específica do projeto. Para ser um módulo, ele precisa: 7 | 8 | - Ser responsável por diversas features que são parecidas entre si 9 | - Pode trabalhar de forma isolada ou tem pouca integração entre outros módulos 10 | - Ele poderia se tornar em um serviço separado 11 | 12 | Dentro de cada módulo, existe pastas para cada responsabilidade: 13 | - Controller 14 | - Expõe endpoints. Além disso, são documentados via Swagger 15 | - Service 16 | - Possuem a lógica de negócio. São eles que se ligam aos 17 | repositories para acesso ao banco 18 | - Repository 19 | - Classes de acesso ao banco de dados 20 | - Entity 21 | - Classes que mapeam o banco de dados 22 | - DTO 23 | - Classes de transferência de dados entre o back e o front 24 | - Mapper 25 | - Classe para transformar de Entity em DTO, e DTO em Entity 26 | - Swagger 27 | - Classes para documentação 28 | 29 | > Porque essa estrutura? 30 | 31 | Projetos em TypeScript tendem a seguir assim, o próprio NestJS 32 | usa essa estrutura para seu código fonte, então consideramos que seja 33 | uma estrutura forte o suficiente para que a comunidade possa seguir. 34 | -------------------------------------------------------------------------------- /src/UserModule/controller/state.controller.ts: -------------------------------------------------------------------------------- 1 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 2 | import { CacheInterceptor, CacheTTL, Controller, Get, UseGuards, UseInterceptors } from '@nestjs/common'; 3 | import { Constants } from '../../CommonsModule/constants'; 4 | import { NeedPolicies, NeedRoles } from '../../CommonsModule/decorator/role-guard-metadata.decorator'; 5 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 6 | import { RoleGuard } from '../../CommonsModule/guard/role.guard'; 7 | import { StateService } from '../service/state.service'; 8 | import { StateDTO } from '../dto/state.dto'; 9 | 10 | const secondsInADay = 86400; 11 | 12 | @ApiTags('State') 13 | @ApiBearerAuth() 14 | @Controller( 15 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.STATE_ENDPOINT}`, 16 | ) 17 | export class StateController { 18 | constructor(private service: StateService) {} 19 | 20 | @Get() 21 | @NeedRoles(RoleEnum.ADMIN, RoleEnum.STUDENT, RoleEnum.EXTERNAL) 22 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_STATES`) 23 | @UseGuards(RoleGuard) 24 | @UseInterceptors(CacheInterceptor) 25 | @CacheTTL(secondsInADay) 26 | public async getStates(): Promise { 27 | return await this.service.getStates(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/GameficationModule/entity/badge.entity.ts: -------------------------------------------------------------------------------- 1 | import { Achievement } from './achievement.entity'; 2 | import slugify from 'slugify'; 3 | import { Expose } from 'class-transformer'; 4 | import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 5 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 6 | import { EventNameEnum } from '../enum/event-name.enum'; 7 | 8 | @Entity() 9 | export class Badge extends Audit { 10 | @PrimaryGeneratedColumn('uuid') 11 | @Expose() 12 | id: string; 13 | 14 | @Column({ 15 | type: 'enum', 16 | enum: EventNameEnum, 17 | }) 18 | eventName: EventNameEnum; 19 | 20 | @Column() 21 | eventOrder: number; 22 | 23 | @Column() 24 | badgeName: string; 25 | 26 | @Column() 27 | badgeDescription: string; 28 | 29 | @Column() 30 | points: number; 31 | 32 | private _slug: string; 33 | 34 | @Column({ name: 'slug' }) 35 | get slug(): string { 36 | return slugify(this.badgeName, { replacement: '-', lower: true }); 37 | } 38 | 39 | set slug(slug: string) { 40 | this._slug = slugify(this.badgeName, { replacement: '-', lower: true }); 41 | } 42 | 43 | @OneToMany(() => Achievement, (achievement: Achievement) => achievement.badge) 44 | @Expose() 45 | achievements: Achievement[]; 46 | } 47 | -------------------------------------------------------------------------------- /src/UserModule/controller/city.controller.ts: -------------------------------------------------------------------------------- 1 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 2 | import { CacheInterceptor, CacheTTL, Controller, Get, Param, UseGuards, UseInterceptors } from '@nestjs/common'; 3 | import { Constants } from '../../CommonsModule/constants'; 4 | import { CityService } from '../service/city.service'; 5 | import { CityDTO } from '../dto/city.dto'; 6 | import { NeedPolicies, NeedRoles } from '../../CommonsModule/decorator/role-guard-metadata.decorator'; 7 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 8 | import { RoleGuard } from '../../CommonsModule/guard/role.guard'; 9 | 10 | const secondsInADay = 86400; 11 | 12 | @ApiTags('City') 13 | @ApiBearerAuth() 14 | @Controller( 15 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.CITY_ENDPOINT}`, 16 | ) 17 | export class CityController { 18 | constructor(private service: CityService) {} 19 | 20 | @Get('/:uf') 21 | @UseGuards(RoleGuard) 22 | @NeedRoles(RoleEnum.ADMIN, RoleEnum.STUDENT, RoleEnum.EXTERNAL) 23 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_CITIES_BY_UF`) 24 | @UseInterceptors(CacheInterceptor) 25 | @CacheTTL(secondsInADay) 26 | public async getCitiesByUf(@Param('uf') uf: string): Promise { 27 | return await this.service.getCitiesByUf(uf); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-pilar.dto.ts: -------------------------------------------------------------------------------- 1 | export interface CMSPilarDTO { 2 | id: number; 3 | titulo: string; 4 | published_at: Date; 5 | created_at: Date; 6 | updated_at: Date; 7 | cursos: Curso[]; 8 | } 9 | 10 | export interface Curso { 11 | id: number; 12 | descricao: string; 13 | published_at: Date; 14 | created_at: Date; 15 | updated_at: Date; 16 | titulo: string; 17 | nomeDoAutor: string; 18 | descricaoDoAutor: string; 19 | horas: number; 20 | slug: string; 21 | pilar: number; 22 | capa: Capa; 23 | } 24 | 25 | export interface Capa { 26 | id: number; 27 | name: string; 28 | alternativeText: string; 29 | caption: string; 30 | width: number; 31 | height: number; 32 | formats: Formats; 33 | hash: string; 34 | ext: string; 35 | mime: string; 36 | size: number; 37 | url: string; 38 | previewUrl: null; 39 | provider: string; 40 | provider_metadata: null; 41 | created_at: Date; 42 | updated_at: Date; 43 | } 44 | 45 | export interface Formats { 46 | small: Large; 47 | medium?: Large; 48 | thumbnail: Large; 49 | large?: Large; 50 | } 51 | 52 | export interface Large { 53 | ext: string; 54 | url: string; 55 | hash: string; 56 | mime: string; 57 | name: string; 58 | path: null; 59 | size: number; 60 | width: number; 61 | height: number; 62 | } 63 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-trail.dto.ts: -------------------------------------------------------------------------------- 1 | export interface CMSTrailDTO { 2 | id: number; 3 | titulo: string; 4 | published_at: Date; 5 | created_at: Date; 6 | updated_at: Date; 7 | cursos: Curso[]; 8 | } 9 | 10 | export interface Curso { 11 | id: number; 12 | descricao: string; 13 | published_at: Date; 14 | created_at: Date; 15 | updated_at: Date; 16 | titulo: string; 17 | nomeDoAutor: string; 18 | descricaoDoAutor: string; 19 | horas: number; 20 | slug: string; 21 | pilar: number; 22 | capa: Capa; 23 | } 24 | 25 | export interface Capa { 26 | id: number; 27 | name: string; 28 | alternativeText: string; 29 | caption: string; 30 | width: number; 31 | height: number; 32 | formats: Formats; 33 | hash: string; 34 | ext: string; 35 | mime: string; 36 | size: number; 37 | url: string; 38 | previewUrl: null; 39 | provider: string; 40 | provider_metadata: null; 41 | created_at: Date; 42 | updated_at: Date; 43 | } 44 | 45 | export interface Formats { 46 | small: Large; 47 | medium?: Large; 48 | thumbnail: Large; 49 | large?: Large; 50 | } 51 | 52 | export interface Large { 53 | ext: string; 54 | url: string; 55 | hash: string; 56 | mime: string; 57 | name: string; 58 | path: null; 59 | size: number; 60 | width: number; 61 | height: number; 62 | } 63 | -------------------------------------------------------------------------------- /src/UserModule/controller/school.controller.ts: -------------------------------------------------------------------------------- 1 | import { SchoolService } from '../service/school.service'; 2 | import { CacheInterceptor, CacheTTL, Controller, Get, Query, UseGuards, UseInterceptors } from '@nestjs/common'; 3 | import { NeedPolicies, NeedRoles } from '../../CommonsModule/decorator/role-guard-metadata.decorator'; 4 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 5 | import { RoleGuard } from '../../CommonsModule/guard/role.guard'; 6 | import { Constants } from '../../CommonsModule/constants'; 7 | import { School } from '../dto/school.dto'; 8 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 9 | 10 | const secondsInADay = 86400; 11 | 12 | @ApiTags('School') 13 | @ApiBearerAuth() 14 | @Controller( 15 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.SCHOOL_ENDPOINT}`, 16 | ) 17 | export class SchoolController { 18 | constructor(private service: SchoolService) {} 19 | 20 | @Get() 21 | @UseGuards(RoleGuard) 22 | @NeedRoles(RoleEnum.ADMIN, RoleEnum.STUDENT, RoleEnum.EXTERNAL) 23 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_CITIES_BY_UF`) 24 | @UseInterceptors(CacheInterceptor) 25 | @CacheTTL(secondsInADay) 26 | public async getSchool(@Query('name') name = ''): Promise { 27 | return await this.service.getUserSchool(name); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-highlight.dto.ts: -------------------------------------------------------------------------------- 1 | export interface CMSHighlightDTO { 2 | id: number; 3 | titulo: string; 4 | published_at: Date; 5 | created_at: Date; 6 | updated_at: Date; 7 | cursos: Curso[]; 8 | } 9 | 10 | export interface Curso { 11 | id: number; 12 | descricao: string; 13 | published_at: Date; 14 | created_at: Date; 15 | updated_at: Date; 16 | titulo: string; 17 | nomeDoAutor: string; 18 | descricaoDoAutor: string; 19 | horas: number; 20 | slug: string; 21 | pilar: number; 22 | capa: Capa; 23 | } 24 | 25 | export interface Capa { 26 | id: number; 27 | name: string; 28 | alternativeText: string; 29 | caption: string; 30 | width: number; 31 | height: number; 32 | formats: Formats; 33 | hash: string; 34 | ext: string; 35 | mime: string; 36 | size: number; 37 | url: string; 38 | previewUrl: null; 39 | provider: string; 40 | provider_metadata: null; 41 | created_at: Date; 42 | updated_at: Date; 43 | } 44 | 45 | export interface Formats { 46 | small: Large; 47 | medium?: Large; 48 | thumbnail: Large; 49 | large?: Large; 50 | } 51 | 52 | export interface Large { 53 | ext: string; 54 | url: string; 55 | hash: string; 56 | mime: string; 57 | name: string; 58 | path: null; 59 | size: number; 60 | width: number; 61 | height: number; 62 | } 63 | -------------------------------------------------------------------------------- /src/UserModule/service/change-password.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { ChangePasswordRepository } from '../repository/change-password.repository'; 3 | import { AppConfigService as ConfigService } from '../../ConfigModule/service/app-config.service'; 4 | import { ChangePassword } from '../entity/change-password.entity'; 5 | import { User } from '../entity/user.entity'; 6 | 7 | @Injectable() 8 | export class ChangePasswordService { 9 | constructor( 10 | private readonly repository: ChangePasswordRepository, 11 | private readonly configService: ConfigService, 12 | ) {} 13 | 14 | public async createChangePasswordRequest( 15 | user: User, 16 | ): Promise { 17 | const changePassword: ChangePassword = new ChangePassword(); 18 | changePassword.user = user; 19 | changePassword.expirationTime = this.configService.changePasswordExpirationTime; 20 | return this.repository.save(changePassword); 21 | } 22 | 23 | public async findById(id: string): Promise { 24 | const changePassword: ChangePassword = await this.repository.findOne( 25 | { id }, 26 | { relations: ['user'] }, 27 | ); 28 | if (!changePassword) { 29 | throw new NotFoundException(); 30 | } 31 | return changePassword; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/MessageModule/dto/templates.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsString } from 'class-validator'; 3 | import { Expose } from 'class-transformer'; 4 | 5 | export class TemplateDTO { 6 | @ApiProperty({ 7 | type: String, 8 | description: 'Template Name, this name is unique.', 9 | required: true, 10 | example: 'template-name', 11 | }) 12 | @IsNotEmpty() 13 | @IsString() 14 | @Expose() 15 | name: string; 16 | 17 | @ApiProperty({ 18 | type: String, 19 | description: 'Value that will be placed in the message subject.', 20 | required: true, 21 | example: 'Example subject', 22 | }) 23 | @IsNotEmpty() 24 | @IsString() 25 | @Expose() 26 | title: string; 27 | 28 | @ApiProperty({ 29 | type: String, 30 | description: 'Template to be saved', 31 | required: true, 32 | example: 33 | '

Lorem Ipsum - {0} ' + 34 | 'is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry standard {1} ' + 35 | 'ever since the 1500s, when an unknown {2} took a galley of type and scrambled it to make a type specimen {3}.

', 36 | }) 37 | @IsNotEmpty() 38 | @IsString() 39 | @Expose() 40 | template: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/CourseModule/dto/course-taken.dto.ts: -------------------------------------------------------------------------------- 1 | import { Expose, Type } from 'class-transformer'; 2 | import { IsDate, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, Max, Min } from 'class-validator'; 3 | import { CourseTakenStatusEnum } from '../enum/course-taken-status.enum'; 4 | import { CourseTaken } from '../entity/course-taken.entity'; 5 | 6 | export class CourseTakenDTO { 7 | @IsNotEmpty() 8 | @Expose() 9 | userId: string; 10 | 11 | @IsNotEmpty() 12 | @Expose() 13 | courseId: number; 14 | 15 | @IsOptional() 16 | @IsDate() 17 | @Expose() 18 | courseCompleteDate?: CourseTaken['courseCompleteDate']; 19 | 20 | @IsNotEmpty() 21 | @IsEnum(CourseTakenStatusEnum) 22 | @Expose() 23 | status: CourseTaken['status']; 24 | 25 | @Type(() => Number) 26 | @IsNumber() 27 | @Min(0) 28 | @Max(100) 29 | @Expose() 30 | completion: CourseTaken['completion']; 31 | 32 | @Expose() 33 | currentLessonId: number; 34 | 35 | @Expose() 36 | currentPartId: number; 37 | 38 | @Expose() 39 | currentTestId?: number; 40 | 41 | @IsOptional() 42 | @Type(() => Number) 43 | @IsNumber() 44 | @Min(0) 45 | @Max(10) 46 | @Expose() 47 | rating?: CourseTaken['rating']; 48 | 49 | @IsOptional() 50 | @IsString() 51 | @Expose() 52 | feedback?: CourseTaken['feedback']; 53 | 54 | @IsOptional() 55 | @IsString() 56 | @Expose() 57 | challenge?: CourseTaken['challenge']; 58 | } 59 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/pilar.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 3 | import { Constants } from '../../../CommonsModule/constants'; 4 | import { PilarService } from '../../service/v2/pilar.service'; 5 | import { CMSPilarDTO } from '../../dto/cms-pilar.dto'; 6 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 7 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 8 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 9 | 10 | @ApiTags('Pilar') 11 | @ApiBearerAuth() 12 | @Controller( 13 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.PILAR_ENDPOINT}`, 14 | ) 15 | export class PilarController { 16 | constructor(private readonly service: PilarService) {} 17 | 18 | @Get() 19 | @UseGuards(RoleGuard) 20 | @NeedRoles(RoleEnum.STUDENT) 21 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_ALL_PILARS`) 22 | public getAll(): Promise { 23 | return this.service.getAll(); 24 | } 25 | 26 | @Get('/:id') 27 | @UseGuards(RoleGuard) 28 | @NeedRoles(RoleEnum.STUDENT) 29 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_PILAR_BY_ID`) 30 | public findById(@Param('id', ParseIntPipe) id: number): Promise { 31 | return this.service.findById(id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/trail.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 3 | import { Constants } from '../../../CommonsModule/constants'; 4 | import { TrailService } from '../../service/v2/trail.service'; 5 | import { CMSTrailDTO } from '../../dto/cms-trail.dto'; 6 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 7 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 8 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 9 | 10 | @ApiTags('Trail') 11 | @ApiBearerAuth() 12 | @Controller( 13 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.TRAIL_ENDPOINT}`, 14 | ) 15 | export class TrailController { 16 | constructor(private readonly service: TrailService) {} 17 | 18 | @Get() 19 | @UseGuards(RoleGuard) 20 | @NeedRoles(RoleEnum.STUDENT) 21 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_ALL_TRAILS`) 22 | public getAll(): Promise { 23 | return this.service.getAll(); 24 | } 25 | 26 | @Get('/:id') 27 | @UseGuards(RoleGuard) 28 | @NeedRoles(RoleEnum.STUDENT) 29 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_TRAIL_BY_ID`) 30 | public findById(@Param('id', ParseIntPipe) id: number): Promise { 31 | return this.service.findById(id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CourseModule/entity/comment.entity.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../UserModule/entity/user.entity'; 2 | import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 3 | import { UserLikedComment } from './user-liked-comment.entity'; 4 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 5 | import { Expose } from 'class-transformer'; 6 | 7 | @Entity() 8 | export class Comment extends Audit { 9 | @PrimaryGeneratedColumn('uuid') 10 | id: string; 11 | 12 | @Column('varchar', { name: 'text', length: 255 }) 13 | text: string; 14 | 15 | @ManyToOne('User') 16 | @JoinColumn({ 17 | name: 'userId', 18 | referencedColumnName: 'id', 19 | }) 20 | @Expose() 21 | user: User; 22 | 23 | @Column('varchar', { name: 'userId', length: 36 }) 24 | userId: string; 25 | 26 | @Column('int', { name: 'partId' }) 27 | partId: number; 28 | 29 | @ManyToOne(() => Comment, (comment) => comment.responses) 30 | @JoinColumn([{ name: 'parentCommentId', referencedColumnName: 'id' }]) 31 | parentComment?: Comment; 32 | 33 | @Column('varchar', { name: 'parentCommentId', length: 36, nullable: true }) 34 | parentCommentId: string; 35 | 36 | @OneToMany(() => Comment, (comment) => comment.parentComment) 37 | responses: Comment[]; 38 | 39 | @OneToMany( 40 | () => UserLikedComment, 41 | (userLikedComment) => userLikedComment.comment, 42 | ) 43 | likedBy: UserLikedComment[]; 44 | } 45 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-trail-order.dto.ts: -------------------------------------------------------------------------------- 1 | export interface CMSTrailOrderDTO { 2 | id: number; 3 | curso: Curso; 4 | ordem: number; 5 | trilha: Trilha; 6 | published_at: Date; 7 | created_at: Date; 8 | updated_at: Date; 9 | } 10 | 11 | export interface Curso { 12 | id: number; 13 | descricao: string; 14 | published_at: Date; 15 | created_at: Date; 16 | updated_at: Date; 17 | titulo: string; 18 | nomeDoAutor: string; 19 | descricaoDoAutor: string; 20 | horas: number; 21 | slug: string; 22 | pilar: null; 23 | capa: Capa; 24 | } 25 | 26 | export interface Capa { 27 | id: number; 28 | name: string; 29 | alternativeText: string; 30 | caption: string; 31 | width: number; 32 | height: number; 33 | formats: Formats; 34 | hash: string; 35 | ext: string; 36 | mime: string; 37 | size: number; 38 | url: string; 39 | previewUrl: null; 40 | provider: string; 41 | provider_metadata: null; 42 | created_at: Date; 43 | updated_at: Date; 44 | } 45 | 46 | export interface Formats { 47 | small: Small; 48 | thumbnail: Small; 49 | } 50 | 51 | export interface Small { 52 | ext: string; 53 | url: string; 54 | hash: string; 55 | mime: string; 56 | name: string; 57 | path: null; 58 | size: number; 59 | width: number; 60 | height: number; 61 | } 62 | 63 | export interface Trilha { 64 | id: number; 65 | nome: string; 66 | published_at: Date; 67 | created_at: Date; 68 | updated_at: Date; 69 | } 70 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/highlight.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 3 | import { Constants } from '../../../CommonsModule/constants'; 4 | import { HighlightService } from '../../service/v2/highlight.service'; 5 | import { CMSHighlightDTO } from '../../dto/cms-highlight.dto'; 6 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 7 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 8 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 9 | 10 | @ApiTags('Highlight') 11 | @ApiBearerAuth() 12 | @Controller( 13 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.HIGHLIGHT_ENDPOINT}`, 14 | ) 15 | export class HighlightController { 16 | constructor(private readonly service: HighlightService) {} 17 | 18 | @Get() 19 | @UseGuards(RoleGuard) 20 | @NeedRoles(RoleEnum.STUDENT) 21 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_ALL_HIGHLIGHTS`) 22 | public getAll(): Promise { 23 | return this.service.getAll(); 24 | } 25 | 26 | @Get('/:id') 27 | @UseGuards(RoleGuard) 28 | @NeedRoles(RoleEnum.STUDENT) 29 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_HIGHLIGHT_BY_ID`) 30 | public findById(@Param('id', ParseIntPipe) id: number): Promise { 31 | return this.service.findById(id); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/course-v2.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { CourseV2Service } from '../../service/v2/course-v2.service'; 3 | import { CMSCourseDTO } from '../../dto/cms-course.dto'; 4 | import { Constants } from '../../../CommonsModule/constants'; 5 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 6 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 7 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 8 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 9 | 10 | @ApiTags('CourseV2') 11 | @ApiBearerAuth() 12 | @Controller( 13 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.COURSE_ENDPOINT}`, 14 | ) 15 | export class CourseV2Controller { 16 | constructor(private readonly service: CourseV2Service) {} 17 | 18 | @Get() 19 | @UseGuards(RoleGuard) 20 | @NeedRoles(RoleEnum.STUDENT) 21 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_ALL_COURSES`) 22 | public async getAll(): Promise { 23 | return this.service.getAll(); 24 | } 25 | 26 | @Get(':id') 27 | @UseGuards(RoleGuard) 28 | @NeedRoles(RoleEnum.STUDENT) 29 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_COURSE_BY_ID`) 30 | public async findById( 31 | @Param('id', ParseIntPipe) id: number, 32 | ): Promise { 33 | return this.service.findById(id); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/GameficationModule/subscriber/achievement.subscriber.ts: -------------------------------------------------------------------------------- 1 | import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm'; 2 | import { Achievement } from '../entity/achievement.entity'; 3 | import { NotificationService } from '../../NotificationModule/service/notification.service'; 4 | import { NotificationTypeEnum } from '../../NotificationModule/enum/notification-type.enum'; 5 | import { InjectConnection } from '@nestjs/typeorm'; 6 | 7 | @EventSubscriber() 8 | export class AchievementSubscriber 9 | implements EntitySubscriberInterface { 10 | constructor( 11 | @InjectConnection() readonly connection: Connection, 12 | private readonly notificationService: NotificationService, 13 | ) { 14 | connection.subscribers.push(this); 15 | } 16 | 17 | listenTo(): typeof Achievement { 18 | return Achievement; 19 | } 20 | 21 | async afterInsert(event: InsertEvent): Promise { 22 | if (!event.entity.completed) return; 23 | await this.notificationService.create( 24 | event.entity.user, 25 | NotificationTypeEnum.GAMEFICATION, 26 | event.entity, 27 | ); 28 | } 29 | 30 | async afterUpdate(event: UpdateEvent): Promise { 31 | if (!event.entity.completed) return; 32 | await this.notificationService.create( 33 | event.entity.user, 34 | NotificationTypeEnum.GAMEFICATION, 35 | event.entity, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/part-v2.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { Constants } from '../../../CommonsModule/constants'; 3 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 4 | import { PartV2Service } from '../../service/v2/part-v2.service'; 5 | import { CMSPartDTO } from '../../dto/cms-part.dto'; 6 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 7 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 8 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 9 | 10 | @ApiTags('PartV2') 11 | @ApiBearerAuth() 12 | @Controller( 13 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.PART_ENDPOINT}`, 14 | ) 15 | export class PartV2Controller { 16 | constructor(private readonly service: PartV2Service) {} 17 | 18 | @Get('lesson/:lessonId') 19 | @UseGuards(RoleGuard) 20 | @NeedRoles(RoleEnum.STUDENT) 21 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_PARTS_BY_LESSON_ID`) 22 | public async getAllByLessonId( 23 | @Param('lessonId', ParseIntPipe) lessonId: number, 24 | ): Promise { 25 | return this.service.getAll(lessonId); 26 | } 27 | 28 | @Get(':id') 29 | @UseGuards(RoleGuard) 30 | @NeedRoles(RoleEnum.STUDENT) 31 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_PART_BY_ID`) 32 | public async findById( 33 | @Param('id', ParseIntPipe) id: number, 34 | ): Promise { 35 | return this.service.findById(id); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-course.dto.ts: -------------------------------------------------------------------------------- 1 | export interface CMSCourseDTO { 2 | id: number; 3 | descricao: string; 4 | published_at: Date; 5 | created_at: Date; 6 | updated_at: Date; 7 | titulo: string; 8 | nomeDoAutor: string; 9 | descricaoDoAutor: string; 10 | horas: number; 11 | slug: string; 12 | pilars: Pilar[]; 13 | capa: Capa; 14 | aulas: Aula[]; 15 | ordenacao_da_trilhas: Aula[]; 16 | } 17 | 18 | export interface Pilar { 19 | id: number; 20 | nome: string; 21 | published_at: Date; 22 | created_at: Date; 23 | updated_at: Date; 24 | } 25 | 26 | export interface Aula { 27 | id: number; 28 | published_at: Date; 29 | created_at: Date; 30 | updated_at: Date; 31 | curso: number; 32 | ordem: number; 33 | titulo?: string; 34 | descricao?: string; 35 | trilha?: number; 36 | } 37 | 38 | export interface Capa { 39 | id: number; 40 | name: string; 41 | alternativeText: string; 42 | caption: string; 43 | width: number; 44 | height: number; 45 | formats: Formats; 46 | hash: string; 47 | ext: string; 48 | mime: string; 49 | size: number; 50 | url: string; 51 | previewUrl: null; 52 | provider: string; 53 | provider_metadata: null; 54 | created_at: Date; 55 | updated_at: Date; 56 | } 57 | 58 | export interface Formats { 59 | small: Small; 60 | thumbnail: Small; 61 | } 62 | 63 | export interface Small { 64 | ext: string; 65 | url: string; 66 | hash: string; 67 | mime: string; 68 | name: string; 69 | path: null; 70 | size: number; 71 | width: number; 72 | height: number; 73 | } 74 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/lesson-v2.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseIntPipe, UseGuards } from '@nestjs/common'; 2 | import { Constants } from '../../../CommonsModule/constants'; 3 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 4 | import { LessonV2Service } from '../../service/v2/lesson-v2.service'; 5 | import { CMSLessonDTO } from '../../dto/cms-lesson.dto'; 6 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 7 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 8 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 9 | 10 | @ApiTags('LessonV2') 11 | @ApiBearerAuth() 12 | @Controller( 13 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.LESSON_ENDPOINT}`, 14 | ) 15 | export class LessonV2Controller { 16 | constructor(private readonly service: LessonV2Service) {} 17 | 18 | @Get('course/:courseId') 19 | @UseGuards(RoleGuard) 20 | @NeedRoles(RoleEnum.STUDENT) 21 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_ALL_LESSONS_BY_COURSE_ID`) 22 | public async getAllByCourseId( 23 | @Param('courseId', ParseIntPipe) courseId: number, 24 | ): Promise { 25 | return this.service.getAll(courseId); 26 | } 27 | 28 | @Get(':id') 29 | @UseGuards(RoleGuard) 30 | @NeedRoles(RoleEnum.STUDENT) 31 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_LESSON_BY_COURSE_ID`) 32 | public async findById( 33 | @Param('id', ParseIntPipe) id: number, 34 | ): Promise { 35 | return this.service.findById(id); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CommonsModule/guard/student.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Injectable, 5 | InternalServerErrorException, 6 | UnauthorizedException, 7 | } from '@nestjs/common'; 8 | import { Reflector } from '@nestjs/core'; 9 | import { JwtService } from '@nestjs/jwt'; 10 | import { Request } from 'express'; 11 | import { User } from '../../UserModule/entity/user.entity'; 12 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 13 | 14 | @Injectable() 15 | export class StudentGuard implements CanActivate { 16 | constructor( 17 | private readonly reflector: Reflector, 18 | private readonly jwtService: JwtService, 19 | ) {} 20 | 21 | canActivate(context: ExecutionContext): boolean { 22 | const userIdParam = this.reflector.get( 23 | 'userIdParam', 24 | context.getHandler(), 25 | ); 26 | 27 | if (!userIdParam) return true; 28 | 29 | const request: Request = context.switchToHttp().getRequest(); 30 | 31 | const authorizationHeader = request.headers.authorization; 32 | 33 | if (!authorizationHeader) return true; 34 | 35 | const [, token] = authorizationHeader.split(' '); 36 | let user: User; 37 | 38 | try { 39 | user = this.jwtService.verify(token); 40 | } catch (e) { 41 | if (e.name === 'TokenExpiredError') { 42 | throw new UnauthorizedException(e.message); 43 | } 44 | throw new InternalServerErrorException(e); 45 | } 46 | 47 | if (user.role.name !== RoleEnum.STUDENT) return true; 48 | 49 | const userId = request.params[userIdParam]; 50 | 51 | return userId === user.id; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CourseModule/service/v2/test-v2.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; 2 | import { CmsIntegration } from '../../integration/cms.integration'; 3 | import { CMSTestDTO } from '../../dto/cms-test.dto'; 4 | import { ChosenAlternativeEnum } from '../../dto/check-test.dto'; 5 | import { PublisherService } from '../../../GameficationModule/service/publisher.service'; 6 | 7 | @Injectable() 8 | export class TestV2Service { 9 | constructor( 10 | private readonly cmsIntegration: CmsIntegration, 11 | private readonly publisherService: PublisherService, 12 | ) {} 13 | 14 | public async getAll(partId: number): Promise { 15 | const { data } = await this.cmsIntegration.getTestsByPartId(partId); 16 | return data; 17 | } 18 | 19 | public async findById(id: number): Promise { 20 | const errors = { 21 | 404: () => { 22 | throw new NotFoundException('Course not found'); 23 | }, 24 | }; 25 | 26 | try { 27 | const { data } = await this.cmsIntegration.findTestById(id); 28 | return data; 29 | } catch (e) { 30 | const error = errors[e.response.status]; 31 | if (!error) throw new InternalServerErrorException(); 32 | error(); 33 | } 34 | } 35 | 36 | public async checkTest( 37 | id: number, 38 | chosenAlternative: ChosenAlternativeEnum, 39 | ): Promise { 40 | const test: CMSTestDTO = await this.findById(id); 41 | this.publisherService.emitCheckTestReward(test, chosenAlternative); 42 | return chosenAlternative === test.alternativa_certa.toUpperCase(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/UserModule/dto/self-update.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entity/user.entity'; 2 | import { IsDate, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; 3 | import { Expose, Transform } from 'class-transformer'; 4 | import { GenderEnum } from '../enum/gender.enum'; 5 | import { EscolarityEnum } from '../enum/escolarity.enum'; 6 | 7 | export class SelfUpdateDTO { 8 | @IsOptional() 9 | @IsString() 10 | @Expose() 11 | id?: User['id']; 12 | 13 | @IsNotEmpty() 14 | @IsString() 15 | @Expose() 16 | name: User['name']; 17 | 18 | @IsNotEmpty() 19 | @IsString() 20 | @Expose() 21 | email: User['email']; 22 | 23 | @IsNotEmpty() 24 | @IsString() 25 | @Expose() 26 | nickname: string; 27 | 28 | @Transform((date) => date && new Date(date)) 29 | @IsNotEmpty() 30 | @IsDate() 31 | @Expose() 32 | birthday: Date; 33 | 34 | @IsNotEmpty() 35 | @IsEnum(GenderEnum) 36 | @Expose() 37 | gender: GenderEnum; 38 | 39 | @IsNotEmpty() 40 | @IsEnum(EscolarityEnum) 41 | @Expose() 42 | schooling: EscolarityEnum; 43 | 44 | @IsNotEmpty() 45 | @IsString() 46 | @Expose() 47 | institutionName: string; 48 | 49 | @IsNotEmpty() 50 | @IsString() 51 | @Expose() 52 | profession: string; 53 | 54 | @IsNotEmpty() 55 | @IsString() 56 | @Expose() 57 | address: string; 58 | 59 | @IsNotEmpty() 60 | @IsString() 61 | @Expose() 62 | city: string; 63 | 64 | @IsNotEmpty() 65 | @IsString() 66 | @Expose() 67 | state: string; 68 | 69 | @IsOptional() 70 | @IsString() 71 | @Expose() 72 | urlFacebook?: User['urlFacebook']; 73 | 74 | @IsOptional() 75 | @IsString() 76 | @Expose() 77 | urlInstagram?: User['urlInstagram']; 78 | } 79 | -------------------------------------------------------------------------------- /src/CourseModule/dto/cms-lesson.dto.ts: -------------------------------------------------------------------------------- 1 | export class CMSLessonDTO { 2 | id: number; 3 | Titulo: string; 4 | 'published_at': Date; 5 | 'created_at': Date; 6 | 'updated_at': Date; 7 | title: string; 8 | description: string; 9 | 'seq_num': number; 10 | curso: Curso; 11 | ordem: number; 12 | titulo: string; 13 | descricao: string; 14 | partes: Parte[]; 15 | } 16 | 17 | export class Curso { 18 | id: string; 19 | descricao: string; 20 | 'published_at': Date; 21 | 'created_at': Date; 22 | 'updated_at': Date; 23 | titulo: string; 24 | nomeDoAutor: string; 25 | descricaoDoAutor: string; 26 | horas: number; 27 | capa: Capa; 28 | } 29 | 30 | export class Capa { 31 | id: number; 32 | name: string; 33 | alternativeText: string; 34 | caption: string; 35 | width: number; 36 | height: number; 37 | formats: Formats; 38 | hash: string; 39 | ext: string; 40 | mime: string; 41 | size: number; 42 | url: string; 43 | previewUrl: string; 44 | provider: string; 45 | 'provider_metadata': string; 46 | 'created_at': Date; 47 | 'updated_at': Date; 48 | } 49 | 50 | export class Formats { 51 | small: Format; 52 | medium: Format; 53 | thumbnail: Format; 54 | } 55 | 56 | export class Format { 57 | ext: string; 58 | url: string; 59 | hash: string; 60 | mime: string; 61 | name: string; 62 | path: string; 63 | size: number; 64 | width: number; 65 | height: number; 66 | } 67 | 68 | export class Parte { 69 | id: number; 70 | titulo: string; 71 | descricao: string; 72 | ordem: number; 73 | 'published_at': Date; 74 | 'created_at': Date; 75 | 'updated_at': Date; 76 | aula: number; 77 | exercicio: string; 78 | video: string; 79 | } 80 | -------------------------------------------------------------------------------- /src/SecurityModule/security.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Global, HttpModule, Module } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { Role } from './entity/role.entity'; 6 | import { RoleService } from './service/role.service'; 7 | import { SecurityController } from './controller/security.controller'; 8 | import { SecurityService } from './service/security.service'; 9 | import { ClientCredentialsRepository } from './repository/client-credentials.repository'; 10 | import { UserModule } from '../UserModule/user.module'; 11 | import { ClientCredentials } from './entity/client-credentials.entity'; 12 | import { RoleRepository } from './repository/role.repository'; 13 | import { SecurityIntegration } from './integration/security.integration'; 14 | 15 | @Global() 16 | @Module({ 17 | imports: [ 18 | HttpModule, 19 | TypeOrmModule.forFeature([ 20 | Role, 21 | RoleRepository, 22 | ClientCredentials, 23 | ClientCredentialsRepository, 24 | ]), 25 | JwtModule.registerAsync({ 26 | useFactory: async (configService: ConfigService) => ({ 27 | secret: configService.get('JWT_SECRET'), 28 | signOptions: { 29 | expiresIn: configService.get('EXPIRES_IN_ACCESS_TOKEN'), 30 | }, 31 | }), 32 | inject: [ConfigService], 33 | }), 34 | forwardRef(() => UserModule), 35 | ], 36 | controllers: [SecurityController], 37 | providers: [SecurityService, RoleService, SecurityIntegration], 38 | exports: [SecurityService, RoleService, JwtModule, SecurityIntegration], 39 | }) 40 | export class SecurityModule {} 41 | -------------------------------------------------------------------------------- /src/SecurityModule/entity/client-credentials.entity.ts: -------------------------------------------------------------------------------- 1 | import { BeforeInsert, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Expose } from 'class-transformer'; 3 | import slugify from 'slugify'; 4 | import { Role } from './role.entity'; 5 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 6 | 7 | @Entity({ name: 'client-credentials' }) 8 | export class ClientCredentials extends Audit { 9 | @PrimaryGeneratedColumn('uuid') 10 | id: string; 11 | 12 | @Column() 13 | name: string; 14 | 15 | private _slug: string; 16 | 17 | @Column({ 18 | nullable: false, 19 | unique: true, 20 | }) 21 | get slug(): string { 22 | return slugify(this.name); 23 | } 24 | 25 | set slug(name: string) {} 26 | 27 | @Column({ type: 'varchar' }) 28 | secret: string; 29 | 30 | @Column({ 31 | name: 'authorized_grant_types', 32 | }) 33 | private _authorizedGrantTypes: string; 34 | 35 | @Column({ 36 | name: 'access_token_validity', 37 | nullable: false, 38 | }) 39 | accessTokenValidity: number; 40 | 41 | @Column({ 42 | name: 'refresh_token_validity', 43 | nullable: true, 44 | }) 45 | refreshTokenValidity?: number; 46 | 47 | @ManyToOne(() => Role, (role: Role) => role.clientCredentials) 48 | role: Role; 49 | 50 | @Expose() 51 | get authorizedGrantTypes(): string[] { 52 | return this._authorizedGrantTypes 53 | .split(',') 54 | .filter((grantType) => grantType); 55 | } 56 | 57 | set authorizedGrantTypes(authorizedGrantTypes: string[]) { 58 | this._authorizedGrantTypes = authorizedGrantTypes.join(','); 59 | } 60 | 61 | @BeforeInsert() 62 | setId() { 63 | this.slug = slugify(this.name); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ormconfig.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionOptions } from 'typeorm'; 2 | import * as path from 'path'; 3 | 4 | require('dotenv-flow').config(); 5 | 6 | // console.log(path.resolve(path.join(__dirname, '..')) + '/.env'); 7 | // require('dotenv-flow').load([ 8 | // path.resolve(path.join(__dirname, '..')) + '/.env', 9 | // ]); 10 | 11 | const databaseHost: string = process.env.DATABASE_HOST; 12 | const databaseName: string = process.env.DATABASE_NAME; 13 | const databasePort = Number(process.env.DATABASE_PORT); 14 | const databaseUsername: string = process.env.DATABASE_USERNAME; 15 | const databasePassword: string = process.env.DATABASE_PASSWORD; 16 | const synchronize: boolean = process.env.SYNC_DATABASE == 'true'; 17 | const logging: boolean = process.env.NODE_ENV !== 'TEST'; 18 | 19 | // You can load you .env file here synchronously using dotenv package (not installed here), 20 | // import * as dotenv from 'dotenv'; 21 | // import * as fs from 'fs'; 22 | // const environment = process.env.NODE_ENV || 'development'; 23 | // const data: any = dotenv.parse(fs.readFileSync(`${environment}.env`)); 24 | // You can also make a singleton service that load and expose the .env file content. 25 | // ... 26 | 27 | // Check typeORM documentation for more information. 28 | 29 | const config: ConnectionOptions = { 30 | type: 'mysql', 31 | multipleStatements: true, 32 | entities: [path.resolve(path.join(__dirname)) + '/**/*.entity{.ts,.js}'], 33 | migrations: ['src/migration/*.ts'], 34 | migrationsRun: true, 35 | migrationsTableName: 'migration', 36 | cli: { 37 | migrationsDir: 'src/migration', 38 | }, 39 | host: databaseHost, 40 | database: databaseName, 41 | port: databasePort, 42 | username: databaseUsername, 43 | password: databasePassword, 44 | synchronize: synchronize || false, 45 | logging: logging, 46 | }; 47 | 48 | export = config; 49 | -------------------------------------------------------------------------------- /src/UserModule/dto/new-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entity/user.entity'; 2 | import { IsDate, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; 3 | import { Expose, Transform } from 'class-transformer'; 4 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 5 | import { GenderEnum } from '../enum/gender.enum'; 6 | import { EscolarityEnum } from '../enum/escolarity.enum'; 7 | import { UserProfileEnum } from '../enum/user-profile.enum'; 8 | 9 | export class NewUserDTO { 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | name: User['name']; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | email: User['email']; 19 | 20 | @IsNotEmpty() 21 | @IsEnum(UserProfileEnum) 22 | @Expose() 23 | profile: User['profile']; 24 | 25 | @IsNotEmpty() 26 | @IsString() 27 | @Expose() 28 | password: User['password']; 29 | 30 | @IsOptional() 31 | @IsString() 32 | @Expose() 33 | nickname?: string; 34 | 35 | @Transform((date) => date && new Date(date)) 36 | @IsOptional() 37 | @IsDate() 38 | @Expose() 39 | birthday?: Date; 40 | 41 | @IsOptional() 42 | @IsEnum(GenderEnum) 43 | @Expose() 44 | gender?: GenderEnum; 45 | 46 | @IsOptional() 47 | @IsEnum(EscolarityEnum) 48 | @Expose() 49 | schooling?: EscolarityEnum; 50 | 51 | @IsOptional() 52 | @IsString() 53 | @Expose() 54 | institutionName?: string; 55 | 56 | @IsOptional() 57 | @IsString() 58 | @Expose() 59 | profession?: string; 60 | 61 | @IsOptional() 62 | @IsString() 63 | @Expose() 64 | address?: string; 65 | 66 | @IsOptional() 67 | @IsString() 68 | @Expose() 69 | urlFacebook?: User['urlFacebook']; 70 | 71 | @IsOptional() 72 | @IsString() 73 | @Expose() 74 | urlInstagram?: User['urlInstagram']; 75 | 76 | @IsNotEmpty() 77 | @IsEnum(RoleEnum) 78 | @Expose() 79 | role: RoleEnum; 80 | 81 | invitedByUserId?: string; 82 | } 83 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app.module'; 4 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 5 | import { ValidationPipe } from '@nestjs/common'; 6 | import { 7 | initializeTransactionalContext, 8 | patchTypeORMRepositoryWithBaseRepository, 9 | } from 'typeorm-transactional-cls-hooked'; 10 | import * as Sentry from '@sentry/node'; 11 | import { RavenInterceptor } from 'nest-raven'; 12 | import { json, urlencoded } from 'express'; 13 | import { HttpExceptionFilter } from './CommonsModule/httpFilter/http-exception.filter'; 14 | import { AppConfigService as ConfigService } from './ConfigModule/service/app-config.service'; 15 | 16 | async function bootstrap() { 17 | initializeTransactionalContext(); 18 | patchTypeORMRepositoryWithBaseRepository(); 19 | 20 | const appOptions = { cors: true }; 21 | const app = await NestFactory.create(AppModule, appOptions); 22 | 23 | const options = new DocumentBuilder() 24 | .setTitle('@NewSchool/back') 25 | .setDescription('Backend para o projeto NewSchool') 26 | .setVersion('1.0') 27 | .build(); 28 | const document = SwaggerModule.createDocument(app, options); 29 | 30 | SwaggerModule.setup(process.env.IS_GITPOD ? '' : 'swagger', app, document); 31 | 32 | app.useGlobalPipes( 33 | new ValidationPipe({ 34 | transform: true, 35 | whitelist: true, 36 | }), 37 | ); 38 | 39 | const appConfigService = app.get(ConfigService); 40 | 41 | app.useGlobalInterceptors(new RavenInterceptor()); 42 | app.useGlobalFilters(new HttpExceptionFilter()); 43 | 44 | Sentry.init(appConfigService.getSentryConfiguration()); 45 | 46 | app.use(json({ limit: '50mb' })); 47 | app.use(urlencoded({ extended: true, limit: '50mb' })); 48 | 49 | await app.listen(appConfigService.port || 8080); 50 | } 51 | 52 | bootstrap(); 53 | -------------------------------------------------------------------------------- /src/CourseModule/entity/course-taken.entity.ts: -------------------------------------------------------------------------------- 1 | import { CourseTakenStatusEnum } from '../enum/course-taken-status.enum'; 2 | import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; 3 | import { Expose } from 'class-transformer'; 4 | import { User } from '../../UserModule/entity/user.entity'; 5 | import { Audit } from '../../CommonsModule/entity/audit.entity'; 6 | 7 | @Entity('course_taken') 8 | @Index( 9 | (courseTaken: CourseTaken) => [courseTaken.userId, courseTaken.courseId], 10 | { 11 | unique: true, 12 | }, 13 | ) 14 | export class CourseTaken extends Audit { 15 | @Column('datetime', { name: 'course_complete_date', nullable: true }) 16 | courseCompleteDate?: Date; 17 | 18 | @Column('enum', { 19 | name: 'status', 20 | enum: CourseTakenStatusEnum, 21 | default: CourseTakenStatusEnum.TAKEN, 22 | }) 23 | status: CourseTakenStatusEnum; 24 | 25 | @Column({ name: 'completion', default: 0 }) 26 | completion: number; 27 | 28 | @ManyToOne('User') 29 | @JoinColumn({ 30 | name: 'user_id', 31 | referencedColumnName: 'id', 32 | }) 33 | @Expose() 34 | user: User; 35 | 36 | @Column('varchar', { primary: true, name: 'user_id', length: 36 }) 37 | userId: string; 38 | 39 | @Column('varchar', { primary: true, name: 'course_id', length: 36 }) 40 | courseId: number; 41 | 42 | @Column('int', { name: 'current_lesson_id', nullable: false }) 43 | currentLessonId: number; 44 | 45 | @Column('int', { name: 'current_part_id', nullable: false }) 46 | currentPartId: number; 47 | 48 | @Column('int', { name: 'current_test_id', nullable: true }) 49 | currentTestId?: number; 50 | 51 | @Column('int', { name: 'rating', nullable: true }) 52 | rating?: number; 53 | 54 | @Column('varchar', { name: 'feedback', nullable: true, length: 255 }) 55 | feedback?: string; 56 | 57 | @Column('varchar', { name: 'challenge', nullable: true, length: 500 }) 58 | challenge?: string; 59 | } 60 | -------------------------------------------------------------------------------- /src/UserModule/service/semear.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit } from '@nestjs/common'; 2 | import * as PubSub from 'pubsub-js'; 3 | import { EventNameEnum } from '../../GameficationModule/enum/event-name.enum'; 4 | import { User } from '../entity/user.entity'; 5 | import { EscolarityEnum } from '../enum/escolarity.enum'; 6 | import { NotificationService } from '../../NotificationModule/service/notification.service'; 7 | import { NotificationTypeEnum } from '../../NotificationModule/enum/notification-type.enum'; 8 | import { UploadService } from '../../UploadModule/service/upload.service'; 9 | 10 | @Injectable() 11 | export class SemearService implements OnModuleInit { 12 | constructor( 13 | private readonly notificationService: NotificationService, 14 | private readonly uploadService: UploadService, 15 | ) {} 16 | 17 | onModuleInit(): void { 18 | PubSub.subscribe( 19 | EventNameEnum.COURSE_REWARD_COMPLETE_COURSE, 20 | async (message: string, data: User) => { 21 | await this.sendSemearNotification(data); 22 | }, 23 | ); 24 | } 25 | 26 | public async sendSemearNotification(user: User): Promise { 27 | if ( 28 | user.schooling !== EscolarityEnum.ENSINO_MEDIO_COMPLETO && 29 | user.schooling !== EscolarityEnum.TERCEIRO_ANO 30 | ) 31 | return; 32 | 33 | const userHasSemearFile = await this.uploadService.fileExists( 34 | `/semear/${user.id}.json`, 35 | ); 36 | if (userHasSemearFile) return; 37 | 38 | const notification = await this.notificationService.getSemearNotSeenAndEanbledNotificationByUserId( 39 | user.id, 40 | ); 41 | if (notification) return; 42 | 43 | await this.notificationService.create>( 44 | user, 45 | NotificationTypeEnum.OTHER, 46 | { 47 | semearSiteUrl: 'http://www.isemear.org.br/processo-seletivo/', 48 | }, 49 | { important: true }, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: New School Backend CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - feature/* 7 | - fix/* 8 | - hotfix/* 9 | 10 | pull_request: 11 | branches: 12 | - develop 13 | 14 | jobs: 15 | #integration-tests: 16 | #name: Integration Tests 17 | #runs-on: ubuntu-latest 18 | #strategy: 19 | #matrix: 20 | #node-version: [12.x, 14.x] 21 | #steps: 22 | #- name: Checkout Repo 23 | #uses: actions/checkout@master 24 | #- name: Use Node.js ${{ matrix.node-version }} 25 | #uses: actions/setup-node@v1 26 | #with: 27 | #node-version: ${{ matrix.node-version }} 28 | #- name: Install Dependencies 29 | #run: npm ci 30 | #- name: Setup integration database 31 | #uses: mirromutth/mysql-action@v1.1 32 | #with: 33 | #mysql version: 5.7 34 | #mysql database: 'newschool_tests' 35 | #mysql user: 'newschool_tests' # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Can use secrets, too 36 | #mysql password: '123987' # Required if "mysql user" exists. The password for the "mysql user" 37 | #- name: Integration tests 38 | #run: npm run test:e2e 39 | #env: 40 | #CI: true 41 | #NODE_ENV: 'TEST' 42 | #DATABASE_HOST: 'localhost' 43 | #DATABASE_NAME: 'newschool_tests' 44 | #DATABASE_USERNAME: 'newschool_tests' 45 | #DATABASE_PASSWORD: '123987' 46 | #DATABASE_PORT: 3306 47 | 48 | build: 49 | name: Build 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | node-version: [12.x, 14.x] 54 | steps: 55 | - name: Checkout Repo 56 | uses: actions/checkout@master 57 | - name: Use Node.js ${{ matrix.node-version }} 58 | uses: actions/setup-node@v1 59 | with: 60 | node-version: ${{ matrix.node-version }} 61 | - name: Install Dependencies 62 | run: npm ci 63 | - name: Build 64 | run: npm run build 65 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/v2/test-v2.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Param, ParseIntPipe, Post, UseGuards } from '@nestjs/common'; 2 | import { Constants } from '../../../CommonsModule/constants'; 3 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 4 | import { CheckTestBodyDTO, CheckTestResponseDTO } from '../../dto/check-test.dto'; 5 | import { CMSTestDTO } from '../../dto/cms-test.dto'; 6 | import { TestV2Service } from '../../service/v2/test-v2.service'; 7 | import { RoleEnum } from '../../../SecurityModule/enum/role.enum'; 8 | import { RoleGuard } from '../../../CommonsModule/guard/role.guard'; 9 | import { NeedPolicies, NeedRoles } from '../../../CommonsModule/decorator/role-guard-metadata.decorator'; 10 | 11 | @ApiTags('TestV2') 12 | @ApiBearerAuth() 13 | @Controller( 14 | `${Constants.API_PREFIX}/${Constants.API_VERSION_2}/${Constants.TEST_ENDPOINT}`, 15 | ) 16 | export class TestV2Controller { 17 | constructor(private readonly service: TestV2Service) {} 18 | 19 | @Get('part/:partId') 20 | @UseGuards(RoleGuard) 21 | @NeedRoles(RoleEnum.STUDENT) 22 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_ALL_TESTS_BY_PART_ID`) 23 | public async getAllByPartId( 24 | @Param('partId', ParseIntPipe) partId: number, 25 | ): Promise { 26 | return this.service.getAll(partId); 27 | } 28 | 29 | @Get(':id') 30 | @UseGuards(RoleGuard) 31 | @NeedRoles(RoleEnum.STUDENT) 32 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/FIND_TEST_BY_ID`) 33 | public async findById( 34 | @Param('id', ParseIntPipe) id: number, 35 | ): Promise { 36 | return this.service.findById(id); 37 | } 38 | 39 | @Post(':id/check-test') 40 | @UseGuards(RoleGuard) 41 | @NeedRoles(RoleEnum.STUDENT) 42 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/CHECK_TEST`) 43 | public async checkTest( 44 | @Param('id', ParseIntPipe) id: number, 45 | @Body() checkTest: CheckTestBodyDTO, 46 | ): Promise { 47 | return { 48 | isCorrect: await this.service.checkTest(id, checkTest.chosenAlternative), 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/DashboardModule/service/dashboard.service.ts: -------------------------------------------------------------------------------- 1 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 2 | import { Injectable, NotImplementedException } from '@nestjs/common'; 3 | import { UserStatusEnum } from '../enum/UserStatusEnum'; 4 | import { UserService } from '../../UserModule/service/user.service'; 5 | import { CourseTakenStatusEnum } from '../../CourseModule/enum/course-taken-status.enum'; 6 | import { getCoursesByFinished } from '../interfaces/getCoursesByFinished'; 7 | import { CourseTakenService } from '../../CourseModule/service/v1/course-taken.service'; 8 | 9 | @Injectable() 10 | export class DashboardService { 11 | constructor( 12 | private userService: UserService, 13 | private courseTakenService: CourseTakenService, 14 | ) {} 15 | 16 | public async getUserQuantity(status?: UserStatusEnum): Promise { 17 | if (!status) { 18 | return this.userService.getUsersQuantity(); 19 | } 20 | if (status === UserStatusEnum.ACTIVE) { 21 | return this.courseTakenService.getActiveUsersQuantity(); 22 | } 23 | // TODO: will get here if status === 'INACTIVE'. it should be implemented 24 | throw new NotImplementedException(); 25 | } 26 | 27 | getCertificateQuantity(): Promise { 28 | return this.courseTakenService.getCertificateQuantity(); 29 | } 30 | 31 | getUsersInCourseQuantity( 32 | status: CourseTakenStatusEnum, 33 | ): number | PromiseLike { 34 | if (!status) { 35 | return this.courseTakenService.getUsersWithCompletedAndTakenCourses(); 36 | } 37 | if (status === CourseTakenStatusEnum.COMPLETED) { 38 | return this.courseTakenService.getUsersWithCompletedCourses(); 39 | } 40 | return this.courseTakenService.getUsersWithTakenCourses(); 41 | } 42 | 43 | getCoursesByFinished( 44 | order: OrderEnum, 45 | limit: number, 46 | ): Promise { 47 | //deve executar uma função presente em 'courses.taken.service' que por sua vez retorna o resultado de uma query no db 48 | return this.courseTakenService.getCoursesByFinished(order, limit); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ENVCONFIG.md: -------------------------------------------------------------------------------- 1 | ## Guia para uso de variáveis de configuração 2 | 3 | Para acesso às variáveis de ambiente do projeto (.env), utilizamos o pacote do Nest `@nestjs/config`. 4 | Este pacote consiste de 2 classes: 5 | 6 | - `ConfigModule` 7 | - Módulo de configuração, que expõe o serviço (ConfigService) para utilização 8 | 9 | - `ConfigService` 10 | - Serviço responsável pelo carregamento e disponibilização das variáveis de configuração para uso no código 11 | 12 | O `ConfigModule` já é instanciado de forma global no "start" da aplicação, sendo necessário apenas instanciar o `ConfigService` nas classes que farão uso das variáveis. 13 | 14 | ### Utilização do ConfigService 15 | 16 | Para utilizar o `ConfigService` em uma classe, será necessário: 17 | 18 | - Importar a classe `ConfigService` 19 | >**import { ConfigService } from '@nestjs/config';** 20 | 21 | - Em um módulo, sempre que for necessário importar um módulo do Nest (ex.: `JwtModule`) com uso de variáveis de ambiente, deve-se usar os métodos `RegisterAsync` ou `forRootAsync` (ao invés dos métodos `Register` ou `forRoot`), inserindo o `ConfigService` por injeção de dependência. Exemplo: 22 | ``` 23 | JwtModule.registerAsync({ 24 | useFactory: async (configService: ConfigService) => ({ 25 | secret: configService.get('JWT_SECRET'), 26 | signOptions: { expiresIn: configService.get('EXPIRES_IN_ACCESS_TOKEN') }, 27 | }), 28 | inject: [ConfigService], 29 | }), 30 | ``` 31 | 32 | - Em outras classes (controllers, services...), deve-se declarar o `ConfigService` como parâmetro do construtor para utilização do mesmo como atributo da classe. 33 | >**constructor(private readonly configService: ConfigService) {}** 34 | 35 | - O acesso a variáveis de ambiente, onde seria utilizado o formato `process.env.`, deve ser feito com a seguinte estrutura: 36 | - Em módulos: 37 | >**configService.get('VARIAVEL')** 38 | 39 | - Em outras classes: 40 | >**this.configService.get('VARIAVEL')** 41 | 42 | Para maiores informações sobre o `@nestjs/config`, acesse: https://docs.nestjs.com/techniques/configuration 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/GameficationModule/gamefication.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, HttpModule, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { CourseRewardsService } from './service/course-rewards.service'; 4 | import { PublisherService } from './service/publisher.service'; 5 | import { Badge } from './entity/badge.entity'; 6 | import { Achievement } from './entity/achievement.entity'; 7 | import { AchievementRepository } from './repository/achievement.repository'; 8 | import { BadgeRepository } from './repository/badge.repository'; 9 | import { NotificationModule } from '../NotificationModule/notification.module'; 10 | import { AchievementSubscriber } from './subscriber/achievement.subscriber'; 11 | import { UserModule } from '../UserModule/user.module'; 12 | import { CourseModule } from '../CourseModule/course.module'; 13 | import { GameficationController } from './controller/gamefication.controller'; 14 | import { GameficationService } from './service/gamefication.service'; 15 | import { UserRewardsService } from './service/user-rewards.service'; 16 | import { AchievementService } from './service/achievement.service'; 17 | import { CourseTakenRepository } from '../CourseModule/repository/course.taken.repository'; 18 | import { UserRepository } from '../UserModule/repository/user.repository'; 19 | import { UploadModule } from '../UploadModule/upload.module'; 20 | 21 | @Module({ 22 | controllers: [GameficationController], 23 | imports: [ 24 | TypeOrmModule.forFeature([ 25 | Achievement, 26 | AchievementRepository, 27 | Badge, 28 | BadgeRepository, 29 | CourseTakenRepository, 30 | UserRepository, 31 | ]), 32 | HttpModule, 33 | forwardRef(() => UserModule), 34 | forwardRef(() => CourseModule), 35 | UploadModule, 36 | NotificationModule, 37 | ], 38 | providers: [ 39 | CourseRewardsService, 40 | UserRewardsService, 41 | PublisherService, 42 | AchievementSubscriber, 43 | GameficationService, 44 | AchievementService, 45 | ], 46 | exports: [PublisherService, AchievementService], 47 | }) 48 | export class GameficationModule {} 49 | -------------------------------------------------------------------------------- /src/UserModule/user.module.ts: -------------------------------------------------------------------------------- 1 | import { CacheModule, forwardRef, HttpModule, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { User } from './entity/user.entity'; 4 | import { UserRepository } from './repository/user.repository'; 5 | import { ChangePassword } from './entity/change-password.entity'; 6 | import { ChangePasswordRepository } from './repository/change-password.repository'; 7 | import { UserController } from './controller/user.controller'; 8 | import { UserService } from './service/user.service'; 9 | import { UserMapper } from './mapper/user.mapper'; 10 | import { ChangePasswordService } from './service/change-password.service'; 11 | import { SchoolController } from './controller/school.controller'; 12 | import { SchoolService } from './service/school.service'; 13 | import { GameficationModule } from '../GameficationModule/gamefication.module'; 14 | import { UploadModule } from '../UploadModule/upload.module'; 15 | import { CityController } from './controller/city.controller'; 16 | import { CityService } from './service/city.service'; 17 | import { StateService } from './service/state.service'; 18 | import { StateController } from './controller/state.controller'; 19 | import { SemearService } from './service/semear.service'; 20 | import { NotificationModule } from '../NotificationModule/notification.module'; 21 | import { School } from './entity/school.entity'; 22 | 23 | @Module({ 24 | imports: [ 25 | CacheModule.register(), 26 | TypeOrmModule.forFeature([ 27 | User, 28 | School, 29 | UserRepository, 30 | ChangePassword, 31 | ChangePasswordRepository, 32 | ]), 33 | HttpModule, 34 | UploadModule, 35 | NotificationModule, 36 | forwardRef(() => GameficationModule), 37 | ], 38 | controllers: [ 39 | UserController, 40 | SchoolController, 41 | CityController, 42 | StateController, 43 | ], 44 | providers: [ 45 | UserService, 46 | UserMapper, 47 | ChangePasswordService, 48 | SchoolService, 49 | CityService, 50 | StateService, 51 | SemearService, 52 | ], 53 | exports: [UserService, UserMapper], 54 | }) 55 | export class UserModule {} 56 | -------------------------------------------------------------------------------- /src/NotificationModule/controller/notification.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, HttpCode, Param, Post, Put, Query, UseGuards } from '@nestjs/common'; 2 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 3 | import { Constants } from '../../CommonsModule/constants'; 4 | import { Notification } from '../entity/notification.entity'; 5 | import { NotificationService } from '../service/notification.service'; 6 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 7 | import { CreateNotificationDTO } from '../dto/create-notification.dto'; 8 | import { RoleGuard } from '../../CommonsModule/guard/role.guard'; 9 | import { NeedPolicies } from '../../CommonsModule/decorator/role-guard-metadata.decorator'; 10 | 11 | @ApiBearerAuth() 12 | @ApiTags('Notification') 13 | @Controller( 14 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.NOTIFICATION_ENDPOINT}`, 15 | ) 16 | export class NotificationController { 17 | constructor(private readonly service: NotificationService) {} 18 | 19 | @Get('/:id') 20 | @HttpCode(200) 21 | public async findNotificationById( 22 | @Param('id') id: string, 23 | ): Promise { 24 | return this.service.findById(id); 25 | } 26 | 27 | @Put('/:id/see') 28 | @HttpCode(200) 29 | public async setSeeNotification(@Param('id') id: string): Promise { 30 | return this.service.setSeeNotification(id); 31 | } 32 | 33 | @Get('/user/:userId') 34 | @HttpCode(200) 35 | public async getNotificationsByUserId( 36 | @Param('userId') userId: string, 37 | @Query('order') order: OrderEnum = OrderEnum.DESC, 38 | @Query('enabled') enabled: boolean = true, 39 | @Query('order') seen: boolean = false, 40 | ): Promise { 41 | return this.service.getNotificationsByUserId(userId, order, enabled, seen); 42 | } 43 | 44 | @Post('/user/:userId') 45 | @HttpCode(200) 46 | @UseGuards(RoleGuard) 47 | @NeedPolicies('@EDUCATION-PLATFORM/CREATE-NOTIFICATION') 48 | public async addNotificationsToUser( 49 | @Param('userId') userId: string, 50 | @Body() notification: CreateNotificationDTO, 51 | ): Promise { 52 | return this.service.addNotificationToUser(userId, notification); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/UserModule/repository/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { createQueryBuilder, EntityRepository, Repository } from 'typeorm'; 2 | import { User } from '../entity/user.entity'; 3 | 4 | @EntityRepository(User) 5 | export class UserRepository extends Repository { 6 | async findByEmail(email: string): Promise { 7 | return this.findOne({ email }, { relations: ['role', 'role.policies'] }); 8 | } 9 | 10 | async findByIdWithCertificates(id: string): Promise { 11 | return this.findOneOrFail(id, { relations: ['certificates'] }); 12 | } 13 | 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | public async getCertificateByUser(userId: string): Promise { 16 | return createQueryBuilder('user', 'user') 17 | .innerJoinAndSelect( 18 | 'certificate_users_user', 19 | 'certificate_user', 20 | 'certificate_user.userId = user.id', 21 | ) 22 | .innerJoinAndSelect( 23 | 'certificate', 24 | 'certificate', 25 | 'certificate.id = certificate_user.certificateId', 26 | ) 27 | .where('user.id = :userId', { userId }) 28 | .getRawMany(); 29 | } 30 | 31 | async findByIdWithCourses(id: string): Promise { 32 | return this.findOneOrFail(id, { relations: ['createdCourses'] }); 33 | } 34 | 35 | async findByEmailAndFacebookId( 36 | email: string, 37 | facebookId: string, 38 | ): Promise { 39 | return this.findOne({ email, facebookId }); 40 | } 41 | 42 | getUsersQuantity(): Promise { 43 | return this.count(); 44 | } 45 | 46 | // #TODO: Criar lógica de usuários ativos 47 | getActiveUsersQuantity(): Promise { 48 | return this.count(); 49 | } 50 | 51 | // #TODO: Criar lógica de usuários inativos 52 | getInactiveUsersQuantity(): Promise { 53 | return this.count(); 54 | } 55 | 56 | async findByInviteKey(inviteKey: string): Promise { 57 | const response = await this.find({ where: { inviteKey } }); 58 | return response[0]; 59 | } 60 | 61 | public async countUsersInvitedByUserId(id: string): Promise { 62 | return this.count({ where: { invitedByUserId: { id } } }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/CourseModule/service/v1/course-taken.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, NotFoundException } from '@nestjs/common'; 2 | import { OrderEnum } from '../../../CommonsModule/enum/order.enum'; 3 | import { CourseTakenRepository } from '../../repository/course.taken.repository'; 4 | import { CourseTaken } from '../../entity/course-taken.entity'; 5 | import { getCoursesByFinished } from '../../../DashboardModule/interfaces/getCoursesByFinished'; 6 | import { PublisherService } from '../../../GameficationModule/service/publisher.service'; 7 | 8 | @Injectable() 9 | export class CourseTakenService { 10 | @Inject(PublisherService) 11 | private readonly publisherService: PublisherService; 12 | 13 | constructor(private readonly repository: CourseTakenRepository) {} 14 | 15 | public async getActiveUsersQuantity(): Promise { 16 | return this.repository.getActiveUsersQuantity(); 17 | } 18 | 19 | public async getCertificateQuantity(): Promise { 20 | return this.repository.getCertificateQuantity(); 21 | } 22 | 23 | public async getUsersWithTakenCourses(): Promise { 24 | return this.repository.getUsersWithTakenCourses(); 25 | } 26 | 27 | public async getUsersWithCompletedCourses(): Promise { 28 | return this.repository.getUsersWithCompletedCourses(); 29 | } 30 | 31 | public async getUsersWithCompletedAndTakenCourses(): Promise { 32 | return this.repository.getUsersWithCompletedAndTakenCourses(); 33 | } 34 | 35 | public async getCoursesByFinished( 36 | order: OrderEnum, 37 | limit: number, 38 | ): Promise { 39 | //executar query que rotorna o array de cursos de acordo com a ordem levando em consideração a coluna que armazena o array dos alunos que terminaram 40 | 41 | return this.repository.getDistinctCourses(order, limit); 42 | } 43 | 44 | public async findByUserIdAndCourseId( 45 | userId: string, 46 | courseId: number, 47 | ): Promise { 48 | const courseTaken = this.repository.findByUserIdAndCourseId( 49 | userId, 50 | courseId, 51 | ); 52 | if (!courseTaken) { 53 | throw new NotFoundException('Course not taken by user'); 54 | } 55 | return courseTaken; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { MailerModule } from '@nest-modules/mailer'; 2 | import { MailerAsyncOptions } from '@nest-modules/mailer/dist/interfaces/mailer-async-options.interface'; 3 | import { Module } from '@nestjs/common'; 4 | import { ConfigModule as NestConfigModule } from '@nestjs/config'; 5 | import { SecurityModule } from './SecurityModule/security.module'; 6 | import { UserModule } from './UserModule/user.module'; 7 | import { TypeOrmModule, TypeOrmModuleAsyncOptions } from '@nestjs/typeorm'; 8 | import { CourseModule } from './CourseModule/course.module'; 9 | import { MessageModule } from './MessageModule/message.module'; 10 | import { UploadModule } from './UploadModule/upload.module'; 11 | import { ConfigModule } from './ConfigModule/config.module'; 12 | import { AppConfigService as ConfigService } from './ConfigModule/service/app-config.service'; 13 | import { DashboardModule } from './DashboardModule/dashboard.module'; 14 | import { GameficationModule } from './GameficationModule/gamefication.module'; 15 | import { NotificationModule } from './NotificationModule/notification.module'; 16 | import { RavenModule } from 'nest-raven'; 17 | import { ScheduleModule } from '@nestjs/schedule'; 18 | import { MulterModule } from '@nestjs/platform-express'; 19 | 20 | const typeOrmAsyncModule: TypeOrmModuleAsyncOptions = { 21 | imports: [ConfigModule], 22 | inject: [ConfigService], 23 | useFactory: (appConfigService: ConfigService) => 24 | appConfigService.getDatabaseConfig(), 25 | }; 26 | 27 | const mailerAsyncModule: MailerAsyncOptions = { 28 | useFactory: (appConfigService: ConfigService) => 29 | appConfigService.getSmtpConfiguration(), 30 | imports: [ConfigModule], 31 | inject: [ConfigService], 32 | }; 33 | 34 | @Module({ 35 | imports: [ 36 | RavenModule, 37 | ConfigModule, 38 | ScheduleModule.forRoot(), 39 | NestConfigModule.forRoot({ 40 | isGlobal: true, 41 | }), 42 | MulterModule.register({ 43 | dest: './upload', 44 | limits: { fieldSize: 15 * 1024 * 1024 }, 45 | }), 46 | TypeOrmModule.forRootAsync(typeOrmAsyncModule), 47 | MailerModule.forRootAsync(mailerAsyncModule), 48 | SecurityModule, 49 | UserModule, 50 | CourseModule, 51 | MessageModule, 52 | UploadModule, 53 | DashboardModule, 54 | GameficationModule, 55 | NotificationModule, 56 | ], 57 | }) 58 | export class AppModule {} 59 | -------------------------------------------------------------------------------- /src/UserModule/dto/user-update.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entity/user.entity'; 2 | import { IsDate, IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; 3 | import { Expose, Transform } from 'class-transformer'; 4 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 5 | import { GenderEnum } from '../enum/gender.enum'; 6 | import { EscolarityEnum } from '../enum/escolarity.enum'; 7 | import { UserProfileEnum } from '../enum/user-profile.enum'; 8 | 9 | export class UserUpdateDTO { 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | id: User['id']; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | name: User['name']; 19 | 20 | @IsNotEmpty() 21 | @IsEnum(UserProfileEnum) 22 | @Expose() 23 | profile: User['profile']; 24 | 25 | @IsNotEmpty() 26 | @IsString() 27 | @Expose() 28 | email: User['email']; 29 | 30 | @IsOptional() 31 | @IsString() 32 | @Expose() 33 | nickname?: string; 34 | 35 | @Transform((date) => date && new Date(date)) 36 | @IsOptional() 37 | @IsDate() 38 | @Expose() 39 | birthday?: Date; 40 | 41 | @IsOptional() 42 | @IsEnum(GenderEnum) 43 | @Expose() 44 | gender?: GenderEnum; 45 | 46 | @IsOptional() 47 | @IsEnum(EscolarityEnum) 48 | @Expose() 49 | schooling?: EscolarityEnum; 50 | 51 | @IsOptional() 52 | @IsString() 53 | @Expose() 54 | institutionName?: string; 55 | 56 | @IsOptional() 57 | @IsString() 58 | @Expose() 59 | profession?: string; 60 | 61 | @IsOptional() 62 | @IsString() 63 | @Expose() 64 | address?: string; 65 | 66 | @IsOptional() 67 | @IsString() 68 | @Expose() 69 | city?: string; 70 | 71 | @IsOptional() 72 | @IsString() 73 | @Expose() 74 | phone?: string; 75 | 76 | @IsOptional() 77 | @IsString() 78 | @Expose() 79 | cep?: string; 80 | 81 | @IsOptional() 82 | @IsString() 83 | @Expose() 84 | complement?: string; 85 | 86 | @IsOptional() 87 | @IsString() 88 | @Expose() 89 | houseNumber?: string; 90 | 91 | @IsOptional() 92 | @IsString() 93 | @Expose() 94 | state?: string; 95 | 96 | @IsOptional() 97 | @IsString() 98 | @Expose() 99 | urlFacebook?: User['urlFacebook']; 100 | 101 | @IsOptional() 102 | @IsString() 103 | @Expose() 104 | urlInstagram?: User['urlInstagram']; 105 | } 106 | -------------------------------------------------------------------------------- /src/CommonsModule/guard/role.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | HttpService, 5 | Injectable, 6 | InternalServerErrorException, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { Reflector } from '@nestjs/core'; 10 | import { JwtService } from '@nestjs/jwt'; 11 | import axios, { AxiosError } from 'axios'; 12 | import { User } from '../../UserModule/entity/user.entity'; 13 | import { AppConfigService as ConfigService } from '../../ConfigModule/service/app-config.service'; 14 | 15 | @Injectable() 16 | export class RoleGuard implements CanActivate { 17 | constructor( 18 | private readonly reflector: Reflector, 19 | private readonly jwtService: JwtService, 20 | private readonly configService: ConfigService, 21 | private readonly httpService: HttpService, 22 | ) {} 23 | 24 | async canActivate(context: ExecutionContext): Promise { 25 | const roles: string[] = 26 | this.reflector.get('roles', context.getHandler()) || []; 27 | const policies: string[] = 28 | this.reflector.get('policies', context.getHandler()) || []; 29 | if ((!roles && !policies) || (!roles.length && !policies.length)) { 30 | return true; 31 | } 32 | 33 | const request = context.switchToHttp().getRequest(); 34 | 35 | const authorizationHeader = request.headers.authorization; 36 | 37 | if (!authorizationHeader) return false; 38 | 39 | let user: User; 40 | try { 41 | const { data } = await this.httpService 42 | .post(this.configService.securityOauthTokenDetailsUrl, null, { 43 | headers: { authorization: authorizationHeader }, 44 | }) 45 | .toPromise(); 46 | user = data; 47 | } catch (e) { 48 | if (axios.isAxiosError(e)) { 49 | const error: AxiosError = e; 50 | if (error.response.status === 401) throw new UnauthorizedException(); 51 | throw new InternalServerErrorException(); 52 | } 53 | throw new InternalServerErrorException(); 54 | } 55 | 56 | const hasRoles = roles.length 57 | ? roles.some((role) => role === user.role.name) 58 | : true; 59 | 60 | const hasPolicies = policies.length 61 | ? policies.some((policy) => 62 | user.role.policies.some((userPolicy) => userPolicy.name === policy), 63 | ) 64 | : true; 65 | 66 | return hasRoles || hasPolicies; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.22.1-alpine 2 | 3 | # set our node environment, either development or production 4 | # defaults to production, compose overrides this to development on build and run 5 | ARG NODE_ENV=production 6 | ENV NODE_ENV $NODE_ENV 7 | 8 | # default to port 3000 for node, and 9229 and 9230 (tests) for debug 9 | ARG PORT=8080 10 | ENV PORT $PORT 11 | EXPOSE $PORT 9229 9230 12 | 13 | # you'll likely want the latest npm, regardless of node version, for speed and fixes 14 | # but pin this version for the best stability 15 | RUN npm i npm@7.5.2 -g 16 | 17 | # install dependencies first, in a different location for easier app bind mounting for local development 18 | # due to default /opt permissions we have to create the dir with root and change perms 19 | RUN mkdir /opt/node_app && chown node:node /opt/node_app 20 | # the official node image provides an unprivileged user as a security best practice 21 | # but we have to manually enable it. We put it here so npm installs dependencies as the same 22 | # user who runs the app. 23 | # https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user 24 | USER node 25 | WORKDIR /opt/node_app 26 | COPY --chown=node:node package*.json package-lock.json* ./ 27 | RUN npm config list \ 28 | && npm ci \ 29 | && npm cache clean --force 30 | ENV PATH /opt/node_app/node_modules/.bin:$PATH 31 | 32 | FROM base as source 33 | # copy in our source code last, as it changes the most 34 | COPY --chown=node:node . ./app 35 | 36 | FROM source as prod 37 | # check every 30s to ensure this service returns HTTP 200 38 | COPY --chown=node:node ./dist ./app/dist 39 | 40 | # if you want to use npm start instead, then use `docker run --init in production` 41 | # so that signals are passed properly. Note the code in index.js is needed to catch Docker signals 42 | # using node here is still more graceful stopping then npm with --init afaik 43 | # I still can't come up with a good production way to run with npm and graceful shutdown 44 | CMD [ "node", "./app/dist/src/main" ] 45 | 46 | ## Stage 2 (development) 47 | # we don't COPY in this stage because for dev you'll bind-mount anyway 48 | # this saves time when building locally for dev via docker-compose 49 | FROM source as dev 50 | ENV NODE_ENV=development 51 | ENV PATH /opt/node_app/node_modules/.bin:$PATH 52 | RUN npm install --only=development \ 53 | && npm cache clean --force 54 | WORKDIR /opt/node_app/app 55 | CMD ["npm", "run", "start:debug"] 56 | -------------------------------------------------------------------------------- /src/UserModule/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../entity/user.entity'; 2 | import { Expose, Transform, Type } from 'class-transformer'; 3 | import { RoleDTO } from '../../SecurityModule/dto/role.dto'; 4 | import { IsDate, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; 5 | import { GenderEnum } from '../enum/gender.enum'; 6 | import { EscolarityEnum } from '../enum/escolarity.enum'; 7 | import { UserProfileEnum } from '../enum/user-profile.enum'; 8 | 9 | export class UserDTO { 10 | @IsNotEmpty() 11 | @IsString() 12 | @Expose() 13 | id: User['id']; 14 | 15 | @IsNotEmpty() 16 | @IsString() 17 | @Expose() 18 | name: User['name']; 19 | 20 | @IsNotEmpty() 21 | @IsString() 22 | @Expose() 23 | email: User['email']; 24 | 25 | @IsNotEmpty() 26 | @IsEnum(UserProfileEnum) 27 | @Expose() 28 | profile: User['profile']; 29 | 30 | @IsNotEmpty() 31 | @IsString() 32 | @Expose() 33 | nickname?: string; 34 | 35 | @Transform((date) => date && new Date(date)) 36 | @IsNotEmpty() 37 | @IsDate() 38 | @Expose() 39 | birthday?: Date; 40 | 41 | @IsNotEmpty() 42 | @IsEnum(GenderEnum) 43 | @Expose() 44 | gender?: GenderEnum; 45 | 46 | @IsOptional() 47 | @IsNumber() 48 | @Expose() 49 | inviteKey: string; 50 | 51 | @IsOptional() 52 | @IsNumber() 53 | @Expose() 54 | invitedByUserId: string; 55 | 56 | @IsNotEmpty() 57 | @IsEnum(EscolarityEnum) 58 | @Expose() 59 | schooling?: EscolarityEnum; 60 | 61 | @IsNotEmpty() 62 | @IsString() 63 | @Expose() 64 | institutionName?: string; 65 | 66 | @IsNotEmpty() 67 | @IsString() 68 | @Expose() 69 | profession?: string; 70 | 71 | @IsNotEmpty() 72 | @IsString() 73 | @Expose() 74 | phone?: string; 75 | 76 | @IsNotEmpty() 77 | @IsString() 78 | @Expose() 79 | cep?: string; 80 | 81 | @IsNotEmpty() 82 | @IsString() 83 | @Expose() 84 | complement?: string; 85 | 86 | @IsNotEmpty() 87 | @IsString() 88 | @Expose() 89 | houseNumber?: string; 90 | 91 | @IsNotEmpty() 92 | @IsString() 93 | @Expose() 94 | address?: string; 95 | 96 | @IsNotEmpty() 97 | @IsString() 98 | @Expose() 99 | city?: string; 100 | 101 | @IsNotEmpty() 102 | @IsString() 103 | @Expose() 104 | state?: string; 105 | 106 | @IsOptional() 107 | @IsString() 108 | @Expose() 109 | urlFacebook?: User['urlFacebook']; 110 | 111 | @IsOptional() 112 | @IsString() 113 | @Expose() 114 | urlInstagram?: User['urlInstagram']; 115 | 116 | @Type(() => RoleDTO) 117 | @IsNotEmpty() 118 | @Expose() 119 | role: RoleDTO; 120 | 121 | @Expose() 122 | photo: string; 123 | } 124 | -------------------------------------------------------------------------------- /src/coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | switch (event.which) { 67 | case 78: // n 68 | case 74: // j 69 | goToNext(); 70 | break; 71 | case 66: // b 72 | case 75: // k 73 | case 80: // p 74 | goToPrevious(); 75 | break; 76 | } 77 | }; 78 | })(); 79 | window.addEventListener('keydown', jumpToCode); 80 | -------------------------------------------------------------------------------- /src/NotificationModule/service/notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { NotificationRepository } from '../repository/notification.repository'; 3 | import { NotificationTypeEnum } from '../enum/notification-type.enum'; 4 | import { Notification } from '../entity/notification.entity'; 5 | import { User } from '../../UserModule/entity/user.entity'; 6 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 7 | import { CreateNotificationDTO } from '../dto/create-notification.dto'; 8 | 9 | @Injectable() 10 | export class NotificationService { 11 | constructor(private readonly repository: NotificationRepository) {} 12 | 13 | public async create< 14 | T = { new () } | Record 15 | >( 16 | user: User, 17 | type: NotificationTypeEnum, 18 | content: T, 19 | options = { important: false }, 20 | ): Promise> { 21 | return this.repository.save({ user, type, content, ...options }); 22 | } 23 | 24 | public async getNotificationsByUser(user: User): Promise { 25 | return this.repository.find({ where: user }); 26 | } 27 | 28 | public async getNotificationsByUserId( 29 | userId: string, 30 | order: OrderEnum, 31 | enabled: boolean, 32 | seen: boolean, 33 | ): Promise { 34 | return this.repository.getNotificationsByUserId( 35 | userId, 36 | order, 37 | enabled, 38 | seen, 39 | ); 40 | } 41 | 42 | public async getSemearNotSeenAndEanbledNotificationByUserId( 43 | userId: string, 44 | ): Promise { 45 | const otherNotSeenNotifications = await this.repository.getOtherNotificationsByUserId( 46 | userId, 47 | ); 48 | return otherNotSeenNotifications.find((notification) => { 49 | const content = JSON.parse(notification.content as string); 50 | return content.semearSiteUrl != null; 51 | }); 52 | } 53 | 54 | public async setSeeNotification(id: string): Promise { 55 | const notification = await this.findById(id); 56 | await this.repository.save({ ...notification, enabled: false, seen: true }); 57 | } 58 | 59 | public async findById(id: string): Promise { 60 | const response: Notification[] = await this.repository.find({ id }); 61 | if (!response.length) { 62 | throw new NotFoundException('Notification not found'); 63 | } 64 | return response[0]; 65 | } 66 | 67 | public async addNotificationToUser( 68 | userId: string, 69 | notification: CreateNotificationDTO, 70 | ): Promise { 71 | return this.repository.save({ 72 | ...notification, 73 | user: { id: userId }, 74 | seen: false, 75 | enabled: true, 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/GameficationModule/service/gamefication.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PublisherService } from './publisher.service'; 3 | import { StartEventRules } from '../dto/start-event-rules.dto'; 4 | import { StartEventEnum } from '../enum/start-event.enum'; 5 | import { AchievementRepository } from '../repository/achievement.repository'; 6 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 7 | import { TimeRangeEnum } from '../enum/time-range.enum'; 8 | import { RankingDTO } from '../dto/ranking.dto'; 9 | import { UserService } from '../../UserModule/service/user.service'; 10 | import { UploadService } from '../../UploadModule/service/upload.service'; 11 | import { Pageable } from '../../CommonsModule/dto/pageable.dto'; 12 | import { RankingQueryDTO } from '../dto/ranking-query.dto'; 13 | 14 | @Injectable() 15 | export class GameficationService { 16 | constructor( 17 | private readonly publisherService: PublisherService, 18 | private readonly achivementRepository: AchievementRepository, 19 | private readonly userService: UserService, 20 | private readonly uploadService: UploadService, 21 | ) {} 22 | 23 | startEvent(event: StartEventEnum, rule: StartEventRules): void { 24 | this.publisherService.startEvent(event, rule); 25 | } 26 | 27 | async getRanking( 28 | order: OrderEnum, 29 | timeRange: TimeRangeEnum, 30 | limit: number, 31 | page: number, 32 | institutionName?: string, 33 | city?: string, 34 | state?: string, 35 | ): Promise> { 36 | const result: Pageable = await this.achivementRepository.getRankingPaginated( 37 | order, 38 | timeRange, 39 | limit, 40 | page, 41 | institutionName, 42 | city, 43 | state, 44 | ); 45 | 46 | let rankedUsers = []; 47 | 48 | for (const rankedUser of result.content) { 49 | const { photoPath, ...rankedUserInfo } = rankedUser; 50 | const user: RankingDTO = { 51 | ...rankedUserInfo, 52 | photo: photoPath 53 | ? await this.uploadService.getUserPhoto(photoPath) 54 | : null, 55 | }; 56 | rankedUsers = [...rankedUsers, user]; 57 | } 58 | 59 | return { ...result, content: rankedUsers }; 60 | } 61 | 62 | public async getUserRanking( 63 | userId: string, 64 | options: { 65 | timeRange; 66 | institutionName; 67 | city; 68 | state; 69 | }, 70 | ): Promise { 71 | await this.userService.findById(userId); 72 | return this.achivementRepository.getUserRanking(userId, options); 73 | } 74 | 75 | public async getUserTotalPoints( 76 | userId: string, 77 | ): Promise { 78 | await this.userService.findById(userId); 79 | return this.achivementRepository.getUserTotalPoints(userId); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @NewSchool/back 2 | 3 | > Backend da NewSchool, desenvolvido em NodeJS 4 | > Banco de Dados MySQL 5 | 6 | # Como rodar o projeto 7 | 8 | Você pode rodar o projeto direto da sua máquina. 9 | 10 | 1 - Tenha a versão 12 do Node.js instalada em sua máquina. 11 | 12 | 2 - Faça uma cópia do arquivo **.env.example** para **.env** 13 | 14 | 3- Instale as depedências do projeto 15 | 16 | > **npm install** 17 | 18 | 4- Inicie a aplicação 19 | 20 | > **npm run start:dev** 21 | 22 | 5- Veja o swagger pelo navegador 23 | 24 | > **http://localhost:8080/swagger** 25 | 26 | ## Docker 27 | 28 | Ou, você pode optar por rodar o projeto via Docker. Para isso precisamos 29 | que você tenha instalado o Docker na sua máquina. 30 | Após isso basta rodar o seguinte comando: 31 | 32 | > **docker-compose up** 33 | 34 | E o ambiente de desenvolvimento estará rodando localmente para você. Lembrando 35 | que estamos com o hot reloading no Docker também, ou seja, você não precisará 36 | ficar parando o container e subindo ele novamente a cada mudança que você fizer. 37 | Isso acontecerá automaticamente. 38 | 39 | 5- Para executar os teste E2E execute o script abaixo 40 | 41 | > **npm run test:e2e** 42 | 43 | # Guidelines 44 | 45 | # Como contribuir passo-a-passo 46 | 47 | 1 - CONHEÇA O PROJETO **New School** 48 | 49 | https://youtu.be/u4O8wE0gYO0 50 | 51 | 2 - ENTRE NO SLACK 52 | 53 | https://join.slack.com/t/newschoolgrupo/shared_invite/enQtODQ4NjUyMjAzNTUzLTg3NTJiNmQ1ODE3YzYzMjcyYzVhYTNkZjIzYjViMjI4NTBjYzFiYTc3Njg0ZWI3YTk2MjE5NDY3MDlkYzViOGI 54 | 55 | 2.1 - LÁ NO SLACK, ENTRE NO CANAL #BACKEND 56 | 57 | - Se apresente. Nome, cidade, profissão, e principais habilidades. 58 | - Pergunte sobre as tarefas em aberto. 59 | - Troque uma ideia com o time técnico. Comente como planeja solucionar. Ouça os conselhos dos devs mais experientes. Esse alinhamento é super importante pra aumentar significativamente as chances do seu PULL REQUEST ser aprovado depois. 60 | 61 | 3 - FAÇA PARTE DA EQUIPE NO TRELLO 62 | 63 | https://trello.com/invite/b/2MHuWn0C/b1a15b7112ea11b856cfa78174a6f72d/projeto-new-school-app 64 | 65 | 3.1 - PEGUE UMA TAREFA NO TRELLO. 66 | 67 | - https://trello.com/b/2MHuWn0C/projeto-new-school-app 68 | - Coloque no seu nome e mova para DOING. 69 | 70 | 4 - GITHUB 71 | 72 | 4.1 FAÇA UM FORK DO REPOSITÓRIO 73 | 74 | https://github.com/NewSchoolBR/newschool-backend 75 | 76 | 4.2 ESCREVA CÓDIGO 77 | 78 | Hora de colocar a mão na massa. A parte mais divertida, trabalhar no código-fonte. Depois de concluir e testar, envie e aguarde o PULL REQUEST ser aprovado. 79 | 80 | 5 MISSÃO CUMPRIDA. VC AJUDOU O PROJETO. ❤️ 81 | 82 | ## 🤝 Como contribuir 83 | 84 | Nós precisamos muito de contribuidores! Não importa o seu nível, o que importa é nos ajudar nessa causa. 85 | 86 | Se você quer participar, veja nosso [Guia de contribuição](https://github.com/NewSchoolApp/newschool-backend/blob/develop/CONTRIBUTING.md). 87 | 88 | -------------------------------------------------------------------------------- /src/DashboardModule/controller/dashboard.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Query, UseGuards } from '@nestjs/common'; 2 | import { Constants } from '../../CommonsModule/constants'; 3 | import { NeedPolicies, NeedRoles } from '../../CommonsModule/decorator/role-guard-metadata.decorator'; 4 | import { RoleGuard } from '../../CommonsModule/guard/role.guard'; 5 | import { DashboardService } from '../service/dashboard.service'; 6 | import { UserStatusEnum } from '../enum/UserStatusEnum'; 7 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 8 | import { UserQuantityDTO } from '../dto/user-quantity.dto'; 9 | import { ApiTags } from '@nestjs/swagger'; 10 | import { CertificateQuantityDTO } from '../dto/certificate-quantity.dto'; 11 | import { CourseTakenStatusEnum } from '../../CourseModule/enum/course-taken-status.enum'; 12 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 13 | import { CourseTakenUsersDTO } from '../dto/course-taken-users.dto'; 14 | import { getCoursesByFinished } from '../interfaces/getCoursesByFinished'; 15 | 16 | @ApiTags('Dashboard') 17 | @Controller( 18 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.DASHBOARD_ENDPOINT}`, 19 | ) 20 | export class DashboardController { 21 | constructor(private service: DashboardService) {} 22 | 23 | @Get('/user/quantity') 24 | @UseGuards(RoleGuard) 25 | @NeedRoles(RoleEnum.ADMIN) 26 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_USER_QUANTITY`) 27 | public async getUserQuantity( 28 | @Query('status') status?: UserStatusEnum, 29 | ): Promise { 30 | return { totalElements: await this.service.getUserQuantity(status) }; 31 | } 32 | 33 | @Get('/certificate/quantity') 34 | @UseGuards(RoleGuard) 35 | @NeedRoles(RoleEnum.ADMIN) 36 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_CERTIFICATE_QUANTITY`) 37 | public async getCertificateQuantity(): Promise { 38 | return { totalElements: await this.service.getCertificateQuantity() }; 39 | } 40 | 41 | @Get('/course-taken/user/quantity') 42 | @UseGuards(RoleGuard) 43 | @NeedRoles(RoleEnum.ADMIN) 44 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_USERS_IN_COURSE_QUANTITY`) 45 | public async getUsersInCourseQuantity( 46 | @Query('status') status?: CourseTakenStatusEnum, 47 | ): Promise { 48 | return { 49 | totalElements: await this.service.getUsersInCourseQuantity(status), 50 | }; 51 | } 52 | 53 | @Get('/course/views') 54 | @UseGuards(RoleGuard) 55 | @NeedRoles(RoleEnum.ADMIN) 56 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_COURSES_FREQUENCY`) 57 | public async getCoursesByViews( 58 | @Query('order') order: OrderEnum = OrderEnum.ASC, // Indica que order precisa ter um dos tipos presentes no enum. Se não, recebe o valor 'ASC' 59 | @Query('limit') limit = 10, 60 | ): Promise { 61 | return await this.service.getCoursesByFinished(order, limit); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/CourseModule/controllers/comment.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Param, ParseIntPipe, Post, Query, UseGuards } from '@nestjs/common'; 2 | import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; 3 | import { Constants } from '../../CommonsModule/constants'; 4 | import { CommentService } from '../service/v1/comment.service'; 5 | import { AddCommentDTO } from '../dto/add-comment.dto'; 6 | import { CommentDTO } from '../dto/comment.dto'; 7 | import { NeedPolicies, NeedRoles } from '../../CommonsModule/decorator/role-guard-metadata.decorator'; 8 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 9 | import { RoleGuard } from '../../CommonsModule/guard/role.guard'; 10 | import { ResponseDTO } from '../dto/response.dto'; 11 | import { ClapCommentDTO } from '../dto/clap-comment.dto'; 12 | import { OrderEnum } from '../../CommonsModule/enum/order.enum'; 13 | 14 | @ApiTags('Comment') 15 | @ApiBearerAuth() 16 | @Controller( 17 | `${Constants.API_PREFIX}/${Constants.API_VERSION_1}/${Constants.COMMENT_ENDPOINT}`, 18 | ) 19 | export class CommentController { 20 | constructor(private readonly service: CommentService) {} 21 | 22 | @Post() 23 | @UseGuards(RoleGuard) 24 | @NeedRoles(RoleEnum.STUDENT) 25 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/ADD_COMMENT`) 26 | public async addComment(@Body() comment: AddCommentDTO): Promise { 27 | return await this.service.addComment( 28 | comment.partId, 29 | comment.userId, 30 | comment.text, 31 | ); 32 | } 33 | 34 | @Get(':id/response') 35 | @UseGuards(RoleGuard) 36 | @NeedRoles(RoleEnum.STUDENT) 37 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_COMMENT_RESPONSES`) 38 | public async getCommentResponses( 39 | @Param('id') id: string, 40 | ): Promise { 41 | return this.service.getCommentResponses(id); 42 | } 43 | 44 | @Post(':id/response') 45 | @UseGuards(RoleGuard) 46 | @NeedRoles(RoleEnum.STUDENT) 47 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/ADD_COMMENT_RESPONSE`) 48 | public async addCommentResponse( 49 | @Param('id') id: string, 50 | @Body() response: AddCommentDTO, 51 | ): Promise { 52 | return await this.service.addCommentResponse( 53 | id, 54 | response.partId, 55 | response.userId, 56 | response.text, 57 | ); 58 | } 59 | 60 | @Get('part/:partId') 61 | @UseGuards(RoleGuard) 62 | @NeedRoles(RoleEnum.STUDENT) 63 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/GET_COMMENTS_BY_PART_ID`) 64 | public async getCommentsByPartId( 65 | @Param('partId', ParseIntPipe) partId: number, 66 | @Query('order') order: OrderEnum = OrderEnum.DESC, 67 | @Query('orderBy') orderBy: 'claps' | 'createdAt' = 'claps', 68 | ): Promise { 69 | return await this.service.findPartComments(partId, { order, orderBy }); 70 | } 71 | 72 | @Post(':id/clap') 73 | @UseGuards(RoleGuard) 74 | @NeedRoles(RoleEnum.STUDENT) 75 | @NeedPolicies(`${Constants.POLICIES_PREFIX}/CLAP_COMMENT`) 76 | public async clapComment( 77 | @Param('id') id: string, 78 | @Body() body: ClapCommentDTO, 79 | ): Promise { 80 | await this.service.clapComment(id, body.userId, body.claps); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/CourseModule/course.module.ts: -------------------------------------------------------------------------------- 1 | import { CacheModule, forwardRef, HttpModule, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { UserModule } from '../UserModule/user.module'; 4 | import { GameficationModule } from '../GameficationModule/gamefication.module'; 5 | import { CourseTakenMapper } from './mapper/course-taken.mapper'; 6 | import { CourseTaken } from './entity/course-taken.entity'; 7 | import { CourseTakenRepository } from './repository/course.taken.repository'; 8 | import { CommentRepository } from './repository/comment.repository'; 9 | import { UserLikedCommentRepository } from './repository/user-liked-comment.repository'; 10 | import { CommentController } from './controllers/comment.controller'; 11 | import { CommentMapper } from './mapper/comment.mapper'; 12 | import { CommentService } from './service/v1/comment.service'; 13 | import { Comment } from './entity/comment.entity'; 14 | import { UserLikedComment } from './entity/user-liked-comment.entity'; 15 | import { UploadModule } from '../UploadModule/upload.module'; 16 | import { CmsIntegration } from './integration/cms.integration'; 17 | import { CourseV2Service } from './service/v2/course-v2.service'; 18 | import { CourseV2Controller } from './controllers/v2/course-v2.controller'; 19 | import { LessonV2Controller } from './controllers/v2/lesson-v2.controller'; 20 | import { LessonV2Service } from './service/v2/lesson-v2.service'; 21 | import { PartV2Service } from './service/v2/part-v2.service'; 22 | import { PartV2Controller } from './controllers/v2/part-v2.controller'; 23 | import { TestV2Service } from './service/v2/test-v2.service'; 24 | import { TestV2Controller } from './controllers/v2/test-v2.controller'; 25 | import { CourseTakenV2Controller } from './controllers/v2/course-taken-v2.controller'; 26 | import { CourseTakenV2Service } from './service/v2/course-taken-v2.service'; 27 | import { CourseTakenService } from './service/v1/course-taken.service'; 28 | import { CmsService } from './service/v2/cms.service'; 29 | import { PilarService } from './service/v2/pilar.service'; 30 | import { TrailService } from './service/v2/trail.service'; 31 | import { HighlightService } from './service/v2/highlight.service'; 32 | import { PilarController } from './controllers/v2/pilar.controller'; 33 | import { TrailController } from './controllers/v2/trail.controller'; 34 | import { HighlightController } from './controllers/v2/highlight.controller'; 35 | 36 | @Module({ 37 | imports: [ 38 | HttpModule, 39 | CacheModule.register(), 40 | TypeOrmModule.forFeature([ 41 | CourseTaken, 42 | CourseTakenRepository, 43 | Comment, 44 | CommentRepository, 45 | UserLikedComment, 46 | UserLikedCommentRepository, 47 | ]), 48 | forwardRef(() => UserModule), 49 | forwardRef(() => GameficationModule), 50 | UploadModule, 51 | ], 52 | controllers: [ 53 | CommentController, 54 | CourseV2Controller, 55 | LessonV2Controller, 56 | PartV2Controller, 57 | TestV2Controller, 58 | CourseTakenV2Controller, 59 | PilarController, 60 | TrailController, 61 | HighlightController 62 | ], 63 | providers: [ 64 | CourseTakenMapper, 65 | CommentMapper, 66 | CommentService, 67 | CmsIntegration, 68 | CourseV2Service, 69 | LessonV2Service, 70 | PartV2Service, 71 | TestV2Service, 72 | CourseTakenV2Service, 73 | CourseTakenService, 74 | CmsService, 75 | PilarService, 76 | TrailService, 77 | HighlightService 78 | ], 79 | exports: [CourseTakenService], 80 | }) 81 | export class CourseModule {} 82 | -------------------------------------------------------------------------------- /src/GameficationModule/service/publisher.service.ts: -------------------------------------------------------------------------------- 1 | import * as PubSub from 'pubsub-js'; 2 | import { REQUEST } from '@nestjs/core'; 3 | import { Request } from 'express'; 4 | import { JwtService } from '@nestjs/jwt'; 5 | import { Inject, Injectable } from '@nestjs/common'; 6 | import { TestTry } from './course-rewards.service'; 7 | import { EventNameEnum } from '../enum/event-name.enum'; 8 | import { User } from '../../UserModule/entity/user.entity'; 9 | import { StartEventEnum } from '../enum/start-event.enum'; 10 | import { StartEventRules } from '../dto/start-event-rules.dto'; 11 | import { RoleEnum } from '../../SecurityModule/enum/role.enum'; 12 | import { InviteUserRewardData } from './user-rewards.service'; 13 | import { CourseTaken } from '../../CourseModule/entity/course-taken.entity'; 14 | import { CourseNpsRewardDTO } from '../dto/course-nps-reward.dto'; 15 | import { CMSTestDTO } from '../../CourseModule/dto/cms-test.dto'; 16 | 17 | @Injectable() 18 | export class PublisherService { 19 | constructor( 20 | @Inject(REQUEST) private request: Request, 21 | private readonly jwtService: JwtService, 22 | ) {} 23 | 24 | public startEvent(eventName: StartEventEnum, rule: StartEventRules): void { 25 | const authorizationHeader = this.request.headers.authorization; 26 | const userStringToken = this.getUserStringToken(authorizationHeader); 27 | const user: User = this.getUserFromToken(userStringToken); 28 | if (user.role.name !== RoleEnum.STUDENT) return; 29 | const events = { 30 | [StartEventEnum.SHARE_COURSE]: EventNameEnum.USER_REWARD_SHARE_COURSE, 31 | [StartEventEnum.RATE_APP]: EventNameEnum.USER_REWARD_RATE_APP, 32 | [StartEventEnum.SHARE_APP]: EventNameEnum.USER_REWARD_SHARE_APP, 33 | }; 34 | const event = events[eventName]; 35 | if (!event) return; 36 | PubSub.publish(event, rule); 37 | } 38 | 39 | public emitInviteUserReward(inviteKey: string): void { 40 | const data: InviteUserRewardData = { 41 | inviteKey, 42 | }; 43 | PubSub.publish(EventNameEnum.USER_REWARD_INVITE_USER, data); 44 | } 45 | 46 | public emitCheckTestReward( 47 | test: CMSTestDTO, 48 | chosenAlternative: string, 49 | ): void { 50 | const authorizationHeader = this.request.headers.authorization; 51 | const userStringToken = this.getUserStringToken(authorizationHeader); 52 | const user: User = this.getUserFromToken(userStringToken); 53 | if (user.role.name !== RoleEnum.STUDENT) return; 54 | 55 | const data: TestTry = { 56 | chosenAlternative: chosenAlternative.toLowerCase(), 57 | userId: user.id, 58 | test, 59 | }; 60 | PubSub.publish(EventNameEnum.COURSE_REWARD_TEST_ON_FIRST_TAKE, data); 61 | } 62 | public emitupdateStudent(id: string): void { 63 | PubSub.publish(EventNameEnum.USER_REWARD_COMPLETE_REGISTRATION, { id }); 64 | } 65 | 66 | public emitNpsReward(userId: string, courseId: number): void { 67 | const data: CourseNpsRewardDTO = { 68 | userId, 69 | courseId, 70 | }; 71 | PubSub.publish(EventNameEnum.COURSE_REWARD_COURSE_NPS, data); 72 | } 73 | 74 | public emitCourseCompleted(course: CourseTaken): void { 75 | PubSub.publish(EventNameEnum.COURSE_REWARD_COMPLETE_COURSE, course); 76 | } 77 | 78 | private getUserStringToken(authorizationHeader: string): string { 79 | const [, userStringToken] = authorizationHeader.split(' '); 80 | return userStringToken; 81 | } 82 | 83 | private getUserFromToken(userStringToken: string): User { 84 | return this.jwtService.verify(userStringToken, { 85 | ignoreExpiration: true, 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribuição 2 | 3 | 🚀 Obrigado por contribuir com a NewSchool! 🚀 4 | 5 | Se você participar, a gente vai entender que você concordou com o nosso [Código de conduta](CODE_OF_CONDUCT.md), ok? 6 | 7 | Se você tiver alguma dúvida que não falamos aqui, você pode falar no nosso [Discord](https://discord.gg/RQfzTZr) 8 | 9 | ## Glossário 10 | 11 | - **ORM**: Biblioteca que abstrai o banco de dados com a aplicação. 12 | 13 | ## Filosofia 14 | 15 | Somos um movimento de educação que está formando novos protagonistas da quebrada por meio de experiências transformadoras de aprendizagem lúdica e prática. 16 | 17 | Trabalhamos duro para dar acesso à educação de qualidade na linguagem da quebrada para os jovens das periferias de todo o Brasil através da tecnologia e de um conceito diferenciado de educação. 18 | 19 | ## Tarefas 20 | 21 | Para pegar as tarefas, veja no nosso [Trello](https://trello.com/b/hY1tozEd/ns-squad). Tarefas de backend serão aceitas nesse repositório, enquanto as de frontend devem ser mandadas para [esse repositório](https://github.com/NewSchoolApp/newschool-frontend) 22 | 23 | ## Arquitetura 24 | 25 | As tecnologias que usamos são: 26 | 27 | - [NodeJS](https://nodejs.org/) como linguagem 28 | - [NestJS](https://nestjs.com/) como framework 29 | - [TypeORM](https://typeorm.io/#/) como ORM pra banco de dados 30 | - MySQL como banco de dados 31 | 32 | ## Variáveis de ambiente 33 | 34 | Como projeto Open Source, tentamos fazer um ambiente fácil para que todo mundo possa contribuir com o mínimo de esforço possível. 35 | 36 | Quando você fizer `clone` do projeto, você vai ver um arquivo chamado `.env.example`. Esse arquivo é um arquivo com variáveis de ambiente que podem ser usadas 37 | por todo mundo que contribuir. Para rodar o projeto, crie um novo arquivo chamado `.env`, copie todo o conteúdo do `.env.example` e cole dentro do seu criado `.env` 38 | 39 | ## Linter e Formatador 40 | 41 | Usamos o ESLint e Prettier para garantir que nosso código siga o mesmo padrão. Sempre que você der `git commit`, o projeto irá rodar o prettier com o eslint 42 | e formatar todos os arquivos que estejam fora do nosso padrão. Porém, se você quiser rodar por conta, use o seguinte comando: 43 | 44 | ```shell script 45 | npm run lint # Isso roda o linter pra dizer o que seu código tem de errado 46 | npm run format # Isso roda o prettier pra formatar os arquivos 47 | ``` 48 | 49 | ## Testes 50 | 51 | No back-end, usamos testes de integração para garantir que está tudo funcionando. Nossos testes se conectam com uma base de dados, e a configuração está no arquivo 52 | `jest-e2e.config.js` 53 | 54 | Como conceito de todo teste, precisamos testar todos os fluxos que podemos ter, então, tente mapear os fluxos possíveis na usa tarefa e faça testes para cobrir eles. 55 | 56 | Na pasta `test`, temos alguns testes já feitos, de uma olhada em como eles funcionam e se tiver alguma dúvida, não deixe de mandar no nosso [Discord](https://discord.gg/rb5sDG) 57 | 58 | ## Git/GitHub workflow 59 | 60 | Esse é o processo que fazemos aqui na NewSchool 61 | 62 | 1. Fork esse repositório 63 | 2. Você provavelmente vai iniciar na branch `master`, troque pra branch `develop` e rode `npm install` 64 | 3. Crie uma branch a partir da `develop` com esse comando: `git checkout -b feature/` 65 | 4. Faça sua tarefa baseado na sua task do Trello e não esqueça de ir dando `git commit` durante isso 66 | 5. Quando sua tarefa estiver pronta, se você ainda não deu `git push`, use o comando `git push origin feature/` para subir seu código para o seu fork 67 | 6. Crie um pull request da sua branch para a branch `develop` 68 | 7. Não precisa marcar ninguém na Pull Request. Não se preocupe, veremos seu Pull Request quando der :smile: 69 | 8. Quando revisarmos e se estiver tudo certo, nós iremos mergear para você 70 | -------------------------------------------------------------------------------- /src/coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | Unknown% 27 | Statements 28 | 0/0 29 |
30 | 31 | 32 |
33 | Unknown% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | Unknown% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | Unknown% 48 | Lines 49 | 0/0 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
FileStatementsBranchesFunctionsLines
77 |
78 |
79 |
80 | 85 | 86 | 87 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "back", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "nest build", 9 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 10 | "start": "nest start", 11 | "start:dev": "nest start --watch", 12 | "start:debug": "nest start --debug 0.0.0.0:9229 --watch", 13 | "start:prod": "node dist/main", 14 | "lint": "eslint \"src/**/*.ts\"", 15 | "test": "jest", 16 | "test:watch": "jest --watch", 17 | "test:cov": "jest --coverage", 18 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 19 | "test:e2e": "jest --config ./test/jest-e2e.config.js --runInBand", 20 | "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/ormconfig.ts", 21 | "typeorm:create-migration": "npm run typeorm migration:create -- -n", 22 | "typeorm:generate-migration": "npm run typeorm migration:generate -- -n", 23 | "typeorm:run-migration": "npm run typeorm migration:run", 24 | "typeorm:revert-migration": "npm run typeorm migration:revert" 25 | }, 26 | "dependencies": { 27 | "@nest-modules/mailer": "1.3.22", 28 | "@nestjs/common": "7.6.15", 29 | "@nestjs/config": "0.6.3", 30 | "@nestjs/core": "7.6.15", 31 | "@nestjs/jwt": "7.2.0", 32 | "@nestjs/platform-express": "7.6.15", 33 | "@nestjs/schedule": "0.4.3", 34 | "@nestjs/swagger": "4.8.0", 35 | "@nestjs/typeorm": "7.1.5", 36 | "@sentry/node": "6.3.0", 37 | "@sentry/tracing": "6.3.0", 38 | "aws-sdk": "2.889.0", 39 | "cache-manager": "3.4.3", 40 | "class-transformer": "0.3.1", 41 | "class-validator": "0.13.1", 42 | "dotenv": "8.2.0", 43 | "dotenv-flow": "3.2.0", 44 | "export-from-json": "1.3.5", 45 | "mysql": "2.18.1", 46 | "nest-raven": "7.2.0", 47 | "nodemailer": "6.5.0", 48 | "passport-facebook-token": "4.0.0", 49 | "passport-jwt": "4.0.0", 50 | "pubsub-js": "1.9.3", 51 | "pusher": "5.0.0", 52 | "reflect-metadata": "0.1.13", 53 | "remove": "0.1.5", 54 | "rimraf": "3.0.2", 55 | "rxjs": "6.6.7", 56 | "secure-password": "4.0.0", 57 | "slugify": "1.5.0", 58 | "swagger-ui-express": "4.1.6", 59 | "typeorm": "0.2.32", 60 | "typeorm-transactional-cls-hooked": "0.1.20", 61 | "uuid": "8.3.2" 62 | }, 63 | "devDependencies": { 64 | "@nestjs/cli": "7.6.0", 65 | "@nestjs/schematics": "7.3.1", 66 | "@nestjs/testing": "7.6.15", 67 | "@types/dotenv": "8.2.0", 68 | "@types/express": "4.17.11", 69 | "@types/jest": "26.0.22", 70 | "@types/multer": "1.4.5", 71 | "@types/mysql": "2.15.18", 72 | "@types/node": "13.13.50", 73 | "@types/nodemailer": "6.4.1", 74 | "@types/passport-jwt": "3.0.5", 75 | "@types/pubsub-js": "1.8.2", 76 | "@types/secure-password": "3.1.0", 77 | "@types/supertest": "2.0.11", 78 | "@typescript-eslint/eslint-plugin": "4.22.0", 79 | "@typescript-eslint/parser": "4.22.0", 80 | "axios": "0.21.1", 81 | "eslint": "7.24.0", 82 | "eslint-config-prettier": "8.2.0", 83 | "husky": "6.0.0", 84 | "jest": "26.6.3", 85 | "prettier": "2.2.1", 86 | "supertest": "6.1.3", 87 | "ts-jest": "26.5.5", 88 | "ts-loader": "9.0.2", 89 | "ts-node": "9.1.1", 90 | "tsconfig-paths": "3.9.0", 91 | "typescript": "4.2.4" 92 | }, 93 | "jest": { 94 | "moduleFileExtensions": [ 95 | "js", 96 | "json", 97 | "ts" 98 | ], 99 | "rootDir": "src", 100 | "testRegex": ".spec.ts$", 101 | "transform": { 102 | "^.+\\.(t|j)s$": "ts-jest" 103 | }, 104 | "coverageDirectory": "./coverage", 105 | "testEnvironment": "node" 106 | }, 107 | "husky": { 108 | "hooks": { 109 | "pre-commit": "npm run format" 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------