├── k8s ├── templates │ ├── NOTES.txt │ ├── configmap.yaml │ ├── secret.yaml │ ├── service.yaml │ ├── _helpers.tpl │ └── ingress.yaml ├── README.md ├── .helmignore ├── cicd-config.yaml ├── Chart.yaml └── values-circleci.yaml ├── .dockerignore ├── src ├── __mocks__ │ └── uuid.ts ├── constants │ ├── roles.ts │ ├── permissions │ │ ├── courses.ts │ │ ├── categories.ts │ │ ├── enrolments.ts │ │ ├── permissions.ts │ │ ├── users.ts │ │ ├── roles.ts │ │ └── index.ts │ └── routes.ts ├── utils │ ├── errors │ │ ├── http │ │ │ ├── NotFoundError.ts │ │ │ ├── ServiceUnavailableError.ts │ │ │ └── ConflictError.ts │ │ ├── auth │ │ │ ├── ForbiddenError.ts │ │ │ ├── LockedAccountError.ts │ │ │ ├── UnauthorizedError.ts │ │ │ ├── VerifyLockoutError.ts │ │ │ ├── ExpiredJwtTokenError.ts │ │ │ ├── InvalidJwtTokenError.ts │ │ │ ├── MissingJwtTokenError.ts │ │ │ ├── InvalidCredentialsError.ts │ │ │ ├── UndefinedJWTSecretError.ts │ │ │ ├── UnverifiedAccountError.ts │ │ │ ├── MissingJwtTokenExtractorError.ts │ │ │ ├── RemindPasswordError.ts │ │ │ ├── AccountAlreadyVerifiedError.ts │ │ │ ├── ExpiredResetPasswordTokenError.ts │ │ │ ├── InvalidResetPasswordTokenError.ts │ │ │ └── InvalidVerifyAccountTokenError.ts │ │ └── validation │ │ │ ├── EmailValidationError.ts │ │ │ ├── DateValidationError.ts │ │ │ ├── PasswordValidationError.ts │ │ │ ├── EnumValidationError.ts │ │ │ └── MatchValidationError.ts │ ├── helpers │ │ ├── commons │ │ │ ├── isString │ │ │ │ └── index.ts │ │ │ └── isDate │ │ │ │ └── index.ts │ │ ├── config │ │ │ ├── getNumberValue │ │ │ │ ├── index.ts │ │ │ │ └── index.spec.ts │ │ │ ├── getStringValue │ │ │ │ ├── index.ts │ │ │ │ └── index.spec.ts │ │ │ └── getBooleanValue │ │ │ │ └── index.ts │ │ ├── model │ │ │ ├── getVisibleRolesProperties │ │ │ │ └── index.ts │ │ │ ├── getVisiblePublicUserProperties │ │ │ │ └── index.ts │ │ │ └── getVisibleUserProperties │ │ │ │ └── index.ts │ │ ├── auth │ │ │ ├── hashPassword │ │ │ │ └── index.ts │ │ │ ├── verifyPassword │ │ │ │ └── index.ts │ │ │ ├── createExtractTokenFromRequest │ │ │ │ └── extractors │ │ │ │ │ ├── createHeaderExtractor │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createBodyFieldExtractor │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createQueryParamExtractor │ │ │ │ │ └── index.ts │ │ │ │ │ └── createAuthSchemeExtractor │ │ │ │ │ └── index.ts │ │ │ ├── generateToken │ │ │ │ └── index.ts │ │ │ └── verifyToken │ │ │ │ └── index.ts │ │ ├── date │ │ │ ├── getUtcDate │ │ │ │ └── index.ts │ │ │ ├── isInTheFuture │ │ │ │ └── index.ts │ │ │ ├── fastForwardTimeBy │ │ │ │ └── index.ts │ │ │ └── lessThanAgo │ │ │ │ └── index.ts │ │ ├── url │ │ │ ├── getResetPasswordUrl │ │ │ │ └── index.ts │ │ │ └── getVerifyEmailUrl │ │ │ │ └── index.ts │ │ └── math │ │ │ └── incrementOrInitialise │ │ │ └── index.ts │ ├── converters │ │ ├── helpers │ │ │ ├── itemToDocumentBoolean │ │ │ │ └── index.ts │ │ │ ├── documentToItemNumber │ │ │ │ └── index.ts │ │ │ ├── documentToItemBoolean │ │ │ │ └── index.ts │ │ │ ├── documentToItemDate │ │ │ │ └── index.ts │ │ │ ├── documentToItemDateTime │ │ │ │ └── index.ts │ │ │ └── itemToDocumentDateTime │ │ │ │ └── index.ts │ │ ├── convertersMaps │ │ │ └── baseMap.ts │ │ ├── baseConverter │ │ │ ├── itemToDocument.ts │ │ │ └── documentToItem.ts │ │ ├── sections │ │ │ ├── itemToDocument.ts │ │ │ └── documentToItem.ts │ │ ├── resetPasswordTokens │ │ │ ├── itemToDocument.ts │ │ │ └── documentToItem.ts │ │ ├── users │ │ │ ├── itemToDocument.ts │ │ │ └── documentToItem.ts │ │ └── recursiveConverter │ │ │ └── index.ts │ └── validation │ │ └── rules │ │ ├── Optional.ts │ │ ├── Email.ts │ │ ├── Password.ts │ │ ├── String.ts │ │ ├── Date.ts │ │ ├── Match.ts │ │ └── Enum.ts ├── types │ ├── items │ │ ├── Role.ts │ │ ├── Category.ts │ │ ├── UserRole.ts │ │ ├── ResetPasswordToken.ts │ │ ├── RolePermission.ts │ │ ├── Permission.ts │ │ ├── Enrolment.ts │ │ ├── Section.ts │ │ ├── BaseItem.ts │ │ ├── Object.ts │ │ ├── Course.ts │ │ └── User.ts │ └── app │ │ └── BaseAppConfig.ts ├── presenter │ ├── express │ │ ├── types │ │ │ └── ExpressHandler.ts │ │ ├── app │ │ │ ├── AppConfig.ts │ │ │ └── index.ts │ │ ├── api │ │ │ ├── v1 │ │ │ │ ├── Options.ts │ │ │ │ └── routes │ │ │ │ │ ├── utils │ │ │ │ │ ├── createPatch │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── beforeGetItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── beforeGetItems │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── convertItemIntoDocument │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── describeApi │ │ │ │ │ │ ├── index.spec.ts │ │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ │ └── index.spec.ts.snap │ │ │ │ │ ├── beforeDeleteItems │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── beforeDeleteItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── beforeCreateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── beforeUpdateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── beforeReplaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── users │ │ │ │ │ └── functions │ │ │ │ │ │ ├── createPatch │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── convertItemIntoDocument │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── convertDocumentIntoItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── beforeCreateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── revokeUserRole │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── assignUserRole │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── sections │ │ │ │ │ ├── functions │ │ │ │ │ │ ├── convertDocumentIntoItem │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── beforeCreateItem │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── factory.ts │ │ │ │ │ ├── courses │ │ │ │ │ ├── functions │ │ │ │ │ │ ├── convertDocumentIntoItem │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── beforeCreateItem │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── factory.ts │ │ │ │ │ ├── categories │ │ │ │ │ └── functions │ │ │ │ │ │ ├── convertDocumentIntoItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── beforeCreateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── enrolments │ │ │ │ │ └── functions │ │ │ │ │ │ ├── convertDocumentIntoItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── beforeCreateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── roles │ │ │ │ │ └── functions │ │ │ │ │ │ ├── convertDocumentIntoItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── revokeRolePermission │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── permissions │ │ │ │ │ ├── functions │ │ │ │ │ │ └── convertDocumentIntoItem │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── factory.ts │ │ │ │ │ ├── auth │ │ │ │ │ ├── verifyAccount │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── factory.ts │ │ │ │ │ └── resetPassword │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── getDiscoveryItems │ │ │ │ │ └── index.ts │ │ │ │ │ └── autocompleteHandler │ │ │ │ │ └── index.ts │ │ │ └── commons │ │ │ │ └── checks │ │ │ │ ├── initFinished │ │ │ │ └── index.ts │ │ │ │ ├── checkDb │ │ │ │ └── index.ts │ │ │ │ └── checkVersion │ │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── schemas │ │ │ │ ├── roles │ │ │ │ │ ├── base │ │ │ │ │ │ └── schema.ts │ │ │ │ │ ├── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── assignRolePermission │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── revokeRolePermission │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ ├── courses │ │ │ │ │ ├── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── base │ │ │ │ │ │ └── schema.ts │ │ │ │ ├── sections │ │ │ │ │ ├── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── base │ │ │ │ │ │ └── schema.ts │ │ │ │ │ └── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ ├── categories │ │ │ │ │ ├── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── base │ │ │ │ │ │ └── schema.ts │ │ │ │ │ └── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ ├── enrolments │ │ │ │ │ ├── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── base │ │ │ │ │ │ └── schema.ts │ │ │ │ ├── permissions │ │ │ │ │ ├── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── base │ │ │ │ │ │ └── schema.ts │ │ │ │ │ └── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ ├── users │ │ │ │ │ ├── assignUserRole │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── revokeUserRole │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── replaceItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── updateItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createItem │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── base │ │ │ │ │ │ └── schema.ts │ │ │ │ └── auth │ │ │ │ │ ├── remindPassword │ │ │ │ │ └── index.ts │ │ │ │ │ ├── resendVerifyToken │ │ │ │ │ └── index.ts │ │ │ │ │ ├── login │ │ │ │ │ └── index.ts │ │ │ │ │ ├── verifyAccount │ │ │ │ │ └── index.ts │ │ │ │ │ ├── resetPassword │ │ │ │ │ └── index.ts │ │ │ │ │ └── register │ │ │ │ │ └── index.ts │ │ │ ├── fakeFactories │ │ │ │ ├── roles │ │ │ │ │ └── factory.ts │ │ │ │ ├── resetPasswordTokens │ │ │ │ │ └── factory.ts │ │ │ │ ├── users │ │ │ │ │ └── factory.ts │ │ │ │ └── index.ts │ │ │ ├── errors │ │ │ │ ├── catchErrors │ │ │ │ │ └── index.ts │ │ │ │ └── handleUnexpectedEvents │ │ │ │ │ ├── index.ts │ │ │ │ │ └── gracefulShutDown │ │ │ │ │ └── index.ts │ │ │ ├── tests │ │ │ │ ├── testData.ts │ │ │ │ ├── smtpServer │ │ │ │ │ └── mail.ts │ │ │ │ └── assertOnResponseAndStatus │ │ │ │ │ └── index.ts │ │ │ ├── translations │ │ │ │ ├── locales │ │ │ │ │ ├── getLocaleFromQueryParam │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── getLocale │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── getAcceptedLanguagesFromHeader.ts │ │ │ │ └── mapValidationErrorsToResponse │ │ │ │ │ └── index.ts │ │ │ └── auth │ │ │ │ └── hasPermission │ │ │ │ └── index.ts │ │ ├── middlewares │ │ │ ├── cors │ │ │ │ └── factory.ts │ │ │ └── rateLimiter │ │ │ │ └── factory.ts │ │ ├── presenterFactory │ │ │ └── Config.ts │ │ └── index.ts │ └── commander │ │ ├── app │ │ ├── AppConfig.ts │ │ └── index.ts │ │ ├── presenterFactory │ │ ├── FactoryConfig.ts │ │ └── factory.ts │ │ ├── index.ts │ │ ├── data │ │ ├── sections.ts │ │ ├── categories.ts │ │ ├── users.ts │ │ └── categories │ │ │ ├── photography.ts │ │ │ ├── personalDevelopment.ts │ │ │ ├── design.ts │ │ │ ├── marketing.ts │ │ │ ├── finance.ts │ │ │ └── business.ts │ │ ├── utils │ │ ├── permissions │ │ │ └── index.ts │ │ └── convertToHtml │ │ │ ├── index.ts │ │ │ └── index.spec.ts │ │ └── functions │ │ └── dbSeed │ │ └── functions │ │ ├── createRoles │ │ └── index.ts │ │ └── connectPermissionsToRoles │ │ └── index.ts ├── repo │ ├── model │ │ ├── knex │ │ │ ├── other │ │ │ │ ├── closeDbConnection │ │ │ │ │ └── index.ts │ │ │ │ └── countPermissions │ │ │ │ │ └── index.ts │ │ │ ├── users │ │ │ │ └── factory.ts │ │ │ ├── roles │ │ │ │ └── factory.ts │ │ │ ├── courses │ │ │ │ └── factory.ts │ │ │ ├── sections │ │ │ │ └── factory.ts │ │ │ ├── categories │ │ │ │ └── factory.ts │ │ │ ├── userRole │ │ │ │ └── factory.ts │ │ │ ├── enrolments │ │ │ │ └── factory.ts │ │ │ ├── permissions │ │ │ │ └── factory.ts │ │ │ ├── rolePermission │ │ │ │ └── factory.ts │ │ │ ├── resetPasswordTokens │ │ │ │ └── factory.ts │ │ │ └── migrations │ │ │ │ └── tables │ │ │ │ ├── createRolesTable.ts │ │ │ │ ├── createCategoriesTable.ts │ │ │ │ ├── createPermissionsTable.ts │ │ │ │ ├── createResetPasswordTokensTable.ts │ │ │ │ ├── createSectionsTable.ts │ │ │ │ ├── createUserRoleTable.ts │ │ │ │ ├── createRolePermissionTable.ts │ │ │ │ └── createEnrolmentsTable.ts │ │ ├── FactoryConfig.ts │ │ └── factory.ts │ ├── mail │ │ ├── FactoryConfig.ts │ │ ├── factory.ts │ │ └── nodemailer │ │ │ ├── factory.ts │ │ │ └── functions │ │ │ └── sendEmail │ │ │ └── index.ts │ ├── FactoryConfig.ts │ └── factory.ts ├── logger │ ├── FactoryConfig.ts │ ├── winston │ │ ├── FactoryConfig.ts │ │ └── factory.ts │ └── factory.ts ├── translator │ ├── FactoryConfig.ts │ ├── default │ │ ├── FactoryConfig.ts │ │ ├── translations │ │ │ ├── interfaces │ │ │ │ ├── index.ts │ │ │ │ ├── Responses.ts │ │ │ │ ├── Emails.ts │ │ │ │ ├── Validation.ts │ │ │ │ └── Errors.ts │ │ │ ├── en │ │ │ │ ├── index.ts │ │ │ │ └── subtranslations │ │ │ │ │ └── responses │ │ │ │ │ └── index.ts │ │ │ └── pl │ │ │ │ ├── index.ts │ │ │ │ └── subtranslations │ │ │ │ └── responses │ │ │ │ └── index.ts │ │ └── factory.ts │ └── factory.ts ├── config │ ├── subconfigs │ │ ├── repo │ │ │ ├── index.ts │ │ │ └── model.ts │ │ └── translator │ │ │ └── index.ts │ └── index.ts └── service │ ├── FactoryConfig.ts │ └── functions │ ├── users │ ├── revokeUserRole │ │ └── index.ts │ └── assignUserRole │ │ └── index.ts │ ├── roles │ ├── revokeRolePermission │ │ └── index.ts │ └── assignRolePermission │ │ └── index.ts │ ├── auth │ ├── factory.ts │ ├── utils │ │ └── getRolesForUser │ │ │ └── index.ts │ └── verifyAccount │ │ └── index.ts │ ├── getCourseDetails │ └── index.ts │ └── getDiscoveryItemsForHomepage │ └── index.ts ├── renovate.json ├── @types └── winston-loggly-bulk │ └── index.d.ts ├── .editorconfig ├── prettier.config.js ├── .jscpd.json ├── jest.config.js ├── tslint.json ├── Dockerfile ├── tsconfig.json ├── scripts └── deploy.sh ├── assets └── jscpd-badge.svg ├── .env.example ├── LICENSE └── .gitignore /k8s/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | kube-ts-server has been installed. 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | assets 4 | k8s 5 | -------------------------------------------------------------------------------- /src/__mocks__/uuid.ts: -------------------------------------------------------------------------------- 1 | module.exports = { v4: jest.fn(() => 1) } 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@kube-js:webapp" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /@types/winston-loggly-bulk/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'winston-loggly-bulk' { 2 | const x: any; 3 | export = x; 4 | } 5 | -------------------------------------------------------------------------------- /src/constants/roles.ts: -------------------------------------------------------------------------------- 1 | export const ADMIN = 'admin'; 2 | export const INSTRUCTOR = 'instructor'; 3 | export const STUDENT = 'student'; 4 | -------------------------------------------------------------------------------- /src/utils/errors/http/NotFoundError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class NotFoundError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/helpers/commons/isString/index.ts: -------------------------------------------------------------------------------- 1 | import is from 'ramda/src/is'; 2 | 3 | export default (value: any): boolean => is(String, value); 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/ForbiddenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class ForbiddenError extends BaseError {} 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | insert_final_newline = true -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require("@kube-js/tscommons/configs/prettier.config.js"); 2 | 3 | module.exports = { 4 | ...baseConfig 5 | }; 6 | -------------------------------------------------------------------------------- /src/types/items/Role.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface Role extends BaseItem { 4 | readonly name: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/errors/auth/LockedAccountError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class LockedAccountError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/UnauthorizedError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class UnauthorizedError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/VerifyLockoutError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class VerifyLockoutError extends BaseError {} 4 | -------------------------------------------------------------------------------- /.jscpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "threshold": 10, 3 | "reporters": ["console", "badge"], 4 | "absolute": true, 5 | "gitignore": true, 6 | "output": "./assets" 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/errors/auth/ExpiredJwtTokenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class ExpiredJwtTokenError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/InvalidJwtTokenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class InvalidJwtTokenError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/MissingJwtTokenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class MissingJwtTokenError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/InvalidCredentialsError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class InvalidCredentialsError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/UndefinedJWTSecretError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class UndefinedJWTSecretError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/UnverifiedAccountError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class UnverifiedAccountError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/http/ServiceUnavailableError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class ServiceUnavailableError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/errors/auth/MissingJwtTokenExtractorError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class MissingJwtTokenExtractorError extends BaseError {} 4 | -------------------------------------------------------------------------------- /src/utils/converters/helpers/itemToDocumentBoolean/index.ts: -------------------------------------------------------------------------------- 1 | const itemToDocumentBoolean = (value: any) => value === 1 || value === '1'; 2 | 3 | export default itemToDocumentBoolean; 4 | -------------------------------------------------------------------------------- /src/types/items/Category.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface Category extends BaseItem { 4 | readonly title: string; 5 | readonly slug: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/types/items/UserRole.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface UserRole extends BaseItem { 4 | readonly userId: string; 5 | readonly roleId: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/presenter/express/types/ExpressHandler.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | 3 | type Handler = (req: Request, res: Response) => Promise; 4 | 5 | export default Handler; 6 | -------------------------------------------------------------------------------- /src/repo/model/knex/other/closeDbConnection/index.ts: -------------------------------------------------------------------------------- 1 | import { RepoConfig } from '../../factory'; 2 | 3 | export default ({ db }: RepoConfig) => async () => { 4 | (await db()).destroy(); 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/helpers/config/getNumberValue/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultTo } from 'ramda'; 2 | 3 | export default (value: any, defaultValue: number): number => 4 | Number(defaultTo(defaultValue)(value)); 5 | -------------------------------------------------------------------------------- /src/utils/helpers/config/getStringValue/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultTo } from 'ramda'; 2 | 3 | export default (value: any, defaultValue = ''): string => 4 | String(defaultTo(defaultValue)(value)); 5 | -------------------------------------------------------------------------------- /src/utils/helpers/model/getVisibleRolesProperties/index.ts: -------------------------------------------------------------------------------- 1 | import Role from '../../../../types/items/Role'; 2 | 3 | export default (roles: Role[] = []): string[] => 4 | roles.map(role => role.name); 5 | -------------------------------------------------------------------------------- /src/utils/converters/helpers/documentToItemNumber/index.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | const documentToItemNumber = (value: any) => 4 | !Number.isNaN(value) ? Number(value) : 0; 5 | 6 | export default documentToItemNumber; 7 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/hashPassword/index.ts: -------------------------------------------------------------------------------- 1 | import * as argon2 from 'argon2'; 2 | 3 | export default (value: string): Promise => 4 | argon2.hash(value, { 5 | type: argon2.argon2id, 6 | }); 7 | -------------------------------------------------------------------------------- /src/utils/helpers/date/getUtcDate/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const getUtcDate = (value?: any, format?: any) => 4 | moment.utc(value, format).toDate(); 5 | 6 | export default getUtcDate; 7 | -------------------------------------------------------------------------------- /src/logger/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { LoggerConfig } from '../config/subconfigs/logger'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface FactoryConfig extends LoggerConfig {} 5 | -------------------------------------------------------------------------------- /src/types/items/ResetPasswordToken.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface ResetPasswordToken extends BaseItem { 4 | readonly userId: string; 5 | readonly expiresAt: Date; 6 | } 7 | -------------------------------------------------------------------------------- /src/types/items/RolePermission.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface RolePermission extends BaseItem { 4 | readonly roleId: string; 5 | readonly permissionId: string; 6 | } 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('@kube-js/tscommons/configs/jest.config.js'); 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | moduleFileExtensions: ['json', 'ts', 'js', 'jsx', 'tsx', 'node'], 6 | }; 7 | -------------------------------------------------------------------------------- /src/repo/mail/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { MailConfig } from '../../config/subconfigs/repo/mail'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface FactoryConfig extends MailConfig {} 5 | -------------------------------------------------------------------------------- /src/repo/model/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { ModelConfig } from '../../config/subconfigs/repo/model'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface FactoryConfig extends ModelConfig {} 5 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/verifyPassword/index.ts: -------------------------------------------------------------------------------- 1 | import * as argon2 from 'argon2'; 2 | 3 | export default (hashedPassword: string, password: string): Promise => 4 | argon2.verify(hashedPassword, password); 5 | -------------------------------------------------------------------------------- /src/logger/winston/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { WinstonConfig } from '../../config/subconfigs/logger'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface FactoryConfig extends WinstonConfig {} 5 | -------------------------------------------------------------------------------- /src/presenter/express/app/AppConfig.ts: -------------------------------------------------------------------------------- 1 | import BaseAppConfig from '../../../types/app/BaseAppConfig'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface AppConfig extends BaseAppConfig {} 5 | -------------------------------------------------------------------------------- /src/types/items/Permission.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface Permission extends BaseItem { 4 | readonly name: string; 5 | readonly method: string; 6 | readonly url: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/helpers/date/isInTheFuture/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import _isNil from 'ramda/src/isNil'; 3 | 4 | export default (input: any): boolean => 5 | !_isNil(input) && moment(input).isAfter(moment()); 6 | -------------------------------------------------------------------------------- /src/translator/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { TranslatorConfig } from '../config/subconfigs/translator'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface FactoryConfig extends TranslatorConfig {} 5 | -------------------------------------------------------------------------------- /src/utils/converters/convertersMaps/baseMap.ts: -------------------------------------------------------------------------------- 1 | import documentToItemDateTime from '../helpers/documentToItemDateTime'; 2 | 3 | export default { 4 | createdAt: documentToItemDateTime, 5 | updatedAt: documentToItemDateTime, 6 | }; 7 | -------------------------------------------------------------------------------- /src/translator/default/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTranslator } from '../../config/subconfigs/translator'; 2 | 3 | // tslint:disable-next-line:no-empty-interface 4 | export default interface FactoryConfig extends DefaultTranslator {} 5 | -------------------------------------------------------------------------------- /src/utils/helpers/config/getBooleanValue/index.ts: -------------------------------------------------------------------------------- 1 | import boolean from 'boolean'; 2 | import { defaultTo } from 'ramda'; 3 | 4 | export default (value: any, defaultValue: boolean): boolean => 5 | boolean(defaultTo(defaultValue)(value)); 6 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/Options.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import Config from '../../presenterFactory/Config'; 3 | 4 | export default interface Options { 5 | readonly router: Router; 6 | readonly config: Config; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/converters/helpers/documentToItemBoolean/index.ts: -------------------------------------------------------------------------------- 1 | import { isBoolean } from "util"; 2 | 3 | const documentToItemBoolean = (value: any) => 4 | isBoolean(value) && Boolean(value) ? 1 : 0; 5 | 6 | export default documentToItemBoolean; 7 | -------------------------------------------------------------------------------- /src/presenter/commander/app/AppConfig.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import BaseAppConfig from '../../../types/app/BaseAppConfig'; 3 | 4 | export default interface AppConfig extends BaseAppConfig { 5 | readonly program: Command; 6 | } 7 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "./node_modules/@kube-js/tscommons/configs/tslint.json", 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "no-any": false, 8 | "no-inferred-empty-object-type": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/types/items/Enrolment.ts: -------------------------------------------------------------------------------- 1 | import BaseItem, { NullableDate } from './BaseItem'; 2 | 3 | export default interface Enrolment extends BaseItem { 4 | readonly userId: string; 5 | readonly courseId: string; 6 | readonly deletedAt?: NullableDate; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/items/Section.ts: -------------------------------------------------------------------------------- 1 | import BaseItem from './BaseItem'; 2 | 3 | export default interface Module extends BaseItem { 4 | readonly courseId: string; 5 | readonly order: number; 6 | readonly title: string; 7 | readonly isPublished?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/converters/helpers/documentToItemDate/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const documentToItemDate = (value: any) => 4 | value === null ? undefined : moment(value).format('YYYY-MM-DD'); 5 | 6 | export default documentToItemDate; 7 | -------------------------------------------------------------------------------- /src/utils/converters/helpers/documentToItemDateTime/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const documentToItemDateTime = (value: any) => 4 | value === null ? undefined : moment.utc(value).toDate(); 5 | 6 | export default documentToItemDateTime; 7 | -------------------------------------------------------------------------------- /src/types/items/BaseItem.ts: -------------------------------------------------------------------------------- 1 | import { Item } from '@js-items/foundation'; 2 | 3 | export type NullableDate = Date | null; 4 | 5 | export default interface BaseItem extends Item { 6 | readonly createdAt: NullableDate; 7 | readonly updatedAt?: NullableDate; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/validation/rules/Optional.ts: -------------------------------------------------------------------------------- 1 | import Rule from 'rulr/Rule'; 2 | const optional = (rule: Rule) => (data: any) => { 3 | if (data === undefined) { 4 | return []; 5 | } 6 | 7 | return rule(data); 8 | }; 9 | 10 | export default optional; 11 | -------------------------------------------------------------------------------- /src/utils/errors/validation/EmailValidationError.ts: -------------------------------------------------------------------------------- 1 | import ValidationError from 'rulr/ValidationError'; 2 | 3 | export default class EmailValidationError extends ValidationError { 4 | constructor(data: unknown) { 5 | super(`expected valid email`, data); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /k8s/README.md: -------------------------------------------------------------------------------- 1 | # kube-ts-server 2 | 3 | ## Installing the Chart 4 | 5 | ``` 6 | helm install --name my-release . 7 | ``` 8 | 9 | ## Uninstalling the Chart 10 | 11 | To uninstall/delete the my-release deployment: 12 | 13 | ``` 14 | $ helm delete my-release 15 | ``` 16 | -------------------------------------------------------------------------------- /src/repo/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import MailFactoryConfig from './mail/FactoryConfig'; 2 | import ModelFactoryConfig from './model/FactoryConfig'; 3 | 4 | export default interface FactoryConfig { 5 | readonly model: ModelFactoryConfig; 6 | readonly mail: MailFactoryConfig; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/errors/auth/RemindPasswordError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class RemindPasswordError extends BaseError { 4 | public email: string; 5 | 6 | constructor(email: string){ 7 | super(); 8 | this.email = email; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/roles/base/schema.ts: -------------------------------------------------------------------------------- 1 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 2 | import String from '../../../../../../utils/validation/rules/String'; 3 | 4 | const baseSchema = { 5 | name: String(0, VARCHAR_LENGTH), 6 | }; 7 | 8 | export default baseSchema; 9 | -------------------------------------------------------------------------------- /src/translator/default/translations/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | import Emails from './Emails'; 2 | import Errors from './Errors'; 3 | import Responses from './Responses'; 4 | import Validation from './Validation'; 5 | 6 | export default interface Translation extends Errors, Validation, Emails, Responses {} 7 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/courses/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeUpdateSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeUpdateSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/roles/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeCreateSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeCreateSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/roles/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeReplaceSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeReplaceSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/sections/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeCreateSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeCreateSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/repo/model/factory.ts: -------------------------------------------------------------------------------- 1 | import FactoryConfig from './FactoryConfig'; 2 | import knexFactory from './knex/factory'; 3 | 4 | export default (config: FactoryConfig) => { 5 | switch (config.type) { 6 | default: 7 | case 'knex': 8 | return knexFactory(config.knex); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/translator/default/translations/interfaces/Responses.ts: -------------------------------------------------------------------------------- 1 | export default interface Responses { 2 | readonly created: () => string; 3 | readonly passwordChangedSuccessfully: () => string; 4 | readonly accountVerifiedSuccessfully: () => string; 5 | readonly resetPasswordLinkSent: () => string; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/errors/auth/AccountAlreadyVerifiedError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class AccountAlreadyVerifiedError extends BaseError { 4 | public email: string; 5 | 6 | constructor(email: string) { 7 | super(); 8 | this.email = email; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/categories/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeUpdateSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeUpdateSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/enrolments/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeUpdateSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeUpdateSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/permissions/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import baseSchema from '../base/schema'; 3 | 4 | export const beforeCreateSchema = { 5 | ...baseSchema, 6 | }; 7 | 8 | const rules = Record(beforeCreateSchema); 9 | 10 | export default rules; 11 | -------------------------------------------------------------------------------- /src/repo/factory.ts: -------------------------------------------------------------------------------- 1 | import FactoryConfig from './FactoryConfig'; 2 | import mailFactory from './mail/factory'; 3 | import modelFactory from './model/factory'; 4 | 5 | export default (factoryConfig: FactoryConfig) => ({ 6 | ...modelFactory(factoryConfig.model), 7 | ...mailFactory(factoryConfig.mail), 8 | }); 9 | -------------------------------------------------------------------------------- /src/utils/errors/auth/ExpiredResetPasswordTokenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class ExpiredResetPasswordTokenError extends BaseError { 4 | public token: string; 5 | 6 | constructor(token: string) { 7 | super(); 8 | this.token = token; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/errors/auth/InvalidResetPasswordTokenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class InvalidResetPasswordTokenError extends BaseError { 4 | public token: string; 5 | 6 | constructor(token: string) { 7 | super(); 8 | this.token = token; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/errors/auth/InvalidVerifyAccountTokenError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class InvalidVerifyAccountTokenError extends BaseError { 4 | public email: string; 5 | 6 | constructor(email: string) { 7 | super(); 8 | this.email = email; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/logger/factory.ts: -------------------------------------------------------------------------------- 1 | import FactoryConfig from './FactoryConfig'; 2 | import winstonFactory from './winston/factory'; 3 | 4 | export default (config: FactoryConfig) => { 5 | switch (config.type) { 6 | default: 7 | case 'winston': 8 | return winstonFactory(config.winston); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/courses/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | 3 | import baseSchema from '../base/schema'; 4 | 5 | export const beforeReplaceSchema = { 6 | ...baseSchema, 7 | }; 8 | 9 | const rules = Record(beforeReplaceSchema); 10 | 11 | export default rules; 12 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/sections/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | 3 | import baseSchema from '../base/schema'; 4 | 5 | export const beforeReplaceSchema = { 6 | ...baseSchema, 7 | }; 8 | 9 | const rules = Record(beforeReplaceSchema); 10 | 11 | export default rules; 12 | -------------------------------------------------------------------------------- /src/utils/helpers/date/fastForwardTimeBy/index.ts: -------------------------------------------------------------------------------- 1 | import moment, { DurationInputArg1, unitOfTime } from 'moment'; 2 | 3 | export default ( 4 | value: DurationInputArg1, 5 | unit: unitOfTime.DurationConstructor 6 | ): Date => 7 | moment() 8 | .add(value, unit) 9 | .utc() 10 | .toDate(); 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/categories/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | 3 | import baseSchema from '../base/schema'; 4 | 5 | export const beforeReplaceSchema = { 6 | ...baseSchema, 7 | }; 8 | 9 | const rules = Record(beforeReplaceSchema); 10 | 11 | export default rules; 12 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/enrolments/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | 3 | import baseSchema from '../base/schema'; 4 | 5 | export const beforeReplaceSchema = { 6 | ...baseSchema, 7 | }; 8 | 9 | const rules = Record(beforeReplaceSchema); 10 | 11 | export default rules; 12 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/permissions/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | 3 | import baseSchema from '../base/schema'; 4 | 5 | export const beforeReplaceSchema = { 6 | ...baseSchema, 7 | }; 8 | 9 | const rules = Record(beforeReplaceSchema); 10 | 11 | export default rules; 12 | -------------------------------------------------------------------------------- /src/translator/factory.ts: -------------------------------------------------------------------------------- 1 | import defaultTranslator from './default/factory'; 2 | import FactoryConfig from './FactoryConfig'; 3 | 4 | export default (config: FactoryConfig) => { 5 | switch (config.type) { 6 | default: 7 | case 'default': 8 | return defaultTranslator(config.default); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/presenter/express/api/commons/checks/initFinished/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../../presenterFactory/Config'; 2 | 3 | export default async (_config: Config) => 4 | new Promise((resolve, _reject) => { 5 | // placeholder for db set up 6 | // and other services i.e. redis cache service 7 | resolve(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/repo/mail/factory.ts: -------------------------------------------------------------------------------- 1 | import FactoryConfig from './FactoryConfig'; 2 | import nodemailerFactory from './nodemailer/factory'; 3 | 4 | export default (config: FactoryConfig) => { 5 | switch (config.type) { 6 | default: 7 | case 'nodemailer': 8 | return nodemailerFactory(config.nodemailer); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/config/subconfigs/repo/index.ts: -------------------------------------------------------------------------------- 1 | import mail, { MailConfig } from './mail'; 2 | import model, { ModelConfig } from './model'; 3 | 4 | export interface RepoConfig { 5 | readonly model: ModelConfig; 6 | readonly mail: MailConfig; 7 | } 8 | 9 | const config: RepoConfig = { 10 | mail, 11 | model, 12 | }; 13 | 14 | export default config; 15 | -------------------------------------------------------------------------------- /src/utils/converters/helpers/itemToDocumentDateTime/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import _isNil from 'ramda/src/isNil'; 3 | 4 | const itemToDocumentDateTime = (value: any) => 5 | !_isNil(value) && moment(value).isValid() 6 | ? moment.utc(value) 7 | .toDate() 8 | : null; 9 | 10 | export default itemToDocumentDateTime; 11 | -------------------------------------------------------------------------------- /src/utils/errors/http/ConflictError.ts: -------------------------------------------------------------------------------- 1 | import { BaseError } from 'make-error'; 2 | 3 | export default class ConflictError extends BaseError { 4 | public readonly itemName: string; 5 | public readonly itemId?: string; 6 | 7 | constructor(itemName: string, itemId?: string) { 8 | super(); 9 | this.itemName = itemName; 10 | this.itemId = itemId; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.18.1-alpine as builder 2 | 3 | STOPSIGNAL SIGTERM 4 | RUN apk --no-cache add g++ gcc libgcc libstdc++ linux-headers make python git 5 | RUN mkdir -p /usr/src/app 6 | WORKDIR /usr/src/app 7 | RUN npm install --quiet node-gyp -g 8 | COPY package*.json ./ 9 | RUN npm ci 10 | RUN npm run build 11 | COPY . . 12 | 13 | EXPOSE 3000 14 | CMD [ "npm", "start" ] 15 | -------------------------------------------------------------------------------- /src/presenter/express/middlewares/cors/factory.ts: -------------------------------------------------------------------------------- 1 | import cors from 'cors'; 2 | import { ExpressConfig } from '../../../../config/subconfigs/http'; 3 | 4 | const createCorsMiddleware = (_config: ExpressConfig) => 5 | // TODO: options should come from config 6 | cors({ 7 | origin: '*', 8 | preflightContinue: true, 9 | }); 10 | 11 | export default createCorsMiddleware; 12 | -------------------------------------------------------------------------------- /src/service/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { Config as AppConfig } from '../config/index'; 2 | import loggerFactory from '../logger/factory'; 3 | import repoFactory from '../repo/factory'; 4 | 5 | export default interface FactoryConfig { 6 | readonly appConfig: AppConfig; 7 | readonly logger: ReturnType; 8 | readonly repo: ReturnType; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/validation/rules/Email.ts: -------------------------------------------------------------------------------- 1 | import { REGEXP_EMAIL } from '../../../constants'; 2 | import EmailValidationError from '../../errors/validation/EmailValidationError'; 3 | 4 | export default function() { 5 | return (data: string) => { 6 | if (REGEXP_EMAIL.test(data)) { 7 | return []; 8 | } 9 | 10 | return [new EmailValidationError(data)]; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@kube-js/tscommons/configs/tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "noImplicitReturns": false, 7 | "typeRoots": ["./@types", "./node_modules/@types"], 8 | "lib": ["dom"] 9 | }, 10 | "includes": ["src/**/*"], 11 | "exclude": ["node_modules", "dist/**/*"] 12 | } 13 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/permissions/base/schema.ts: -------------------------------------------------------------------------------- 1 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 2 | import String from '../../../../../../utils/validation/rules/String'; 3 | 4 | const baseSchema = { 5 | method: String(0, VARCHAR_LENGTH), 6 | name: String(0, VARCHAR_LENGTH), 7 | url: String(0, VARCHAR_LENGTH), 8 | }; 9 | 10 | export default baseSchema; 11 | -------------------------------------------------------------------------------- /src/utils/helpers/url/getResetPasswordUrl/index.ts: -------------------------------------------------------------------------------- 1 | import { ClientConfig } from '../../../../config/subconfigs/http/index'; 2 | 3 | export interface Options { 4 | readonly token: string; 5 | readonly config: ClientConfig; 6 | } 7 | 8 | export default ({ token, config }: Options) => 9 | `${config.resetPasswordUrl}?${ 10 | config.resetPasswordTokenQueryParamName 11 | }=${token}`; 12 | -------------------------------------------------------------------------------- /src/utils/errors/validation/DateValidationError.ts: -------------------------------------------------------------------------------- 1 | import ValidationError from 'rulr/ValidationError'; 2 | 3 | export default class DateValidationError extends ValidationError { 4 | public readonly expectedFormats: string[]; 5 | 6 | constructor(data: unknown, expectedFormats: string[]) { 7 | super(`expected valid date`, data); 8 | this.expectedFormats = expectedFormats; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/errors/validation/PasswordValidationError.ts: -------------------------------------------------------------------------------- 1 | import ValidationError from 'rulr/ValidationError'; 2 | 3 | export default class PasswordValidationError extends ValidationError { 4 | constructor(data: unknown) { 5 | super( 6 | `expected string containing minimum eight characters, at least one letter, one number and one special character`, 7 | data 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/helpers/date/lessThanAgo/index.ts: -------------------------------------------------------------------------------- 1 | import moment, { DurationInputArg1, unitOfTime } from 'moment'; 2 | 3 | export interface Options { 4 | readonly date: Date; 5 | readonly value: DurationInputArg1; 6 | readonly unit: unitOfTime.DurationConstructor; 7 | } 8 | 9 | export default ({ date, value, unit }: Options): boolean => 10 | moment(date).isAfter(moment().subtract(value, unit)); 11 | -------------------------------------------------------------------------------- /src/utils/validation/rules/Password.ts: -------------------------------------------------------------------------------- 1 | import { REGEXP_PASSWORD } from '../../../constants'; 2 | import PasswordValidationError from '../../errors/validation/PasswordValidationError'; 3 | 4 | export default function() { 5 | return (data: string) => { 6 | if (REGEXP_PASSWORD.test(data)) { 7 | return []; 8 | } 9 | 10 | return [new PasswordValidationError(data)]; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/presenter/commander/presenterFactory/FactoryConfig.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import loggerFactory from '../../../logger/factory'; 3 | import serviceFactory from '../../../service/factory'; 4 | 5 | export default interface FactoryConfig { 6 | readonly service: ReturnType; 7 | readonly program: Command; 8 | readonly logger: ReturnType; 9 | } 10 | -------------------------------------------------------------------------------- /src/service/functions/users/revokeUserRole/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../FactoryConfig'; 2 | 3 | export interface Options { 4 | readonly userId: string; 5 | readonly roleId: string; 6 | } 7 | 8 | export default ({ repo }: Config) => async ({ userId, roleId }: Options) => { 9 | await repo.userRole.deleteItems({ 10 | filter: { 11 | roleId, 12 | userId, 13 | }, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/helpers/model/getVisiblePublicUserProperties/index.ts: -------------------------------------------------------------------------------- 1 | import pick from 'ramda/src/pick'; 2 | import User from '../../../../types/items/User'; 3 | 4 | export default (user: User): Partial => 5 | pick( 6 | [ 7 | 'id', 8 | 'email', 9 | 'avatarUrl', 10 | 'firstName', 11 | 'lastName', 12 | 'bio', 13 | 'dateOfBirth', 14 | 'gender', 15 | ], 16 | user 17 | ); 18 | -------------------------------------------------------------------------------- /src/utils/errors/validation/EnumValidationError.ts: -------------------------------------------------------------------------------- 1 | import ValidationError from 'rulr/ValidationError'; 2 | 3 | export type EnumKeys = string[] | number[]; 4 | export default class EnumValidationError extends ValidationError { 5 | public readonly enumValues: EnumKeys; 6 | 7 | constructor(data: unknown, enumValues: EnumKeys) { 8 | super(`expected valid enum value`, data); 9 | this.enumValues = enumValues; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/helpers/url/getVerifyEmailUrl/index.ts: -------------------------------------------------------------------------------- 1 | import { ClientConfig } from '../../../../config/subconfigs/http/index'; 2 | 3 | export interface Options { 4 | readonly token: string; 5 | readonly email: string; 6 | readonly config: ClientConfig; 7 | } 8 | 9 | export default ({ token, email, config }: Options) => 10 | `${config.verifyEmailUrl}?${ 11 | config.verifyTokenQueryParamName 12 | }=${token}&email=${email}`; 13 | -------------------------------------------------------------------------------- /k8s/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /src/utils/converters/baseConverter/itemToDocument.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import recursiveConverter from '../recursiveConverter'; 4 | 5 | const convertersMap = { 6 | ...baseMap, 7 | }; 8 | 9 | const documentToItem = (document: Document) => 10 | recursiveConverter({ obj: document, convertersMap }); 11 | 12 | export default documentToItem; 13 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/users/assignUserRole/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { UUID_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | 5 | export const schema = { 6 | role_id: String(UUID_LENGTH, UUID_LENGTH), 7 | user_id: String(UUID_LENGTH, UUID_LENGTH), 8 | }; 9 | 10 | const rules = Record(schema); 11 | 12 | export default rules; 13 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/users/revokeUserRole/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { UUID_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | 5 | export const schema = { 6 | role_id: String(UUID_LENGTH, UUID_LENGTH), 7 | user_id: String(UUID_LENGTH, UUID_LENGTH), 8 | }; 9 | 10 | const rules = Record(schema); 11 | 12 | export default rules; 13 | -------------------------------------------------------------------------------- /src/service/functions/roles/revokeRolePermission/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../FactoryConfig'; 2 | 3 | export interface Options { 4 | readonly permissionId: string; 5 | readonly roleId: string; 6 | } 7 | 8 | export default ({ repo }: Config) => async ({ permissionId, roleId }: Options) => { 9 | await repo.rolePermission.deleteItems({ 10 | filter: { 11 | permissionId, 12 | roleId, 13 | }, 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/helpers/math/incrementOrInitialise/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | 3 | type Input = undefined | null | number; 4 | 5 | interface Options { 6 | readonly incrementBy: number; 7 | readonly initialValue: 0; 8 | } 9 | 10 | export default ( 11 | input: Input, 12 | { initialValue, incrementBy }: Options = { initialValue: 0, incrementBy: 1 } 13 | ): number => (_isNil(input) ? initialValue : input + incrementBy); 14 | -------------------------------------------------------------------------------- /src/utils/converters/baseConverter/documentToItem.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import recursiveConverter from '../recursiveConverter'; 4 | 5 | const convertersMap = { 6 | ...baseMap, 7 | }; 8 | 9 | const documentToItem = (document: Document): I => 10 | recursiveConverter({ obj: document, convertersMap }) as I; 11 | 12 | export default documentToItem; 13 | -------------------------------------------------------------------------------- /src/presenter/commander/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import dotenv from 'dotenv'; 3 | dotenv.config(); 4 | import config from '../../config'; 5 | import app from './app'; 6 | 7 | const program = new Command(); 8 | 9 | app({ 10 | auth: config.auth, 11 | http: config.http, 12 | logger: config.logger, 13 | program, 14 | repo: config.repo, 15 | translator: config.translator, 16 | }); 17 | 18 | program.parse(process.argv); 19 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/createPatch/index.ts: -------------------------------------------------------------------------------- 1 | import CreatePatch from '@js-items/express/dist/types/CreatePatch'; 2 | import { Item } from '@js-items/foundation'; 3 | import { toCamel } from 'convert-keys'; 4 | import { BaseFactoryConfig } from '../baseFactory'; 5 | 6 | const createPatch = (_config: BaseFactoryConfig): CreatePatch => ({ 7 | document, 8 | }) => toCamel(document); 9 | 10 | export default createPatch; 11 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/roles/assignRolePermission/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { UUID_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | 5 | export const schema = { 6 | permission_id: String(UUID_LENGTH, UUID_LENGTH), 7 | role_id: String(UUID_LENGTH, UUID_LENGTH), 8 | }; 9 | 10 | const rules = Record(schema); 11 | 12 | export default rules; 13 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/roles/revokeRolePermission/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { UUID_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | 5 | export const schema = { 6 | permission_id: String(UUID_LENGTH, UUID_LENGTH), 7 | role_id: String(UUID_LENGTH, UUID_LENGTH), 8 | }; 9 | 10 | const rules = Record(schema); 11 | 12 | export default rules; 13 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/categories/base/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | VARCHAR_LENGTH, 3 | } from '../../../../../../constants'; 4 | import Optional from '../../../../../../utils/validation/rules/Optional'; 5 | import String from '../../../../../../utils/validation/rules/String'; 6 | 7 | const baseSchema = { 8 | slug: Optional(String(0, VARCHAR_LENGTH)), 9 | title: Optional(String(0, VARCHAR_LENGTH)), 10 | }; 11 | 12 | export default baseSchema; 13 | -------------------------------------------------------------------------------- /src/utils/errors/validation/MatchValidationError.ts: -------------------------------------------------------------------------------- 1 | import ValidationError from 'rulr/ValidationError'; 2 | 3 | export default class MatchValidationError extends ValidationError { 4 | public readonly fieldOne: string; 5 | public readonly fieldTwo: string; 6 | 7 | constructor(data: unknown, fieldOne: string, fieldTwo: string) { 8 | super(`expected fields to match`, data); 9 | this.fieldOne = fieldOne; 10 | this.fieldTwo = fieldTwo; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/helpers/commons/isDate/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { DEFAULT_DATE_FORMATS } from '../../../../constants'; 3 | 4 | export interface Options { 5 | readonly value: any; 6 | readonly expectedFormats?: string[]; 7 | readonly strict?: boolean; 8 | } 9 | 10 | export default ({ 11 | value, 12 | expectedFormats = DEFAULT_DATE_FORMATS, 13 | strict = true, 14 | }: Options): boolean => moment(value, expectedFormats, strict).isValid(); 15 | -------------------------------------------------------------------------------- /src/translator/default/translations/en/index.ts: -------------------------------------------------------------------------------- 1 | import Translation from '../interfaces'; 2 | import emails from './subtranslations/emails'; 3 | import errors from './subtranslations/errors'; 4 | import responsesTranslations from './subtranslations/responses'; 5 | import validation from './subtranslations/validation'; 6 | 7 | const en: Translation = { 8 | ...validation, 9 | ...errors, 10 | ...emails, 11 | ...responsesTranslations, 12 | }; 13 | 14 | export default en; 15 | -------------------------------------------------------------------------------- /src/translator/default/translations/pl/index.ts: -------------------------------------------------------------------------------- 1 | import Translation from '../interfaces'; 2 | import emails from './subtranslations/emails'; 3 | import errors from './subtranslations/errors'; 4 | import responsesTranslations from './subtranslations/responses'; 5 | import validation from './subtranslations/validation'; 6 | 7 | const pl: Translation = { 8 | ...validation, 9 | ...errors, 10 | ...emails, 11 | ...responsesTranslations, 12 | }; 13 | 14 | export default pl; 15 | -------------------------------------------------------------------------------- /k8s/cicd-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Config 3 | users: 4 | - name: KUBERNETES_USER 5 | user: 6 | token: $KUBERNETES_TOKEN 7 | clusters: 8 | - cluster: 9 | certificate-authority-data: $KUBERNETES_CLUSTER_CERTIFICATE 10 | server: $KUBERNETES_SERVER 11 | name: $KUBERNETES_CLUSTER_NAME 12 | contexts: 13 | - context: 14 | cluster: $KUBERNETES_CLUSTER_NAME 15 | user: KUBERNETES_USER 16 | name: $KUBERNETES_CONTEXT 17 | current-context: $KUBERNETES_CONTEXT 18 | -------------------------------------------------------------------------------- /k8s/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: A Helm chart for kube-ts-server 3 | name: kube-ts-server 4 | version: 1.0.0 5 | appVersion: 1.5.17 6 | home: https://cloud.docker.com/u/kubejs/repository/docker/kubejs/kube-ts-server 7 | icon: https://avatars2.githubusercontent.com/u/47761918?s=200&v=4 8 | sources: 9 | - https://github.com/kube-js/kube-ts-server 10 | maintainers: 11 | - name: mariocoski 12 | email: mariuszrajczakowski@pm.me 13 | url: https://mariuszrajczakowski.me 14 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeGetItem/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../../../presenterFactory/Config'; 2 | import transactionWrapper from '../../../../../utils/handlers/transactionWrapper'; 3 | 4 | const beforeGetItem = (config: Config) => 5 | transactionWrapper({ 6 | // GET ITEM DO NOT REQUIRE AUTHENTICATION AND AUTHORISATION BY DEFAULT 7 | beforeHandler: async () => Promise.resolve({}), 8 | config, 9 | }); 10 | 11 | export default beforeGetItem; 12 | -------------------------------------------------------------------------------- /src/presenter/express/api/commons/checks/checkDb/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../../presenterFactory/Config'; 2 | 3 | export default async (config: Config) => 4 | new Promise(async (resolve, reject) => { 5 | try { 6 | // FYI: simple check to see if db is ok 7 | if (config.service.users.countItems !== undefined) { 8 | await config.service.users.countItems({}); 9 | } 10 | resolve(); 11 | } catch (err) { 12 | reject(err); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeGetItems/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../../../presenterFactory/Config'; 2 | import transactionWrapper from '../../../../../utils/handlers/transactionWrapper'; 3 | 4 | const beforeGetItems = (config: Config) => 5 | transactionWrapper({ 6 | // GET ITEM DO NOT REQUIRE AUTHENTICATION AND AUTHORISATION BY DEFAULT 7 | beforeHandler: async () => Promise.resolve({}), 8 | config, 9 | }); 10 | 11 | export default beforeGetItems; 12 | -------------------------------------------------------------------------------- /k8s/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "kube-ts-server.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "kube-ts-server.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: {{ .Release.Service }} 9 | helm.sh/chart: {{ include "kube-ts-server.chart" . }} 10 | data: 11 | {{- range $key, $value := .Values.configMap }} 12 | {{ $key }}: {{ $value | quote }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/auth/remindPassword/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import Intersection from 'rulr/Intersection'; 3 | import Record from 'rulr/Record'; 4 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 5 | import Email from '../../../../../../utils/validation/rules/Email'; 6 | import String from '../../../../../../utils/validation/rules/String'; 7 | 8 | const rules = Record({ 9 | email: Intersection([String(0, VARCHAR_LENGTH), Email()]), 10 | }); 11 | 12 | export default rules; 13 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/auth/resendVerifyToken/index.ts: -------------------------------------------------------------------------------- 1 | import Intersection from 'rulr/Intersection'; 2 | import Record from 'rulr/Record'; 3 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 4 | import Email from '../../../../../../utils/validation/rules/Email'; 5 | import String from '../../../../../../utils/validation/rules/String'; 6 | 7 | export const rules = Record({ 8 | email: Intersection([String(0, VARCHAR_LENGTH), Email()]), 9 | }); 10 | 11 | export default rules; 12 | -------------------------------------------------------------------------------- /k8s/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ include "kube-ts-server.fullname" . }} 5 | labels: 6 | app.kubernetes.io/name: {{ include "kube-ts-server.name" . }} 7 | app.kubernetes.io/instance: {{ .Release.Name }} 8 | app.kubernetes.io/managed-by: {{ .Release.Service }} 9 | helm.sh/chart: {{ include "kube-ts-server.chart" . }} 10 | type: Opaque 11 | data: 12 | {{- range $key, $value := .Values.secret }} 13 | {{ $key }}: {{ $value | b64enc | quote }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/categories/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | import baseSchema from '../base/schema'; 5 | 6 | export const beforeCreateSchema = { 7 | ...baseSchema, 8 | slug: String(0, VARCHAR_LENGTH), 9 | title: String(0, VARCHAR_LENGTH), 10 | }; 11 | 12 | const rules = Record(beforeCreateSchema); 13 | 14 | export default rules; 15 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/courses/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | import baseSchema from '../base/schema'; 5 | 6 | export const beforeCreateSchema = { 7 | ...baseSchema, 8 | slug: String(0, VARCHAR_LENGTH), 9 | title: String(0, VARCHAR_LENGTH), 10 | }; 11 | 12 | const rules = Record(beforeCreateSchema); 13 | 14 | export default rules; 15 | -------------------------------------------------------------------------------- /src/utils/validation/rules/String.ts: -------------------------------------------------------------------------------- 1 | import { StringValidationError } from "rulr/String"; 2 | 3 | export default function(minLength = 0, maxLength = Infinity) { 4 | return (data: any) => { 5 | if ( 6 | typeof data === 'string' && 7 | /* allow inclusive minLength and maxLength values */ 8 | data.length >= minLength && 9 | data.length <= maxLength 10 | ) { 11 | return []; 12 | } 13 | 14 | return [new StringValidationError(data, minLength, maxLength)]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/validation/rules/Date.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_DATE_FORMATS } from '../../../constants'; 2 | import DateValidationError from '../../errors/validation/DateValidationError'; 3 | import isDate from '../../helpers/commons/isDate'; 4 | 5 | export default function(expectedFormats: string[] = DEFAULT_DATE_FORMATS) { 6 | return (data: string) => { 7 | if (isDate({ value: data, expectedFormats })) { 8 | return []; 9 | } 10 | 11 | return [new DateValidationError(data, expectedFormats)]; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/auth/login/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import Intersection from 'rulr/Intersection'; 3 | import Record from 'rulr/Record'; 4 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 5 | import Email from '../../../../../../utils/validation/rules/Email'; 6 | import String from '../../../../../../utils/validation/rules/String'; 7 | 8 | const rules = Record({ 9 | email: Intersection([String(0, VARCHAR_LENGTH), Email()]), 10 | password: String(0, VARCHAR_LENGTH), 11 | }); 12 | 13 | export default rules; 14 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/sections/base/schema.ts: -------------------------------------------------------------------------------- 1 | import isBoolean from 'rulr/Boolean'; 2 | import isNumber from 'rulr/Number'; 3 | import { 4 | UUID_LENGTH, 5 | VARCHAR_LENGTH, 6 | } from '../../../../../../constants'; 7 | import String from '../../../../../../utils/validation/rules/String'; 8 | 9 | const baseSchema: any = { 10 | course_id: String(UUID_LENGTH, UUID_LENGTH), 11 | is_published: isBoolean, 12 | order: isNumber(), 13 | title: String(0, VARCHAR_LENGTH), 14 | }; 15 | 16 | export default baseSchema; 17 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/enrolments/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { UUID_LENGTH } from '../../../../../../constants'; 3 | import String from '../../../../../../utils/validation/rules/String'; 4 | import baseSchema from '../base/schema'; 5 | 6 | export const beforeCreateSchema = { 7 | ...baseSchema, 8 | course_id: String(UUID_LENGTH, UUID_LENGTH), 9 | user_id: String(UUID_LENGTH, UUID_LENGTH), 10 | }; 11 | 12 | const rules = Record(beforeCreateSchema); 13 | 14 | export default rules; 15 | -------------------------------------------------------------------------------- /src/types/items/Object.ts: -------------------------------------------------------------------------------- 1 | import BaseItem, { NullableDate } from './BaseItem'; 2 | 3 | export enum UnitType { 4 | video = 'video', 5 | document = 'document' 6 | } 7 | 8 | export default interface Unit extends BaseItem { 9 | readonly moduleId: string; 10 | readonly sortOrder: number; 11 | readonly type: UnitType; 12 | readonly title: string; 13 | readonly description?: string; 14 | readonly slug: string; 15 | readonly isPaid?: boolean; 16 | readonly isPublished?: boolean; 17 | readonly deletedAt?: NullableDate; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/validation/rules/Match.ts: -------------------------------------------------------------------------------- 1 | import MatchValidationError from '../../errors/validation/MatchValidationError'; 2 | import isString from '../../helpers/commons/isString'; 3 | 4 | export default function(fieldOne: string, fieldTwo: string) { 5 | return (data: any) => { 6 | if ( 7 | isString(data[fieldOne]) && 8 | isString(data[fieldTwo]) && 9 | data[fieldOne] === data[fieldTwo] 10 | ) { 11 | return []; 12 | } 13 | 14 | return [new MatchValidationError(data, fieldOne, fieldTwo)]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/presenter/express/presenterFactory/Config.ts: -------------------------------------------------------------------------------- 1 | import { Config as AppConfig } from '../../../config/index'; 2 | import loggerFactory from '../../../logger/factory'; 3 | import serviceFactory from '../../../service/factory'; 4 | import translatorFactory from '../../../translator/factory'; 5 | export default interface Config { 6 | readonly service: ReturnType; 7 | readonly translator: ReturnType; 8 | readonly logger: ReturnType; 9 | readonly appConfig: AppConfig; 10 | } 11 | -------------------------------------------------------------------------------- /src/translator/default/translations/en/subtranslations/responses/index.ts: -------------------------------------------------------------------------------- 1 | import Responses from '../../../interfaces/Responses'; 2 | 3 | const responsesTranslations: Responses = { 4 | accountVerifiedSuccessfully: () => 'Account verified successfully', 5 | created: () => 'Created', 6 | passwordChangedSuccessfully: () => 'Password changed successfully', 7 | resetPasswordLinkSent: () => 8 | `If the email you specified exists in our system, we've sent a password reset link to it.`, 9 | }; 10 | 11 | export default responsesTranslations; 12 | -------------------------------------------------------------------------------- /src/translator/default/translations/pl/subtranslations/responses/index.ts: -------------------------------------------------------------------------------- 1 | import Responses from '../../../interfaces/Responses'; 2 | 3 | const responsesTranslations: Responses = { 4 | accountVerifiedSuccessfully: () => 'Konto zostało poprawnie aktywowane.', 5 | created: () => 'Utworzono', 6 | passwordChangedSuccessfully: () => 'Hasło zostało zmienione.', 7 | resetPasswordLinkSent: () => 8 | `Jeśli podany email istnieje w naszym systemie, link resetujący hasło został wysłany.`, 9 | }; 10 | 11 | export default responsesTranslations; 12 | -------------------------------------------------------------------------------- /src/utils/converters/sections/itemToDocument.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import itemToDocumentBoolean from '../helpers/itemToDocumentBoolean'; 4 | import recursiveConverter from '../recursiveConverter'; 5 | 6 | const convertersMap = { 7 | ...baseMap, 8 | isPublished: itemToDocumentBoolean 9 | }; 10 | 11 | const documentToItem = (document: Document) => 12 | recursiveConverter({ obj: document, convertersMap }); 13 | 14 | export default documentToItem; 15 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/auth/verifyAccount/index.ts: -------------------------------------------------------------------------------- 1 | import Intersection from 'rulr/Intersection'; 2 | import Record from 'rulr/Record'; 3 | import { UUID_LENGTH, VARCHAR_LENGTH } from '../../../../../../constants'; 4 | import Email from '../../../../../../utils/validation/rules/Email'; 5 | import String from '../../../../../../utils/validation/rules/String'; 6 | 7 | export const rules = Record({ 8 | email: Intersection([String(0, VARCHAR_LENGTH), Email()]), 9 | token: String(UUID_LENGTH, UUID_LENGTH), 10 | }); 11 | 12 | export default rules; 13 | -------------------------------------------------------------------------------- /src/types/items/Course.ts: -------------------------------------------------------------------------------- 1 | import BaseItem, { NullableDate } from './BaseItem'; 2 | 3 | export default interface Course extends BaseItem { 4 | readonly userId: string; 5 | readonly title: string; 6 | readonly slug: string; 7 | readonly categoryId: string; 8 | readonly imageUrl?: string; 9 | readonly isPaid?: boolean; 10 | readonly isPublished?: boolean; 11 | readonly isApproved?: boolean; 12 | readonly description?: string; 13 | readonly goals?: string; 14 | readonly requirements?: string; 15 | readonly deletedAt?: NullableDate; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/converters/resetPasswordTokens/itemToDocument.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import itemToDocumentDateTime from '../helpers/itemToDocumentDateTime'; 4 | import recursiveConverter from '../recursiveConverter'; 5 | 6 | const convertersMap = { 7 | ...baseMap, 8 | expiresAt: itemToDocumentDateTime, 9 | }; 10 | 11 | const documentToItem = (document: Document) => 12 | recursiveConverter({ obj: document, convertersMap }); 13 | 14 | export default documentToItem; 15 | -------------------------------------------------------------------------------- /src/utils/helpers/model/getVisibleUserProperties/index.ts: -------------------------------------------------------------------------------- 1 | import pick from 'ramda/src/pick'; 2 | import User from '../../../../types/items/User'; 3 | 4 | export default (user: User): Partial => 5 | pick( 6 | [ 7 | 'createdAt', 8 | 'updatedAt', 9 | 'id', 10 | 'email', 11 | 'avatarUrl', 12 | 'firstName', 13 | 'lastName', 14 | 'bio', 15 | 'dateOfBirth', 16 | 'gender', 17 | 'verifiedAt', 18 | 'loginLastAttemptAt', 19 | 'verifyLastAttemptAt' 20 | ], 21 | user 22 | ); 23 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/roles/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 3 | import Optional from '../../../../../../utils/validation/rules/Optional'; 4 | import String from '../../../../../../utils/validation/rules/String'; 5 | 6 | import baseSchema from '../base/schema'; 7 | 8 | export const beforeUpdateSchema = { 9 | ...baseSchema, 10 | name: Optional(String(0, VARCHAR_LENGTH)), 11 | }; 12 | 13 | const rules = Record(beforeUpdateSchema); 14 | 15 | export default rules; 16 | -------------------------------------------------------------------------------- /src/utils/converters/resetPasswordTokens/documentToItem.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import documentToItemDateTime from '../helpers/documentToItemDateTime'; 4 | import recursiveConverter from '../recursiveConverter'; 5 | 6 | const convertersMap = { 7 | ...baseMap, 8 | expiresAt: documentToItemDateTime, 9 | }; 10 | 11 | const documentToItem = (document: Document): I => 12 | recursiveConverter({ obj: document, convertersMap }) as I; 13 | 14 | export default documentToItem; 15 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/users/replaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import Email from '../../../../../../utils/validation/rules/Email'; 3 | import Optional from '../../../../../../utils/validation/rules/Optional'; 4 | import Password from '../../../../../../utils/validation/rules/Password'; 5 | 6 | import baseSchema from '../base/schema'; 7 | 8 | export const schema = { 9 | ...baseSchema, 10 | email: Optional(Email()), 11 | password: Optional(Password()), 12 | }; 13 | 14 | const rules = Record(schema); 15 | 16 | export default rules; 17 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/users/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import Email from '../../../../../../utils/validation/rules/Email'; 3 | import Optional from '../../../../../../utils/validation/rules/Optional'; 4 | import Password from '../../../../../../utils/validation/rules/Password'; 5 | 6 | import baseSchema from '../base/schema'; 7 | 8 | export const schema = { 9 | ...baseSchema, 10 | email: Optional(Email()), 11 | password: Optional(Password()), 12 | }; 13 | 14 | const rules = Record(schema); 15 | 16 | export default rules; 17 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/convertItemIntoDocument/index.ts: -------------------------------------------------------------------------------- 1 | import ItemIntoDocument from '@js-items/express/dist/types/ItemIntoDocument'; 2 | import { Item } from '@js-items/foundation'; 3 | import { toSnake } from 'convert-keys'; 4 | import { BaseFactoryConfig } from '../baseFactory'; 5 | 6 | const convertItemIntoDocument = ( 7 | _: BaseFactoryConfig 8 | ): ItemIntoDocument => ({ item, req }) => ({ 9 | ...toSnake(item), 10 | id: req.body.id !== undefined ? req.body.id : item.id, 11 | }); 12 | 13 | export default convertItemIntoDocument; 14 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/enrolments/base/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UUID_LENGTH, 3 | } from '../../../../../../constants'; 4 | import Date from '../../../../../../utils/validation/rules/Date'; 5 | import Optional from '../../../../../../utils/validation/rules/Optional'; 6 | import String from '../../../../../../utils/validation/rules/String'; 7 | 8 | const baseSchema = { 9 | course_id: Optional(String(UUID_LENGTH, UUID_LENGTH)), 10 | deleted_at: Optional(Date()), 11 | user_id: Optional(String(UUID_LENGTH, UUID_LENGTH)), 12 | }; 13 | 14 | export default baseSchema; 15 | -------------------------------------------------------------------------------- /src/translator/default/translations/interfaces/Emails.ts: -------------------------------------------------------------------------------- 1 | export default interface Emails { 2 | readonly verifyYourEmailHtml: (link: string) => string; 3 | readonly verifyYourEmailSubject: () => string; 4 | readonly verifyYourEmailText: (link: string) => string; 5 | readonly remindPasswordHtml: (link: string) => string; 6 | readonly remindPasswordSubject: () => string; 7 | readonly remindPasswordText: (link: string) => string; 8 | readonly resetPasswordHtml: () => string; 9 | readonly resetPasswordSubject: () => string; 10 | readonly resetPasswordText: () => string; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/app/BaseAppConfig.ts: -------------------------------------------------------------------------------- 1 | import { AuthConfig } from '../../config/subconfigs/auth'; 2 | import { HttpConfig } from '../../config/subconfigs/http'; 3 | import { LoggerConfig } from '../../config/subconfigs/logger'; 4 | import { RepoConfig } from '../../config/subconfigs/repo'; 5 | import { TranslatorConfig } from '../../config/subconfigs/translator'; 6 | 7 | export default interface BaseAppConfig { 8 | readonly logger: LoggerConfig; 9 | readonly repo: RepoConfig; 10 | readonly auth: AuthConfig; 11 | readonly http: HttpConfig; 12 | readonly translator: TranslatorConfig; 13 | } 14 | -------------------------------------------------------------------------------- /src/presenter/express/utils/fakeFactories/roles/factory.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | import { STUDENT } from '../../../../../constants/roles'; 3 | import Role from '../../../../../types/items/Role'; 4 | import baseFactory, { Options } from '../index'; 5 | 6 | const createRoleItemData = async () => ({ 7 | id: uuid(), 8 | name: STUDENT, 9 | }); 10 | 11 | const roleFactory = async (options: Options) => { 12 | const itemData = await createRoleItemData(); 13 | 14 | return baseFactory(itemData as Partial)(options); 15 | }; 16 | 17 | export default roleFactory; 18 | -------------------------------------------------------------------------------- /src/presenter/commander/data/sections.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const sections = [ 4 | { 5 | id: uuid(), 6 | order: 1, 7 | title: 'First section', 8 | }, 9 | { 10 | id: uuid(), 11 | order: 2, 12 | title: 'Second section' 13 | }, 14 | { 15 | id: uuid(), 16 | order: 3, 17 | title: 'Third section' 18 | }, 19 | { 20 | id: uuid(), 21 | order: 4, 22 | title: 'Fourth section' 23 | }, 24 | { 25 | id: uuid(), 26 | order: 5, 27 | title: 'Fifth section' 28 | }, 29 | 30 | ]; 31 | 32 | export default sections; 33 | -------------------------------------------------------------------------------- /src/logger/winston/factory.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | import loggly from 'winston-loggly-bulk'; 3 | import FactoryConfig from './FactoryConfig'; 4 | 5 | export default (config: FactoryConfig): any => { 6 | const testEnv = process.env.NODE_ENV === 'test'; 7 | 8 | if (!testEnv && config.type === 'loggly') { 9 | winston.add(new loggly.Loggly(config.loggly)); 10 | } 11 | 12 | // only errors during testing to allow debugging 13 | const consoleOptions = testEnv ? { level: 'error' } : {}; 14 | winston.add(new winston.transports.Console(consoleOptions)); 15 | 16 | return winston; 17 | }; 18 | -------------------------------------------------------------------------------- /src/presenter/commander/app/index.ts: -------------------------------------------------------------------------------- 1 | import loggerFactory from '../../../logger/factory'; 2 | import repoFactory from '../../../repo/factory'; 3 | import serviceFactory from '../../../service/factory'; 4 | import presenterFactory from '../presenterFactory/factory'; 5 | import AppConfig from './AppConfig'; 6 | 7 | export default (appConfig: AppConfig) => { 8 | const repo = repoFactory(appConfig.repo); 9 | 10 | const logger = loggerFactory(appConfig.logger); 11 | 12 | const service = serviceFactory({ repo, logger, appConfig }); 13 | 14 | presenterFactory({ service, program: appConfig.program, logger }); 15 | }; 16 | -------------------------------------------------------------------------------- /src/presenter/express/utils/errors/catchErrors/index.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { v4 as uuid } from 'uuid'; 3 | import Config from '../../../presenterFactory/Config'; 4 | import ExpressHandler from '../../../types/ExpressHandler'; 5 | import handleError from '../handleError'; 6 | 7 | export default (config: Config, handler: ExpressHandler) => async ( 8 | req: Request, 9 | res: Response 10 | ) => { 11 | try { 12 | await handler(req, res); 13 | } catch (error) { 14 | const transactionId = uuid(); 15 | handleError({ config, req, res, error, transactionId }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/repo/model/knex/users/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import User from '../../../../types/items/User'; 3 | import userDocumentToItem from '../../../../utils/converters/users/documentToItem'; 4 | import userItemToDocument from '../../../../utils/converters/users/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: userDocumentToItem, 10 | convertItemIntoDocument: userItemToDocument, 11 | db: config.db, 12 | itemName: 'User', 13 | tableName: 'users', 14 | }); 15 | -------------------------------------------------------------------------------- /src/presenter/express/utils/tests/testData.ts: -------------------------------------------------------------------------------- 1 | export const TEST_INVALID_EMAIL = 'invalidemail@test'; 2 | export const TEST_VALID_EMAIL = 'valid.email@test.com'; 3 | export const TEST_DIFFERENT_VALID_EMAIL = 'another.email@test.com'; 4 | 5 | export const TEST_TOO_SHORT_PASSWORD = 'short'; 6 | export const TEST_INVALID_NOT_COMPLICATED_PASSWORD = 'longEnoughButToSimple'; 7 | export const TEST_VALID_PASSWORD = '#Az0ds?!@$%^&*-'; 8 | export const TEST_DIFFERENT_VALID_PASSWORD = '#Az35%^&*(0ds?!@$%^&*-'; 9 | 10 | export const TEST_UUID = '25742910-c654-4481-949f-c3f01e4e823f'; 11 | export const TEST_UTC_DATE = '2000-04-05T23:26:42.000Z'; 12 | -------------------------------------------------------------------------------- /src/presenter/commander/utils/permissions/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import categoriesPermissions from './items/categories'; 3 | import coursesPermissions from './items/courses'; 4 | import enrolmentsPermissions from './items/enrolments'; 5 | import permissionsPermissions from './items/permissions'; 6 | import rolesPermissions from './items/roles'; 7 | import usersPermissions from './items/users'; 8 | 9 | const permissions = [ 10 | ...categoriesPermissions, 11 | ...coursesPermissions, 12 | ...enrolmentsPermissions, 13 | ...usersPermissions, 14 | ...rolesPermissions, 15 | ...permissionsPermissions, 16 | ]; 17 | 18 | export default permissions; 19 | -------------------------------------------------------------------------------- /src/repo/model/knex/roles/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import Role from '../../../../types/items/Role'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'Role', 13 | tableName: 'roles', 14 | }); 15 | -------------------------------------------------------------------------------- /src/repo/model/knex/courses/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import Course from '../../../../types/items/Course'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'Course', 13 | tableName: 'courses', 14 | }); 15 | -------------------------------------------------------------------------------- /src/service/functions/auth/factory.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../FactoryConfig'; 2 | import login from './login'; 3 | import register from './register'; 4 | import remindPassword from './remindPassword'; 5 | import resendVerifyToken from './resendVerifyToken'; 6 | import resetPassword from './resetPassword'; 7 | import verifyAccount from './verifyAccount'; 8 | 9 | export default (config: Config) => ({ 10 | login: login(config), 11 | register: register(config), 12 | remindPassword: remindPassword(config), 13 | resendVerifyToken: resendVerifyToken(config), 14 | resetPassword: resetPassword(config), 15 | verifyAccount: verifyAccount(config), 16 | }); 17 | -------------------------------------------------------------------------------- /src/repo/model/knex/sections/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import Section from '../../../../types/items/Section'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory
({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'Section', 13 | tableName: 'sections', 14 | }); 15 | -------------------------------------------------------------------------------- /src/repo/model/knex/categories/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import Category from '../../../../types/items/Category'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'Category', 13 | tableName: 'categories', 14 | }); 15 | -------------------------------------------------------------------------------- /src/repo/model/knex/userRole/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import UserRole from '../../../../types/items/UserRole'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'UserRole', 13 | tableName: 'user_role', 14 | }); 15 | -------------------------------------------------------------------------------- /src/utils/converters/sections/documentToItem.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import documentToItemBoolean from '../helpers/documentToItemBoolean'; 4 | import documentToItemNumber from '../helpers/documentToItemNumber'; 5 | import recursiveConverter from '../recursiveConverter'; 6 | 7 | const convertersMap = { 8 | ...baseMap, 9 | isPublished: documentToItemBoolean, 10 | order: documentToItemNumber, 11 | }; 12 | 13 | const documentToItem = (document: Document): I => 14 | recursiveConverter({ obj: document, convertersMap }) as I; 15 | 16 | export default documentToItem; 17 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/permissions/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import Record from 'rulr/Record'; 2 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 3 | import Optional from '../../../../../../utils/validation/rules/Optional'; 4 | import String from '../../../../../../utils/validation/rules/String'; 5 | 6 | import baseSchema from '../base/schema'; 7 | 8 | export const beforeUpdateSchema = { 9 | ...baseSchema, 10 | method: Optional(String(0, VARCHAR_LENGTH)), 11 | name: Optional(String(0, VARCHAR_LENGTH)), 12 | url: Optional(String(0, VARCHAR_LENGTH)), 13 | }; 14 | 15 | const rules = Record(beforeUpdateSchema); 16 | 17 | export default rules; 18 | -------------------------------------------------------------------------------- /src/repo/model/knex/enrolments/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import Enrolment from '../../../../types/items/Enrolment'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'Enrolment', 13 | tableName: 'enrolments', 14 | }); 15 | -------------------------------------------------------------------------------- /src/repo/model/knex/permissions/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import Permission from '../../../../types/items/Permission'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'Permission', 13 | tableName: 'permissions', 14 | }); 15 | -------------------------------------------------------------------------------- /src/constants/permissions/courses.ts: -------------------------------------------------------------------------------- 1 | export const COURSES_GET_ITEM = 'courses.getItem'; 2 | export const COURSES_GET_ITEMS = 'courses.getItems'; 3 | export const COURSES_DELETE_ITEM = 'courses.deleteItem'; 4 | export const COURSES_DELETE_ITEMS = 'courses.deleteItems'; 5 | export const COURSES_UPDATE_ITEM = 'courses.updateItem'; 6 | export const COURSES_REPLACE_ITEM = 'courses.replaceItem'; 7 | export const COURSES_CREATE_ITEM = 'courses.createItem'; 8 | 9 | export const coursesPermissions = [ 10 | COURSES_GET_ITEM, 11 | COURSES_GET_ITEMS, 12 | COURSES_DELETE_ITEM, 13 | COURSES_DELETE_ITEMS, 14 | COURSES_UPDATE_ITEM, 15 | COURSES_REPLACE_ITEM, 16 | COURSES_CREATE_ITEM, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/users/functions/createPatch/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import User from '../../../../../../../../types/items/User'; 5 | import Config from '../../../../../../presenterFactory/Config'; 6 | import { schema } from '../../../../../../utils/schemas/users/createItem/index'; 7 | 8 | const createPatch = (_config: Config): DocumentIntoItem => ({ 9 | document, 10 | }) =>{ 11 | const data = _pick(Object.keys(schema), document); 12 | 13 | return toCamel(data); 14 | } 15 | 16 | export default createPatch; 17 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import auth, { AuthConfig } from './subconfigs/auth'; 2 | import http, { HttpConfig } from './subconfigs/http'; 3 | import logger, { LoggerConfig } from './subconfigs/logger'; 4 | import repo, { RepoConfig } from './subconfigs/repo'; 5 | import translator, { TranslatorConfig } from './subconfigs/translator'; 6 | 7 | export interface Config { 8 | readonly http: HttpConfig; 9 | readonly auth: AuthConfig; 10 | readonly repo: RepoConfig; 11 | readonly logger: LoggerConfig; 12 | readonly translator: TranslatorConfig; 13 | } 14 | 15 | const config: Config = { 16 | auth, 17 | http, 18 | logger, 19 | repo, 20 | translator, 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /src/repo/model/knex/rolePermission/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import RolePermission from '../../../../types/items/RolePermission'; 3 | import baseDocumentToItem from '../../../../utils/converters/baseConverter/documentToItem'; 4 | import baseItemToDocument from '../../../../utils/converters/baseConverter/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: baseDocumentToItem, 10 | convertItemIntoDocument: baseItemToDocument, 11 | db: config.db, 12 | itemName: 'RolePermission', 13 | tableName: 'role_permission', 14 | }); 15 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/sections/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import Section from '../../../../../../../../types/items/Section'; 5 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 6 | 7 | const convertDocumentIntoItem = ( 8 | config: BaseFactoryConfig 9 | ): DocumentIntoItem
=> ({ document }) => { 10 | const data = _pick( 11 | Object.keys(config.beforeCreateSchema), 12 | document 13 | ); 14 | 15 | return toCamel(data); 16 | }; 17 | 18 | export default convertDocumentIntoItem; 19 | -------------------------------------------------------------------------------- /src/utils/validation/rules/Enum.ts: -------------------------------------------------------------------------------- 1 | import Rule from 'rulr/Rule'; 2 | import EnumValidationError from '../../errors/validation/EnumValidationError'; 3 | 4 | export const getEnumValues = (enumType: T): any[] => { 5 | const keys = Object.keys(enumType); 6 | 7 | const numKeys = keys.filter((val: string) => !Number.isNaN(val as any)); 8 | 9 | if (numKeys.length === 0) { 10 | return keys; 11 | } 12 | 13 | return numKeys; 14 | }; 15 | 16 | export default (enumType: T): Rule => (data: T) => { 17 | const enumValues = getEnumValues(enumType); 18 | 19 | if (enumValues.includes(data)) { 20 | return []; 21 | } 22 | 23 | return [new EnumValidationError(data, enumValues)]; 24 | }; 25 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/courses/base/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TEXT_LENGTH, 3 | UUID_LENGTH, 4 | VARCHAR_LENGTH, 5 | } from '../../../../../../constants'; 6 | import Date from '../../../../../../utils/validation/rules/Date'; 7 | import Optional from '../../../../../../utils/validation/rules/Optional'; 8 | import String from '../../../../../../utils/validation/rules/String'; 9 | 10 | const baseSchema = { 11 | deleted_at: Optional(Date()), 12 | description: Optional(String(0, TEXT_LENGTH)), 13 | slug: Optional(String(0, VARCHAR_LENGTH)), 14 | title: Optional(String(0, VARCHAR_LENGTH)), 15 | user_id: Optional(String(UUID_LENGTH, UUID_LENGTH)), 16 | }; 17 | 18 | export default baseSchema; 19 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/courses/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import Course from '../../../../../../../../types/items/Course'; 5 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 6 | 7 | const convertDocumentIntoItem = ( 8 | config: BaseFactoryConfig 9 | ): DocumentIntoItem => ({ document }) => { 10 | const data = _pick( 11 | [...Object.keys(config.beforeCreateSchema), 'user_id'], 12 | document 13 | ); 14 | 15 | return toCamel(data); 16 | }; 17 | 18 | export default convertDocumentIntoItem; 19 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/users/functions/convertItemIntoDocument/index.ts: -------------------------------------------------------------------------------- 1 | import ItemIntoDocument from '@js-items/express/dist/types/ItemIntoDocument'; 2 | import { toSnake } from 'convert-keys'; 3 | import User from '../../../../../../../../types/items/User'; 4 | import getVisiblePublicUserProperties from '../../../../../../../../utils/helpers/model/getVisiblePublicUserProperties'; 5 | import Config from '../../../../../../presenterFactory/Config'; 6 | 7 | const convertItemIntoDocument = (_config: Config): ItemIntoDocument => ({ 8 | item, 9 | }) => { 10 | const user = getVisiblePublicUserProperties(item); 11 | 12 | return toSnake(user); 13 | }; 14 | 15 | export default convertItemIntoDocument; 16 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/categories/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import Course from '../../../../../../../../types/items/Course'; 5 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 6 | 7 | const convertDocumentIntoItem = ( 8 | config: BaseFactoryConfig 9 | ): DocumentIntoItem => ({ document }) => { 10 | const data = _pick( 11 | [...Object.keys(config.beforeCreateSchema), 'user_id'], 12 | document 13 | ); 14 | 15 | return toCamel(data); 16 | }; 17 | 18 | export default convertDocumentIntoItem; 19 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/users/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import User from '../../../../../../../../types/items/User'; 5 | import Config from '../../../../../../presenterFactory/Config'; 6 | import { schema } from '../../../../../../utils/schemas/users/createItem/index'; 7 | 8 | const convertDocumentIntoItem = (_config: Config): DocumentIntoItem => ({ 9 | document, 10 | }) => { 11 | const data = _pick(Object.keys(schema), document); 12 | 13 | return toCamel(data); 14 | }; 15 | 16 | export default convertDocumentIntoItem; 17 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/describeApi/index.spec.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import assertOnResponseAndStatus from '../../../../../utils/tests/assertOnResponseAndStatus'; 3 | dotenv.config(); 4 | import { OK } from 'http-status-codes'; 5 | import { API_V1 } from '../../../../../../../constants/routes'; 6 | import initTests from '../../../../../utils/tests/initTests'; 7 | 8 | describe('@presenter/describeApi', () => { 9 | const { request } = initTests({ useMailServer: true }); 10 | 11 | it('describes API metadata', async () => { 12 | await assertOnResponseAndStatus({ 13 | method: 'get', 14 | request, 15 | statusCode: OK, 16 | url: API_V1, 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/enrolments/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import Enrolment from '../../../../../../../../types/items/Enrolment'; 5 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 6 | 7 | const convertDocumentIntoItem = ( 8 | config: BaseFactoryConfig 9 | ): DocumentIntoItem => ({ document }) => { 10 | const data = _pick( 11 | [...Object.keys(config.beforeCreateSchema), 'user_id'], 12 | document 13 | ); 14 | 15 | return toCamel(data); 16 | }; 17 | 18 | export default convertDocumentIntoItem; 19 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeDeleteItems/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../../../presenterFactory/Config'; 2 | import getAuthenticatedUser from '../../../../../utils/auth/getAuthenticatedUser'; 3 | import hasPermission from '../../../../../utils/auth/hasPermission'; 4 | import transactionWrapper, { 5 | HookOptions, 6 | } from '../../../../../utils/handlers/transactionWrapper'; 7 | 8 | const beforeDeleteItems = (config: Config) => 9 | transactionWrapper({ 10 | beforeHandler: async ({ req }: HookOptions) => { 11 | const user = await getAuthenticatedUser({ req, config }); 12 | 13 | await hasPermission({ req, user, config }); 14 | }, 15 | config, 16 | }); 17 | 18 | export default beforeDeleteItems; 19 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/users/createItem/index.ts: -------------------------------------------------------------------------------- 1 | import Intersection from 'rulr/Intersection'; 2 | import Record from 'rulr/Record'; 3 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 4 | import Email from '../../../../../../utils/validation/rules/Email'; 5 | import Password from '../../../../../../utils/validation/rules/Password'; 6 | import String from '../../../../../../utils/validation/rules/String'; 7 | import baseSchema from '../base/schema'; 8 | 9 | export const schema = { 10 | ...baseSchema, 11 | email: Intersection([String(0, VARCHAR_LENGTH), Email()]), 12 | password: Intersection([String(0, VARCHAR_LENGTH), Password()]), 13 | }; 14 | 15 | const rules = Record(schema); 16 | 17 | export default rules; 18 | -------------------------------------------------------------------------------- /src/translator/default/factory.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import getLocale from '../../presenter/express/utils/translations/locales/getLocale'; 3 | import FactoryConfig from './FactoryConfig'; 4 | import en from './translations/en'; 5 | import pl from './translations/pl'; 6 | 7 | export interface Options { 8 | readonly req?: Request; 9 | } 10 | 11 | export default ({ defaultLocale, queryParam, headerName }: FactoryConfig) => ({ 12 | req, 13 | }: Options) => { 14 | const locale = getLocale({ 15 | defaultLocale, 16 | headerName, 17 | queryParam, 18 | req, 19 | }); 20 | 21 | switch (locale) { 22 | case 'pl': 23 | return pl; 24 | case 'en': 25 | default: 26 | return en; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/constants/permissions/categories.ts: -------------------------------------------------------------------------------- 1 | export const CATEGORIES_GET_ITEM = 'categories.getItem'; 2 | export const CATEGORIES_GET_ITEMS = 'categories.getItems'; 3 | export const CATEGORIES_DELETE_ITEM = 'categories.deleteItem'; 4 | export const CATEGORIES_DELETE_ITEMS = 'categories.deleteItems'; 5 | export const CATEGORIES_UPDATE_ITEM = 'categories.updateItem'; 6 | export const CATEGORIES_REPLACE_ITEM = 'categories.replaceItem'; 7 | export const CATEGORIES_CREATE_ITEM = 'categories.createItem'; 8 | 9 | export const catergoriesPermissions = [ 10 | CATEGORIES_GET_ITEM, 11 | CATEGORIES_GET_ITEMS, 12 | CATEGORIES_DELETE_ITEM, 13 | CATEGORIES_DELETE_ITEMS, 14 | CATEGORIES_UPDATE_ITEM, 15 | CATEGORIES_REPLACE_ITEM, 16 | CATEGORIES_CREATE_ITEM, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/constants/permissions/enrolments.ts: -------------------------------------------------------------------------------- 1 | export const ENROLMENTS_GET_ITEM = 'enrolments.getItem'; 2 | export const ENROLMENTS_GET_ITEMS = 'enrolments.getItems'; 3 | export const ENROLMENTS_DELETE_ITEM = 'enrolments.deleteItem'; 4 | export const ENROLMENTS_DELETE_ITEMS = 'enrolments.deleteItems'; 5 | export const ENROLMENTS_UPDATE_ITEM = 'enrolments.updateItem'; 6 | export const ENROLMENTS_REPLACE_ITEM = 'enrolments.replaceItem'; 7 | export const ENROLMENTS_CREATE_ITEM = 'enrolments.createItem'; 8 | 9 | export const enrolmentsPermissions = [ 10 | ENROLMENTS_GET_ITEM, 11 | ENROLMENTS_GET_ITEMS, 12 | ENROLMENTS_DELETE_ITEM, 13 | ENROLMENTS_DELETE_ITEMS, 14 | ENROLMENTS_UPDATE_ITEM, 15 | ENROLMENTS_REPLACE_ITEM, 16 | ENROLMENTS_CREATE_ITEM, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/roles/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import Role from '../../../../../../../../types/items/Role'; 5 | import { beforeCreateSchema } from '../../../../../../utils/schemas/roles/createItem/index'; 6 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 7 | 8 | const convertDocumentIntoItem = (_config: BaseFactoryConfig): DocumentIntoItem => ({ 9 | document, 10 | }) => { 11 | const data = _pick(Object.keys(beforeCreateSchema), document); 12 | 13 | return toCamel(data); 14 | }; 15 | 16 | export default convertDocumentIntoItem; 17 | -------------------------------------------------------------------------------- /src/presenter/express/utils/fakeFactories/resetPasswordTokens/factory.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-magic-numbers 2 | import ResetPasswordToken from '../../../../../types/items/ResetPasswordToken'; 3 | import { TEST_UUID } from '../../tests/testData'; 4 | import baseFactory, { Options } from '../index'; 5 | 6 | const constantDate = new Date('2019-03-27T21:32:31.000Z'); 7 | 8 | const resetPasswordTokensFactory = async ( 9 | options: Options 10 | ) => 11 | // tslint:disable-next-line:no-object-literal-type-assertion 12 | baseFactory({ 13 | expiresAt: constantDate, 14 | id: TEST_UUID, 15 | userId: TEST_UUID, 16 | } as Partial)(options); 17 | 18 | export default resetPasswordTokensFactory; 19 | -------------------------------------------------------------------------------- /src/service/functions/auth/utils/getRolesForUser/index.ts: -------------------------------------------------------------------------------- 1 | import _pluck from 'ramda/src/pluck'; 2 | import repoFactory from '../../../../../repo/factory'; 3 | 4 | export interface Options { 5 | readonly userId: string; 6 | readonly repo: ReturnType; 7 | } 8 | 9 | const getRolesForUser = async ({ repo, userId }: Options) => { 10 | const { items: userRoles } = await repo.userRole.getItems({ 11 | filter: { 12 | userId, 13 | }, 14 | }); 15 | 16 | const rolesIds = _pluck('roleId', userRoles); 17 | 18 | const { items } = await repo.roles.getItems({ 19 | filter: { 20 | id: { 21 | $in: rolesIds, 22 | }, 23 | }, 24 | }); 25 | 26 | return items; 27 | }; 28 | 29 | export default getRolesForUser; 30 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/permissions/functions/convertDocumentIntoItem/index.ts: -------------------------------------------------------------------------------- 1 | import DocumentIntoItem from '@js-items/express/dist/types/DocumentIntoItem'; 2 | import { toCamel } from 'convert-keys'; 3 | import _pick from 'ramda/src/pick'; 4 | import Permission from '../../../../../../../../types/items/Permission'; 5 | import Config from '../../../../../../presenterFactory/Config'; 6 | import { beforeCreateSchema } from '../../../../../../utils/schemas/permissions/createItem/index'; 7 | 8 | const convertDocumentIntoItem = ( 9 | _config: Config 10 | ): DocumentIntoItem => ({ document }) => { 11 | const data = _pick(Object.keys(beforeCreateSchema), document); 12 | 13 | return toCamel(data); 14 | }; 15 | 16 | export default convertDocumentIntoItem; 17 | -------------------------------------------------------------------------------- /src/utils/helpers/config/getStringValue/index.spec.ts: -------------------------------------------------------------------------------- 1 | import getStringValue from './index'; 2 | 3 | describe('getStringValue', () => { 4 | const defaultValue = 'slon'; 5 | 6 | it('gets string value when string value provided', () => { 7 | const expectedValue = 'zebra'; 8 | 9 | expect(getStringValue('zebra', defaultValue)).toEqual(expectedValue); 10 | }); 11 | 12 | it('gets default value when undefined provided', () => { 13 | const expectedValue = 'slon'; 14 | 15 | expect(getStringValue(undefined, defaultValue)).toEqual(expectedValue); 16 | }); 17 | 18 | it('gets default value when null provided', () => { 19 | const expectedValue = 'slon'; 20 | 21 | expect(getStringValue(null, defaultValue)).toEqual(expectedValue); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /k8s/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- $fullName := include "kube-ts-server.fullname" . -}} 2 | {{- $name := include "kube-ts-server.name" . -}} 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: {{ include "kube-ts-server.fullname" . }} 7 | labels: 8 | app.kubernetes.io/name: {{ $name }} 9 | app.kubernetes.io/instance: {{ .Release.Name }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | helm.sh/chart: {{ include "kube-ts-server.chart" . }} 12 | spec: 13 | ports: 14 | - name: api-port 15 | port: {{ .Values.service.port }} 16 | targetPort: {{ .Values.service.targetPort }} 17 | selector: 18 | app.kubernetes.io/name: {{ $name }} 19 | app.kubernetes.io/instance: {{ .Release.Name }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/constants/permissions/permissions.ts: -------------------------------------------------------------------------------- 1 | export const PERMISSIONS_GET_ITEM = 'permissions.getItem'; 2 | export const PERMISSIONS_GET_ITEMS = 'permissions.getItems'; 3 | export const PERMISSIONS_DELETE_ITEM = 'permissions.deleteItem'; 4 | export const PERMISSIONS_DELETE_ITEMS = 'permissions.deleteItems'; 5 | export const PERMISSIONS_UPDATE_ITEM = 'permissions.updateItem'; 6 | export const PERMISSIONS_REPLACE_ITEM = 'permissions.replaceItem'; 7 | export const PERMISSIONS_CREATE_ITEM = 'permissions.createItem'; 8 | 9 | export const permissionsPermissions = [ 10 | PERMISSIONS_GET_ITEM, 11 | PERMISSIONS_GET_ITEMS, 12 | PERMISSIONS_DELETE_ITEM, 13 | PERMISSIONS_DELETE_ITEMS, 14 | PERMISSIONS_UPDATE_ITEM, 15 | PERMISSIONS_REPLACE_ITEM, 16 | PERMISSIONS_CREATE_ITEM, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/createExtractTokenFromRequest/extractors/createHeaderExtractor/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import authConfig from '../../../../../../config/subconfigs/auth'; 3 | import isString from '../../../../commons/isString'; 4 | // @credits: https://github.com/themikenicholson/passport-jwt 5 | 6 | export interface FromHeaderExtractorOptions { 7 | authHeaderName?: string; 8 | } 9 | 10 | const createFromHeaderExtractor = ({ 11 | authHeaderName = authConfig.jwt.authHeaderName, 12 | }: FromHeaderExtractorOptions) => (req: Request): string | null => { 13 | if (isString(req.headers[authHeaderName])) { 14 | return (req.headers as any)[authHeaderName]; 15 | } 16 | 17 | return null; 18 | }; 19 | 20 | export default createFromHeaderExtractor; 21 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories.ts: -------------------------------------------------------------------------------- 1 | import businessCategory from './categories/business'; 2 | import designCategory from './categories/design'; 3 | import healthAndFitnessCategory from './categories/healthAndFitnessCategory'; 4 | import marketingCategory from './categories/marketing'; 5 | import personalDevelopmentCategory from './categories/personalDevelopment'; 6 | import photographyCategory from './categories/photography'; 7 | import softwareDevelopmentCategory from './categories/softwateDevelopment'; 8 | 9 | const categories = [ 10 | photographyCategory, 11 | marketingCategory, 12 | personalDevelopmentCategory, 13 | designCategory, 14 | softwareDevelopmentCategory, 15 | businessCategory, 16 | healthAndFitnessCategory, 17 | ]; 18 | 19 | 20 | export default categories; 21 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/sections/updateItem/index.ts: -------------------------------------------------------------------------------- 1 | import isBoolean from 'rulr/Boolean'; 2 | import isNumber from 'rulr/Number'; 3 | import Record from 'rulr/Record'; 4 | import { UUID_LENGTH, VARCHAR_LENGTH } from '../../../../../../constants'; 5 | import Optional from '../../../../../../utils/validation/rules/Optional'; 6 | import String from '../../../../../../utils/validation/rules/String'; 7 | import baseSchema from '../base/schema'; 8 | 9 | export const beforeUpdateSchema = { 10 | ...baseSchema, 11 | course_id: Optional(String(UUID_LENGTH, UUID_LENGTH)), 12 | is_published: Optional(isBoolean), 13 | order: Optional(isNumber()), 14 | title: Optional(String(0, VARCHAR_LENGTH)), 15 | }; 16 | 17 | const rules = Record(beforeUpdateSchema); 18 | 19 | export default rules; 20 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/users/base/schema.ts: -------------------------------------------------------------------------------- 1 | import { TEXT_LENGTH, VARCHAR_LENGTH } from '../../../../../../constants'; 2 | import { GenderType } from '../../../../../../types/items/User'; 3 | import Date from '../../../../../../utils/validation/rules/Date'; 4 | import Enum from '../../../../../../utils/validation/rules/Enum'; 5 | import Optional from '../../../../../../utils/validation/rules/Optional'; 6 | import String from '../../../../../../utils/validation/rules/String'; 7 | 8 | const baseSchema = { 9 | bio: Optional(String(0, TEXT_LENGTH)), 10 | date_of_birth: Optional(Date()), 11 | first_name: Optional(String(0, VARCHAR_LENGTH)), 12 | gender: Optional(Enum(GenderType)), 13 | last_name: Optional(String(0, VARCHAR_LENGTH)), 14 | }; 15 | 16 | export default baseSchema; 17 | -------------------------------------------------------------------------------- /src/presenter/express/utils/translations/locales/getLocaleFromQueryParam/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { SUPPORTED_LOCALES } from '../../../../../../constants'; 3 | import isString from '../../../../../../utils/helpers/commons/isString'; 4 | 5 | export interface Options { 6 | readonly req: Request; 7 | readonly queryParam: string; 8 | } 9 | 10 | const getLocaleFromQueryParam = ({ req, queryParam }: Options) => { 11 | const queryParameter = req.query[queryParam]; 12 | if (!isString(queryParameter)) { 13 | return null; 14 | } 15 | 16 | const normalisedQueryParam = queryParameter.toLowerCase(); 17 | 18 | return SUPPORTED_LOCALES.includes(normalisedQueryParam) 19 | ? normalisedQueryParam 20 | : null; 21 | }; 22 | export default getLocaleFromQueryParam; 23 | -------------------------------------------------------------------------------- /src/repo/model/knex/resetPasswordTokens/factory.ts: -------------------------------------------------------------------------------- 1 | import knexFactory from '@js-items/knex/dist/factory'; 2 | import ResetPasswordToken from '../../../../types/items/ResetPasswordToken'; 3 | import resetPasswordTokensDocumentToItem from '../../../../utils/converters/resetPasswordTokens/documentToItem'; 4 | import resetPasswordTokensItemToDocument from '../../../../utils/converters/resetPasswordTokens/itemToDocument'; 5 | import { RepoConfig } from '../factory'; 6 | 7 | export default (config: RepoConfig) => 8 | knexFactory({ 9 | convertDocumentIntoItem: resetPasswordTokensDocumentToItem, 10 | convertItemIntoDocument: resetPasswordTokensItemToDocument, 11 | db: config.db, 12 | itemName: 'Reset Password Tokens', 13 | tableName: 'reset_password_tokens', 14 | }); 15 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/createExtractTokenFromRequest/extractors/createBodyFieldExtractor/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import authConfig from '../../../../../../config/subconfigs/auth'; 3 | import isString from '../../../../commons/isString'; 4 | // @credits: https://github.com/themikenicholson/passport-jwt 5 | 6 | export interface FromBodyFieldExtractorOptions { 7 | authBodyFieldName?: string; 8 | } 9 | 10 | const createBodyFieldExtractor = ({ 11 | authBodyFieldName = authConfig.jwt.authBodyFieldName, 12 | }: FromBodyFieldExtractorOptions) => (req: Request): string | null => { 13 | if (req.body && isString(req.body[authBodyFieldName])) { 14 | return req.body[authBodyFieldName]; 15 | } 16 | 17 | return null; 18 | }; 19 | 20 | export default createBodyFieldExtractor; 21 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/generateToken/index.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | import { v4 } from 'uuid'; 3 | import { JwtConfig } from '../../../../config/subconfigs/auth'; 4 | import User from '../../../../types/items/User'; 5 | import UndefinedJWTSecretError from '../../../errors/auth/UndefinedJWTSecretError'; 6 | 7 | export interface Options { 8 | readonly data: Partial; 9 | readonly config: JwtConfig; 10 | } 11 | 12 | export default ({ data, config }: Options): string => { 13 | if (config.secret === undefined) { 14 | throw new UndefinedJWTSecretError(); 15 | } 16 | 17 | const token = jwt.sign({ data, jti: v4() }, config.secret, { 18 | algorithm: config.algoritm, 19 | expiresIn: config.expiresIn, 20 | }); 21 | 22 | return `${config.authSchemeName} ${token}`; 23 | }; 24 | -------------------------------------------------------------------------------- /src/constants/permissions/users.ts: -------------------------------------------------------------------------------- 1 | export const USERS_GET_ITEM = 'users.getItem'; 2 | export const USERS_GET_ITEMS = 'users.getItems'; 3 | export const USERS_DELETE_ITEM = 'users.deleteItem'; 4 | export const USERS_DELETE_ITEMS = 'users.deleteItems'; 5 | export const USERS_UPDATE_ITEM = 'users.updateItem'; 6 | export const USERS_REPLACE_ITEM = 'users.replaceItem'; 7 | export const USERS_CREATE_ITEM = 'users.createItem'; 8 | export const USERS_ASSIGN_ROLE = 'users.assignUserRole'; 9 | export const USERS_REVOKE_ROLE = 'users.revokeUserRole'; 10 | 11 | export const usersPermissions = [ 12 | USERS_GET_ITEM, 13 | USERS_GET_ITEMS, 14 | USERS_DELETE_ITEM, 15 | USERS_DELETE_ITEMS, 16 | USERS_UPDATE_ITEM, 17 | USERS_REPLACE_ITEM, 18 | USERS_CREATE_ITEM, 19 | USERS_ASSIGN_ROLE, 20 | USERS_REVOKE_ROLE, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/utils/helpers/config/getNumberValue/index.spec.ts: -------------------------------------------------------------------------------- 1 | import getNumberValue from './index'; 2 | 3 | describe('getNumberValue', () => { 4 | it('gets number value when numeric value provided', () => { 5 | const defaultValue = 15; 6 | const expectedValue = 12; 7 | 8 | expect(getNumberValue('12', defaultValue)).toEqual(expectedValue); 9 | }); 10 | 11 | it('gets number value when non numeric value provided', () => { 12 | const defaultValue = 15; 13 | const expectedValue = 15; 14 | 15 | expect(getNumberValue(undefined, defaultValue)).toEqual(expectedValue); 16 | }); 17 | 18 | it('gets number value when zero provided', () => { 19 | const defaultValue = 15; 20 | const expectedValue = 0; 21 | 22 | expect(getNumberValue(0, defaultValue)).toEqual(expectedValue); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/utils/converters/users/itemToDocument.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import itemToDocumentDateTime from '../helpers/itemToDocumentDateTime'; 4 | import recursiveConverter from '../recursiveConverter'; 5 | 6 | const convertersMap = { 7 | ...baseMap, 8 | dateOfBirth: itemToDocumentDateTime, 9 | deletedAt: itemToDocumentDateTime, 10 | loginLastAttemptAt: itemToDocumentDateTime, 11 | loginLockoutExpiresAt: itemToDocumentDateTime, 12 | verifiedAt: itemToDocumentDateTime, 13 | verifyLastAttemptAt: itemToDocumentDateTime, 14 | verifyLockoutExpiresAt: itemToDocumentDateTime, 15 | }; 16 | 17 | const itemToDocument = (document: Document) => 18 | recursiveConverter({ obj: document, convertersMap }); 19 | 20 | export default itemToDocument; 21 | -------------------------------------------------------------------------------- /src/repo/mail/nodemailer/factory.ts: -------------------------------------------------------------------------------- 1 | import { createTransport } from 'nodemailer'; 2 | import { NodeMailerConfig } from '../../../config/subconfigs/repo/mail'; 3 | import sendEmail from './functions/sendEmail'; 4 | 5 | const createMailer = ({ 6 | user, 7 | pass, 8 | domain, 9 | api_key, 10 | host, 11 | smtpTestHost, 12 | ...otherOptions 13 | }: NodeMailerConfig) => { 14 | const smtpHost = process.env.NODE_ENV === 'test' ? smtpTestHost : host; 15 | 16 | return createTransport({ 17 | auth: { 18 | api_key, 19 | domain, 20 | pass, 21 | user, 22 | }, 23 | host: smtpHost, 24 | ...otherOptions, 25 | }); 26 | }; 27 | 28 | export default (config: NodeMailerConfig) => { 29 | const mailer = createMailer(config); 30 | 31 | return { 32 | sendEmail: sendEmail({ mailer }), 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/converters/recursiveConverter/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _mapObjIndexed from 'ramda/src/mapObjIndexed'; 3 | import _T from 'ramda/src/T'; 4 | 5 | export type Converter = (propertyValue: T | T[keyof T]) => any; 6 | 7 | export type ConvertersMap = { [Z in keyof T]?: undefined | Converter }; 8 | 9 | export interface Options { 10 | readonly convertersMap: ConvertersMap; 11 | readonly obj: { [Z in keyof T]: any }; 12 | } 13 | 14 | export default ({ obj, convertersMap = {} }: Options) => 15 | Object.keys(obj).reduce((acc, keyName) => { 16 | const converter = convertersMap[keyName as keyof T]; 17 | const value = obj[keyName as keyof T]; 18 | const newValue = _isNil(converter) ? value : converter(value); 19 | 20 | return { ...acc, [keyName]: newValue }; 21 | }, {}); 22 | -------------------------------------------------------------------------------- /src/constants/permissions/roles.ts: -------------------------------------------------------------------------------- 1 | export const ROLES_GET_ITEM = 'roles.getItem'; 2 | export const ROLES_GET_ITEMS = 'roles.getItems'; 3 | export const ROLES_DELETE_ITEM = 'roles.deleteItem'; 4 | export const ROLES_DELETE_ITEMS = 'roles.deleteItems'; 5 | export const ROLES_UPDATE_ITEM = 'roles.updateItem'; 6 | export const ROLES_REPLACE_ITEM = 'roles.replaceItem'; 7 | export const ROLES_CREATE_ITEM = 'roles.createItem'; 8 | export const ROLES_ASSIGN_PERMISSION = 'roles.assignRolePermission'; 9 | export const ROLES_REVOKE_PERMISSION = 'roles.revokeRolePermission'; 10 | 11 | export const rolesPermissions = [ 12 | ROLES_GET_ITEM, 13 | ROLES_GET_ITEMS, 14 | ROLES_DELETE_ITEM, 15 | ROLES_DELETE_ITEMS, 16 | ROLES_UPDATE_ITEM, 17 | ROLES_REPLACE_ITEM, 18 | ROLES_CREATE_ITEM, 19 | ROLES_ASSIGN_PERMISSION, 20 | ROLES_REVOKE_PERMISSION, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/presenter/commander/utils/convertToHtml/index.ts: -------------------------------------------------------------------------------- 1 | import _isEmpty from 'ramda/src/isEmpty'; 2 | import _isNil from 'ramda/src/isNil'; 3 | 4 | export interface Options { 5 | readonly text: string; 6 | readonly delimiter?: string; 7 | readonly elementTag?: string; 8 | readonly parentElement?: string; 9 | } 10 | 11 | const convertToHtml = ({ 12 | text = '', 13 | delimiter = '.', 14 | elementTag = 'p', 15 | parentElement, 16 | }: Options) => { 17 | if (_isNil(text) || _isEmpty(text)) { 18 | return ''; 19 | } 20 | 21 | const elements = text 22 | .split(delimiter) 23 | .map(textLine => `<${elementTag}>${textLine}`); 24 | 25 | return parentElement !== undefined 26 | ? `<${parentElement}>${elements.join('')}` 27 | : elements.join(''); 28 | }; 29 | 30 | export default convertToHtml; 31 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # exit script when any command ran here returns with non-zero exit code 3 | set -e 4 | 5 | cd ~/repo/k8s 6 | 7 | export APP_NAME="kube-ts-server" 8 | 9 | envsubst <~/repo/k8s/values-circleci.yaml >~/repo/k8s/values-circleci.yaml.out 10 | mv ~/repo/k8s/values-circleci.yaml.out ~/repo/k8s/values-circleci.yaml 11 | 12 | envsubst <~/repo/k8s/cicd-config.yaml >~/repo/k8s/cicd-config.yaml.out 13 | mv ~/repo/k8s/cicd-config.yaml.out ~/repo/k8s/cicd-config.yaml 14 | 15 | echo "initialising helm..." 16 | helm init --client-only 17 | 18 | helm --kubeconfig ./cicd-config.yaml list 19 | 20 | echo "installing/upgrading new release..." 21 | 22 | helm upgrade --install --wait --kubeconfig ./cicd-config.yaml ${APP_NAME} . -f ./values-circleci.yaml 23 | 24 | helm --kubeconfig ./cicd-config.yaml list 25 | 26 | echo "deployment completed..." 27 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createRolesTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_roles_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('roles', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table.string('name').unique(); 13 | table.dateTime('createdAt').notNullable(); 14 | table.dateTime('updatedAt').nullable(); 15 | }); 16 | 17 | await Promise.resolve(query); 18 | }; 19 | 20 | const down = async () => { 21 | const connection = await db(); 22 | 23 | await Promise.resolve(connection.schema.dropTable('roles')); 24 | }; 25 | 26 | return { key, up, down }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeDeleteItem/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '../../../../../presenterFactory/Config'; 2 | import getAuthenticatedUser from '../../../../../utils/auth/getAuthenticatedUser'; 3 | import hasPermission from '../../../../../utils/auth/hasPermission'; 4 | import transactionWrapper, { 5 | HookOptions, 6 | } from '../../../../../utils/handlers/transactionWrapper'; 7 | 8 | const beforeDeleteItem = (config: Config) => 9 | transactionWrapper({ 10 | beforeHandler: async ({ req }: HookOptions) => { 11 | const user = await getAuthenticatedUser({ req, config }); 12 | 13 | // FYI: user should be able to delete itself without permission 14 | if (req.params.id !== user.id) { 15 | await hasPermission({ req, user, config }); 16 | } 17 | }, 18 | config, 19 | }); 20 | 21 | export default beforeDeleteItem; 22 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/auth/resetPassword/index.ts: -------------------------------------------------------------------------------- 1 | import Intersection from 'rulr/Intersection'; 2 | import Object from 'rulr/Object'; 3 | import Record from 'rulr/Record'; 4 | import { UUID_LENGTH, VARCHAR_LENGTH } from '../../../../../../constants'; 5 | import Match from '../../../../../../utils/validation/rules/Match'; 6 | import Password from '../../../../../../utils/validation/rules/Password'; 7 | import String from '../../../../../../utils/validation/rules/String'; 8 | 9 | export const schema = { 10 | password: Intersection([String(0, VARCHAR_LENGTH),Password()]), 11 | password_confirmation: Intersection([String(0, VARCHAR_LENGTH),Password()]), 12 | token: String(UUID_LENGTH, UUID_LENGTH), 13 | }; 14 | 15 | const rules = Intersection([ 16 | Object, 17 | Record(schema), 18 | Match('password', 'password_confirmation'), 19 | ]); 20 | 21 | export default rules; 22 | -------------------------------------------------------------------------------- /src/presenter/express/utils/auth/hasPermission/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import _isNil from 'ramda/src/isNil'; 3 | import User from '../../../../../types/items/User'; 4 | import UnverifiedAccountError from '../../../../../utils/errors/auth/UnverifiedAccountError'; 5 | import Config from '../../../presenterFactory/Config'; 6 | 7 | export interface Options { 8 | readonly req: Request; 9 | readonly user: User; 10 | readonly config: Config; 11 | readonly checkIfUserIsVerified?: boolean; 12 | } 13 | 14 | const hasPermission = async ({ 15 | req, 16 | user, 17 | config, 18 | checkIfUserIsVerified = true, 19 | }: Options) => { 20 | if (checkIfUserIsVerified && _isNil(user.verifiedAt)) { 21 | throw new UnverifiedAccountError(); 22 | } 23 | 24 | await config.service.hasPermission({ 25 | req, 26 | user, 27 | }); 28 | }; 29 | export default hasPermission; 30 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/createExtractTokenFromRequest/extractors/createQueryParamExtractor/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import url from 'url'; 3 | import authConfig from '../../../../../../config/subconfigs/auth'; 4 | import isString from '../../../../commons/isString'; 5 | // @credits: https://github.com/themikenicholson/passport-jwt 6 | 7 | export interface FromQueryParamExtractorOptions { 8 | authQueryParamName?: string; 9 | } 10 | 11 | const createQueryParamExtractor = ({ 12 | authQueryParamName = authConfig.jwt.authQueryParamName, 13 | }: FromQueryParamExtractorOptions) => (req: Request): string | null => { 14 | const parsedUrl = url.parse(req.url, true); 15 | 16 | if (isString(parsedUrl.query[authQueryParamName])) { 17 | return (parsedUrl.query as any)[authQueryParamName]; 18 | } 19 | 20 | return null; 21 | }; 22 | 23 | export default createQueryParamExtractor; 24 | -------------------------------------------------------------------------------- /src/utils/converters/users/documentToItem.ts: -------------------------------------------------------------------------------- 1 | import { Document } from '@js-items/knex/dist/FacadeConfig'; 2 | import baseMap from '../convertersMaps/baseMap'; 3 | import documentToItemDate from '../helpers/documentToItemDate'; 4 | import documentToItemDateTime from '../helpers/documentToItemDateTime'; 5 | import recursiveConverter from '../recursiveConverter'; 6 | 7 | const convertersMap = { 8 | ...baseMap, 9 | dateOfBirth: documentToItemDate, 10 | deletedAt: documentToItemDateTime, 11 | loginLastAttemptAt: documentToItemDateTime, 12 | loginLockoutExpiresAt: documentToItemDateTime, 13 | verifiedAt: documentToItemDateTime, 14 | verifyLastAttemptAt: documentToItemDateTime, 15 | verifyLockoutExpiresAt: documentToItemDateTime, 16 | }; 17 | 18 | const documentToItem = (document: Document): I => 19 | recursiveConverter({ obj: document, convertersMap }) as I; 20 | 21 | export default documentToItem; 22 | -------------------------------------------------------------------------------- /src/presenter/express/utils/tests/smtpServer/mail.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: normartin https://github.com/normartin 3 | * @source: https://github.com/normartin/ts-smtp-test 4 | * @copied from: https://github.com/normartin/ts-smtp-test/blob/master/src/mail.ts 5 | */ 6 | 7 | import { ParsedMail } from 'mailparser'; 8 | 9 | export class Mail { 10 | private readonly mail: ParsedMail; 11 | 12 | constructor(mail: ParsedMail) { 13 | this.mail = mail; 14 | } 15 | 16 | public get from(): string { 17 | return this.mail.from.text; 18 | } 19 | 20 | public get to(): string { 21 | return this.mail.to.text; 22 | } 23 | 24 | public get subject(): string { 25 | return this.mail.subject; 26 | } 27 | 28 | public get textContent(): string { 29 | return this.mail.text; 30 | } 31 | 32 | public get htmlContent(): string | boolean | undefined { 33 | return this.mail.html; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/types/items/User.ts: -------------------------------------------------------------------------------- 1 | import BaseItem, { NullableDate } from './BaseItem'; 2 | 3 | export enum GenderType { 4 | female = 'female', 5 | male = 'male', 6 | } 7 | 8 | export default interface User extends BaseItem { 9 | readonly email: string; 10 | readonly password: string; 11 | readonly firstName?: string; 12 | readonly lastName?: string; 13 | readonly bio?: string; 14 | readonly avatarUrl?: string; 15 | readonly dateOfBirth?: NullableDate; 16 | readonly gender?: GenderType; 17 | readonly verifiedAt?: NullableDate; 18 | readonly deletedAt?: NullableDate; 19 | readonly loginFailedAttempts?: number; 20 | readonly loginLastAttemptAt?: NullableDate; 21 | readonly loginLockoutExpiresAt?: NullableDate; 22 | readonly verifyToken?: string; 23 | readonly verifyAttempts?: number; 24 | readonly verifyLastAttemptAt?: NullableDate; 25 | readonly verifyLockoutExpiresAt?: NullableDate; 26 | } 27 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/auth/verifyAccount/index.ts: -------------------------------------------------------------------------------- 1 | import { OK } from 'http-status-codes'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import Config from '../../../../../presenterFactory/Config'; 5 | import catchErrors from '../../../../../utils/errors/catchErrors'; 6 | import rules from '../../../../../utils/schemas/auth/verifyAccount'; 7 | 8 | export default (config: Config) => 9 | catchErrors(config, async (req, res) => { 10 | const { email, token } = req.body; 11 | 12 | validateData(rules)({ email, token }); 13 | 14 | const { translator } = config; 15 | 16 | const translations = translator({ req }); 17 | 18 | await config.service.auth.verifyAccount({ 19 | email, 20 | token, 21 | }); 22 | 23 | const message = translations.accountVerifiedSuccessfully(); 24 | 25 | res.status(OK).json({ message }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createCategoriesTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_categories_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('categories', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table.string('title'); 13 | table.string('slug').unique(); 14 | table.dateTime('createdAt').notNullable(); 15 | table.dateTime('updatedAt').nullable(); 16 | }); 17 | 18 | await Promise.resolve(query); 19 | }; 20 | 21 | const down = async () => { 22 | const connection = await db(); 23 | 24 | await Promise.resolve(connection.schema.dropTable('categories')); 25 | }; 26 | 27 | return { key, up, down }; 28 | }; 29 | -------------------------------------------------------------------------------- /k8s/values-circleci.yaml: -------------------------------------------------------------------------------- 1 | secret: 2 | KNEX_PASSWORD: $DEPLOY_KNEX_PASSWORD 3 | KNEX_USER: $DEPLOY_KNEX_USER 4 | SMTP_USER: $DEPLOY_SMTP_USER 5 | SMTP_PASS: $DEPLOY_SMTP_PASS 6 | JWT_SECRET: $DEPLOY_JWT_SECRET 7 | LOGGLY_TOKEN: $LOGGLY_TOKEN 8 | LOGGLY_SUBDOMAIN: $LOGGLY_SUBDOMAIN 9 | 10 | configMap: 11 | KNEX_DATABASE: $DEPLOY_KNEX_DATABASE 12 | KNEX_CLIENT: $DEPLOY_KNEX_CLIENT 13 | KNEX_HOST: $DEPLOY_KNEX_HOST 14 | SMTP_HOST: $DEPLOY_SMTP_HOST 15 | SMTP_FROM: $DEPLOY_SMTP_FROM 16 | SMTP_PORT: $DEPLOY_SMTP_PORT 17 | SMTP_IGNORE_TLS: $DEPLOY_SMTP_IGNORE_TLS 18 | SMTP_SECURE: $DEPLOY_SMTP_SECURE 19 | SMTP_REQUIRE_TLS: $DEPLOY_SMTP_REQUIRE_TLS 20 | LOGGER_TYPE: $LOGGER_TYPE 21 | LOGGLY_JSON: $LOGGLY_JSON 22 | WINSTON_LOGGER_TYPE: $WINSTON_LOGGER_TYPE 23 | LOGGLY_TAGS: $LOGGLY_TAGS 24 | CLIENT_RESET_PASSWORD_URL: $CLIENT_RESET_PASSWORD_URL 25 | CLIENT_VERIFY_EMAIL_URL: $CLIENT_VERIFY_EMAIL_URL 26 | CLIENT_URL: $CLIENT_URL 27 | -------------------------------------------------------------------------------- /src/repo/mail/nodemailer/functions/sendEmail/index.ts: -------------------------------------------------------------------------------- 1 | import { Transporter } from 'nodemailer'; 2 | import isString from '../../../../../utils/helpers/commons/isString'; 3 | 4 | export interface BaseOptions { 5 | readonly from: string; 6 | readonly cc?: string | string[]; 7 | readonly subject: string; 8 | readonly html: string; 9 | readonly text?: string; 10 | } 11 | 12 | export interface Options extends BaseOptions { 13 | readonly to: string | string[]; 14 | } 15 | 16 | export interface Config { 17 | readonly mailer: Transporter; 18 | } 19 | 20 | export default ({ mailer }: Config) => async ({ 21 | cc, 22 | from, 23 | html, 24 | to, 25 | subject, 26 | text, 27 | }: Options) => { 28 | const ccData = isString(cc) ? { cc } : {}; 29 | const textData = isString(text) ? { text } : {}; 30 | 31 | return mailer.sendMail({ 32 | from, 33 | html, 34 | subject, 35 | to, 36 | ...textData, 37 | ...ccData, 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /src/presenter/commander/data/users.ts: -------------------------------------------------------------------------------- 1 | import { TEST_VALID_PASSWORD } from '../../express/utils/tests/testData'; 2 | 3 | export const adminOptions = { 4 | defaultEmail: 'admin@example.com', 5 | defaultPassword: TEST_VALID_PASSWORD, 6 | userType: 'Admin', 7 | }; 8 | 9 | export const firstInstructorOptions = { 10 | defaultEmail: 'first.instructor@example.com', 11 | defaultPassword: TEST_VALID_PASSWORD, 12 | userType: 'Instructor', 13 | }; 14 | 15 | export const secondInstructorOptions = { 16 | defaultEmail: 'second.instructor@example.com', 17 | defaultPassword: TEST_VALID_PASSWORD, 18 | userType: 'Instructor', 19 | }; 20 | 21 | export const firstStudentOptions = { 22 | defaultEmail: 'first.student@example.com', 23 | defaultPassword: TEST_VALID_PASSWORD, 24 | userType: 'Student', 25 | }; 26 | 27 | export const secondStudentOptions = { 28 | defaultEmail: 'second.student@example.com', 29 | defaultPassword: TEST_VALID_PASSWORD, 30 | userType: 'Student', 31 | }; 32 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createPermissionsTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_permissions_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('permissions', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table.string('name').unique(); 13 | table.string('method').notNullable(); 14 | table.string('url').notNullable(); 15 | table.dateTime('createdAt').notNullable(); 16 | table.dateTime('updatedAt').nullable(); 17 | }); 18 | 19 | await Promise.resolve(query); 20 | }; 21 | 22 | const down = async () => { 23 | const connection = await db(); 24 | 25 | await Promise.resolve(connection.schema.dropTable('permissions')); 26 | }; 27 | 28 | return { key, up, down }; 29 | }; 30 | -------------------------------------------------------------------------------- /assets/jscpd-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Copy/Paste 14 | Copy/Paste 15 | 4.02% 16 | 4.02% 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/presenter/express/api/commons/checks/checkVersion/index.ts: -------------------------------------------------------------------------------- 1 | import sendResponse from '@js-items/express/dist/utils/sendResponse'; 2 | import * as git from 'git-rev'; 3 | import { OK } from 'http-status-codes'; 4 | import Config from '../../../../presenterFactory/Config'; 5 | import catchErrors from '../../../../utils/errors/catchErrors'; 6 | 7 | export default (config: Config) => 8 | catchErrors(config, async (req, res) => { 9 | const [short, long, branch, tag] = await Promise.all([ 10 | new Promise(resolve => { 11 | git.short(resolve); 12 | }), 13 | new Promise(resolve => { 14 | git.long(resolve); 15 | }), 16 | new Promise(resolve => { 17 | git.branch(resolve); 18 | }), 19 | new Promise(resolve => { 20 | git.tag(resolve); 21 | }), 22 | ]); 23 | 24 | sendResponse({ 25 | body: { short, long, branch, tag }, 26 | req, 27 | res, 28 | status: OK, 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/service/functions/users/assignUserRole/index.ts: -------------------------------------------------------------------------------- 1 | import { ConflictingItemError } from '@js-items/foundation'; 2 | import _isNil from 'ramda/src/isNil'; 3 | import { v4 as uuid } from 'uuid'; 4 | import ConflictError from '../../../../utils/errors/http/ConflictError'; 5 | import getUtcDate from '../../../../utils/helpers/date/getUtcDate'; 6 | import Config from '../../../FactoryConfig'; 7 | 8 | export interface Options { 9 | readonly userId: string; 10 | readonly roleId: string; 11 | } 12 | 13 | export default ({ repo }: Config) => async ({ userId, roleId }: Options) => { 14 | try { 15 | const id = uuid(); 16 | 17 | await repo.userRole.createItem({ 18 | id, 19 | item: { 20 | createdAt: getUtcDate(), 21 | id, 22 | roleId, 23 | userId, 24 | }, 25 | }); 26 | } catch (error) { 27 | if (error instanceof ConflictingItemError) { 28 | throw new ConflictError(error.itemName, error.itemId); 29 | } 30 | 31 | throw error; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/verifyToken/index.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | import ExpiredJwtTokenError from '../../../errors/auth/ExpiredJwtTokenError'; 3 | import InvalidJwtTokenError from '../../../errors/auth/InvalidJwtTokenError'; 4 | import UndefinedJWTSecretError from '../../../errors/auth/UndefinedJWTSecretError'; 5 | 6 | interface Options { 7 | readonly token: string; 8 | readonly secret?: string | Buffer; 9 | } 10 | 11 | export default ({ token, secret }: Options) => { 12 | try { 13 | if (secret === undefined) { 14 | throw new UndefinedJWTSecretError(); 15 | } 16 | 17 | return jwt.verify(token, secret); 18 | } catch (err) { 19 | if (err instanceof jwt.JsonWebTokenError) { 20 | throw new InvalidJwtTokenError(); 21 | } else if (err instanceof jwt.NotBeforeError) { 22 | throw new InvalidJwtTokenError(); 23 | } else if (err instanceof jwt.TokenExpiredError) { 24 | throw new ExpiredJwtTokenError(); 25 | } 26 | throw err; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/config/subconfigs/translator/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_LOCALE, 3 | LOCALE_HEADER, 4 | LOCALE_QUERY_PARAM, 5 | } from '../../../constants'; 6 | import getStringValue from '../../../utils/helpers/config/getStringValue'; 7 | 8 | // tslint:disable-next-line:no-empty-interface 9 | export interface DefaultTranslator { 10 | readonly defaultLocale: string; 11 | readonly queryParam: string; 12 | readonly headerName: string; 13 | } 14 | 15 | export interface TranslatorConfig { 16 | readonly type: string; 17 | readonly default: DefaultTranslator; 18 | } 19 | 20 | const config: TranslatorConfig = { 21 | default: { 22 | defaultLocale: getStringValue(process.env.DEFAULT_LOCALE, DEFAULT_LOCALE), 23 | headerName: getStringValue(process.env.LOCALE_HEADER, LOCALE_HEADER), 24 | queryParam: getStringValue( 25 | process.env.LOCALE_QUERY_PARAM, 26 | LOCALE_QUERY_PARAM 27 | ), 28 | }, 29 | type: getStringValue(process.env.TRANSLATOR_TYPE, 'default'), 30 | }; 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /src/presenter/commander/utils/convertToHtml/index.spec.ts: -------------------------------------------------------------------------------- 1 | import convertToHtml from './index'; 2 | 3 | describe('convertToHtml', () => { 4 | it('returns empty string when no options passed', () => { 5 | const result = convertToHtml({ text: '' }); 6 | 7 | expect(result).toBe(''); 8 | }); 9 | 10 | it('returns html elements for the list', () => { 11 | const result = convertToHtml({ 12 | elementTag: 'li', 13 | parentElement: 'ul', 14 | text: 'This is first element. This is second element.', 15 | }); 16 | 17 | expect(result).toBe('
  • This is first element
  • This is second element
'); 18 | }); 19 | 20 | it('returns html elements for list of paragraphs', () => { 21 | const result = convertToHtml({ 22 | parentElement: 'div', 23 | text: 'This is first paragraph. This is second paragraph.', 24 | }); 25 | 26 | expect(result).toBe('

This is first paragraph

This is second paragraph

'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/service/functions/roles/assignRolePermission/index.ts: -------------------------------------------------------------------------------- 1 | import { ConflictingItemError } from '@js-items/foundation'; 2 | import _isNil from 'ramda/src/isNil'; 3 | import { v4 as uuid } from 'uuid'; 4 | import ConflictError from '../../../../utils/errors/http/ConflictError'; 5 | import getUtcDate from '../../../../utils/helpers/date/getUtcDate'; 6 | import Config from '../../../FactoryConfig'; 7 | 8 | export interface Options { 9 | readonly permissionId: string; 10 | readonly roleId: string; 11 | } 12 | 13 | export default ({ repo }: Config) => async ({ permissionId, roleId }: Options) => { 14 | try { 15 | const id = uuid(); 16 | 17 | await repo.rolePermission.createItem({ 18 | id, 19 | item: { 20 | createdAt: getUtcDate(), 21 | id, 22 | permissionId, 23 | roleId, 24 | }, 25 | }); 26 | } catch (error) { 27 | if (error instanceof ConflictingItemError) { 28 | throw new ConflictError(error.itemName, error.itemId); 29 | } 30 | 31 | throw error; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/presenter/express/utils/schemas/auth/register/index.ts: -------------------------------------------------------------------------------- 1 | import Intersection from 'rulr/Intersection'; 2 | import Object from 'rulr/Object'; 3 | import Record from 'rulr/Record'; 4 | import { VARCHAR_LENGTH } from '../../../../../../constants'; 5 | import Email from '../../../../../../utils/validation/rules/Email'; 6 | import Match from '../../../../../../utils/validation/rules/Match'; 7 | import Password from '../../../../../../utils/validation/rules/Password'; 8 | import String from '../../../../../../utils/validation/rules/String'; 9 | import { schema as baseSchema } from '../../users/createItem'; 10 | 11 | export const schema = { 12 | ...baseSchema, 13 | email: Intersection([String(0, VARCHAR_LENGTH), Email()]), 14 | password: Intersection([String(0, VARCHAR_LENGTH), Password()]), 15 | password_confirmation: Intersection([String(0, VARCHAR_LENGTH), Password()]), 16 | }; 17 | 18 | const rules = Intersection([ 19 | Object, 20 | Record(schema), 21 | Match('password', 'password_confirmation'), 22 | ]); 23 | 24 | export default rules; 25 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeCreateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getAuthenticatedUser from '../../../../../utils/auth/getAuthenticatedUser'; 5 | import hasPermission from '../../../../../utils/auth/hasPermission'; 6 | import transactionWrapper, { 7 | HookOptions, 8 | } from '../../../../../utils/handlers/transactionWrapper'; 9 | import { BaseFactoryConfig } from '../baseFactory'; 10 | 11 | const defaultTransactionHandler = (config: BaseFactoryConfig) => 12 | transactionWrapper({ 13 | beforeHandler: async ({ req }: HookOptions) => { 14 | const user = await getAuthenticatedUser({ req, config }); 15 | 16 | await hasPermission({ req, user, config }); 17 | 18 | const payload: any = _pick(Object.keys(config.beforeCreateSchema), req.body); 19 | 20 | validateData(config.beforeCreateRules)(payload); 21 | }, 22 | config, 23 | }); 24 | 25 | export default defaultTransactionHandler; 26 | -------------------------------------------------------------------------------- /src/presenter/express/utils/errors/handleUnexpectedEvents/index.ts: -------------------------------------------------------------------------------- 1 | /* @credits: https://github.com/banzaicloud/node-service-tools */ 2 | import { StoppableServer } from 'stoppable'; 3 | import loggerFactory from '../../../../../logger/factory'; 4 | import serviceFactory from '../../../../../service/factory'; 5 | import gracefulShutDown from './gracefulShutDown'; 6 | 7 | export interface Config { 8 | readonly service: ReturnType; 9 | readonly logger: ReturnType; 10 | readonly server: StoppableServer; 11 | } 12 | 13 | const handleUnexpectedEvents = (config: Config) => { 14 | process.once('SIGTERM', gracefulShutDown({ config, reason: 'SIGTERM' })); 15 | process.once('SIGINT', gracefulShutDown({ config, reason: 'SIGINT' })); 16 | process.on( 17 | 'uncaughtException', 18 | gracefulShutDown({ config, reason: 'uncaughtException' }) 19 | ); 20 | process.on( 21 | 'unhandledRejection', 22 | gracefulShutDown({ config, reason: 'unhandledRejection' }) 23 | ); 24 | }; 25 | 26 | export default handleUnexpectedEvents; 27 | -------------------------------------------------------------------------------- /src/presenter/express/app/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import loggerFactory from '../../../logger/factory'; 3 | import repoFactory from '../../../repo/factory'; 4 | import serviceFactory from '../../../service/factory'; 5 | import translatorFactory from '../../../translator/factory'; 6 | import presenterFactory from '../presenterFactory'; 7 | import AppConfig from './AppConfig'; 8 | 9 | export interface App { 10 | readonly presenter: Router; 11 | readonly service: ReturnType; 12 | readonly logger: ReturnType; 13 | } 14 | 15 | export default (appConfig: AppConfig): App => { 16 | const repo = repoFactory(appConfig.repo); 17 | 18 | const logger = loggerFactory(appConfig.logger); 19 | 20 | const translator = translatorFactory(appConfig.translator); 21 | 22 | const service = serviceFactory({ logger, repo, appConfig }); 23 | 24 | const presenter = presenterFactory({ 25 | appConfig, 26 | logger, 27 | service, 28 | translator, 29 | }); 30 | 31 | return { presenter, service, logger }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/presenter/express/utils/translations/locales/getLocale/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { DefaultTranslator } from '../../../../../../config/subconfigs/translator'; 3 | import isString from '../../../../../../utils/helpers/commons/isString'; 4 | import getLocaleFromHeader from '../getLocaleFromHeader'; 5 | import getLocaleFromQueryParam from '../getLocaleFromQueryParam'; 6 | 7 | export interface Options extends DefaultTranslator { 8 | readonly req?: Request; 9 | } 10 | 11 | const getLocale = ({ defaultLocale, headerName, queryParam, req }: Options) => { 12 | if (req === undefined) { 13 | return defaultLocale; 14 | } 15 | 16 | const queryParameter = getLocaleFromQueryParam({ req, queryParam }); 17 | 18 | // queryParam takes precedence over header 19 | if (isString(queryParameter)) { 20 | return queryParameter; 21 | } 22 | 23 | const header = getLocaleFromHeader({ req, headerName }); 24 | 25 | if (isString(header)) { 26 | return header; 27 | } 28 | 29 | return defaultLocale; 30 | }; 31 | 32 | export default getLocale; 33 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/auth/factory.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { 3 | LOGIN, 4 | REGISTER, 5 | REMIND_PASSWORD, 6 | RESEND_VERIFY_TOKEN, 7 | RESET_PASSWORD, 8 | VERIFY_ACCOUNT, 9 | } from '../../../../../../constants/routes'; 10 | import Config from '../../../../presenterFactory/Config'; 11 | import login from './login'; 12 | import register from './register'; 13 | import remindPassword from './remindPassword'; 14 | import resendVerifyToken from './resendVerifyToken'; 15 | import resetPassword from './resetPassword'; 16 | import verifyAccount from './verifyAccount'; 17 | 18 | const authFactory = (config: Config): Router => { 19 | const router = Router(); 20 | 21 | router.post(LOGIN, login(config)); 22 | router.post(REGISTER, register(config)); 23 | router.post(REMIND_PASSWORD, remindPassword(config)); 24 | router.post(RESET_PASSWORD, resetPassword(config)); 25 | router.post(VERIFY_ACCOUNT, verifyAccount(config)); 26 | router.post(RESEND_VERIFY_TOKEN, resendVerifyToken(config)); 27 | 28 | return router; 29 | }; 30 | 31 | export default authFactory; 32 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createResetPasswordTokensTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_reset_password_tokens_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | const query = connection.schema.createTable( 10 | 'reset_password_tokens', 11 | table => { 12 | table.string('id', UUID_LENGTH).primary(); 13 | table 14 | .string('userId', UUID_LENGTH) 15 | .references('id') 16 | .inTable('users') 17 | .onDelete('cascade'); 18 | table.dateTime('expiresAt'); 19 | table.dateTime('createdAt').notNullable(); 20 | table.dateTime('updatedAt').nullable(); 21 | } 22 | ); 23 | await Promise.resolve(query); 24 | }; 25 | 26 | const down = async () => { 27 | const connection = await db(); 28 | await Promise.resolve(connection.schema.dropTable('reset_password_tokens')); 29 | }; 30 | 31 | return { key, up, down }; 32 | }; 33 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | DEBUG=true 3 | 4 | # AUTH 5 | JWT_SECRET=secret 6 | 7 | # HTTP 8 | LIVENESS_CHECK_URL=/health/liveness 9 | READINESS_CHECK_URL=/health/readiness 10 | VERSION_CHECK_URL=/version 11 | 12 | EXPRESS_PORT=3000 13 | EXPRESS_HOST=http://localhost 14 | 15 | CLIENT_URL=https://client.mariuszrajczakowski.me 16 | CLIENT_RESET_PASSWORD_URL=https://client.mariuszrajczakowski.me/reset-password 17 | CLIENT_VERIFY_EMAIL_URL=https://client.mariuszrajczakowski.me/verify 18 | 19 | # REPO 20 | MODELS_REPO_TYPE=knex 21 | 22 | KNEX_DATABASE=kube-ts-server 23 | KNEX_PASSWORD=password 24 | KNEX_USER=root 25 | KNEX_CLIENT=mysql 26 | KNEX_HOST=127.0.0.1 27 | 28 | # LOGGER 29 | LOGGER_TYPE=winston 30 | 31 | # TRANSLATOR 32 | TRANSLATOR_TYPE=default 33 | 34 | # MAIL 35 | # FOR TESTING SMTP_HOST=localhost 36 | # SMTP_HOST=smtp.mailgun.org 37 | # SMTP_SERVICE=mailgun 38 | SMTP_HOST=localhost 39 | SMTP_FROM=noreply@example.com 40 | SMTP_PORT=2025 41 | SMTP_USER=test@example.com 42 | SMTP_PASS=password 43 | SMTP_IGNORE_TLS=true 44 | SMTP_SECURE=false 45 | SMTP_REQUIRE_TLS=true 46 | SMTP_API_KEY= 47 | SMTP_DOMAIN= 48 | -------------------------------------------------------------------------------- /src/presenter/express/middlewares/rateLimiter/factory.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import RateLimit from 'express-rate-limit'; 3 | import _F from 'ramda/src/F'; 4 | import { ExpressConfig } from '../../../../config/subconfigs/http'; 5 | 6 | // see https://github.com/nfriedly/express-rate-limit/issues/69 7 | export const createSkipFunction = (skipMethods?: string) => { 8 | if (skipMethods === undefined) { 9 | return _F; 10 | } 11 | 12 | const skippedMethods = skipMethods 13 | .split(',') 14 | .map(method => method.toLocaleLowerCase()); 15 | 16 | return (req: Request, _res: Response) => 17 | skippedMethods.includes(req.method.toLocaleLowerCase()); 18 | }; 19 | 20 | const createRateLimiter = (config: ExpressConfig) => { 21 | const { 22 | middlewares: { rateLimiter }, 23 | } = config; 24 | const skip = createSkipFunction(rateLimiter.skipMethods); 25 | 26 | return new RateLimit({ 27 | max: rateLimiter.maxNumberOfRequest, 28 | message: rateLimiter.message, 29 | skip, 30 | windowMs: rateLimiter.windowMs, 31 | }); 32 | }; 33 | 34 | export default createRateLimiter; 35 | -------------------------------------------------------------------------------- /src/presenter/express/utils/tests/assertOnResponseAndStatus/index.ts: -------------------------------------------------------------------------------- 1 | import { UNPROCESSABLE_ENTITY } from 'http-status-codes'; 2 | import supertest from 'supertest'; 3 | 4 | export interface Request extends supertest.SuperTest { 5 | readonly [key: string]: any; 6 | } 7 | 8 | export interface BaseAssertionOptions { 9 | readonly request: Request; 10 | readonly fields?: { [key: string]: any }; 11 | readonly statusCode?: number; 12 | } 13 | 14 | export interface AssertionOptions extends BaseAssertionOptions { 15 | readonly url: string; 16 | readonly method?: string; 17 | } 18 | 19 | const assertOnResponseAndStatus = async ({ 20 | request, 21 | fields, 22 | method = 'post', 23 | statusCode = UNPROCESSABLE_ENTITY, 24 | url, 25 | }: AssertionOptions) => { 26 | // tslint:disable-next-line:no-string-literal 27 | const { status, body } = await request[method](url) 28 | .set('Content-Type', 'application/json') 29 | .send(fields); 30 | 31 | expect(status).toBe(statusCode); 32 | expect(body).toMatchSnapshot(); 33 | 34 | return { status, body }; 35 | }; 36 | 37 | export default assertOnResponseAndStatus; 38 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createSectionsTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_sections_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('sections', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table 13 | .string('courseId', UUID_LENGTH) 14 | .references('id') 15 | .inTable('courses') 16 | .onDelete('cascade'); 17 | table.string('title'); 18 | table.integer('order').defaultTo(0); 19 | table.boolean('isPublished').defaultTo(true); 20 | table.dateTime('createdAt').notNullable(); 21 | table.dateTime('updatedAt').nullable(); 22 | }); 23 | 24 | await Promise.resolve(query); 25 | }; 26 | 27 | const down = async () => { 28 | const connection = await db(); 29 | 30 | await Promise.resolve(connection.schema.dropTable('sections')); 31 | }; 32 | 33 | return { key, up, down }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/users/functions/beforeCreateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import Config from '../../../../../../presenterFactory/Config'; 5 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 6 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 7 | import transactionWrapper, { 8 | HookOptions, 9 | } from '../../../../../../utils/handlers/transactionWrapper'; 10 | import rules, { 11 | schema, 12 | } from '../../../../../../utils/schemas/users/createItem'; 13 | 14 | const defaultTransactionHandler = (config: Config) => 15 | transactionWrapper({ 16 | beforeHandler: async ({ req }: HookOptions) => { 17 | const user = await getAuthenticatedUser({ req, config }); 18 | 19 | await hasPermission({ req, user, config }); 20 | 21 | const payload: any = _pick(Object.keys(schema), req.body); 22 | 23 | validateData(rules)(payload); 24 | }, 25 | config, 26 | }); 27 | 28 | export default defaultTransactionHandler; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kube-ts-server 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createUserRoleTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_user_role_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('user_role', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table 13 | .string('userId', UUID_LENGTH) 14 | .references('id') 15 | .inTable('users') 16 | .onDelete('cascade'); 17 | table 18 | .string('roleId', UUID_LENGTH) 19 | .references('id') 20 | .inTable('roles') 21 | .onDelete('cascade'); 22 | table.dateTime('createdAt').notNullable(); 23 | table.dateTime('updatedAt').nullable(); 24 | }); 25 | 26 | await Promise.resolve(query); 27 | }; 28 | 29 | const down = async () => { 30 | const connection = await db(); 31 | 32 | await Promise.resolve(connection.schema.dropTable('user_role')); 33 | }; 34 | 35 | return { key, up, down }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/repo/model/knex/other/countPermissions/index.ts: -------------------------------------------------------------------------------- 1 | import { RepoConfig } from '../../factory'; 2 | 3 | export interface Options { 4 | readonly url: string; 5 | readonly permissionsIds: string[]; 6 | readonly method: string; 7 | } 8 | 9 | export default ({ db }: RepoConfig) => async ({ 10 | url, 11 | method, 12 | permissionsIds, 13 | }: Options) => { 14 | const connection = await db(); 15 | 16 | // @credits: 17 | // https://medium.com/technology-learning/how-we-solved-authentication-and-authorization-in-our-microservice-architecture-994539d1b6e6 18 | // https://stackoverflow.com/questions/47552940/how-can-i-store-a-regex-pattern-in-a-mysql-field-and-check-an-input-against-it 19 | // query string regex: 20 | // [?([a-z0-9$_.+!*'(),;:@&=-]|%[0-9a-f]{2})*]? 21 | const countQuery = connection 22 | .count('*') 23 | .from('permissions') 24 | .where({ method }) 25 | .whereIn('id', permissionsIds) 26 | .whereRaw('? REGEXP url = 1', [url]); 27 | 28 | const [result] = await Promise.resolve(countQuery); 29 | 30 | const count = result !== undefined ? result['count(*)'] : 0; 31 | 32 | return { count }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/getDiscoveryItems/index.ts: -------------------------------------------------------------------------------- 1 | import sendResponse from '@js-items/express/dist/utils/sendResponse'; 2 | import { toSnake } from 'convert-keys'; 3 | import { OK } from 'http-status-codes'; 4 | import Config from '../../../../presenterFactory/Config'; 5 | import catchErrors from '../../../../utils/errors/catchErrors'; 6 | 7 | const getDiscoveryItems = (config: Config) => 8 | catchErrors(config, async (req, res) => { 9 | // TODO: provide validation for query params 10 | const type = req.query.type; 11 | 12 | let response; 13 | 14 | switch (type) { 15 | case 'course': 16 | response = await config.service.getCourseDetails({ 17 | // TODO: validate filter 18 | filter: req.query.filter ? JSON.parse(req.query.filter) : {}, 19 | }); 20 | break; 21 | case 'homepage': 22 | default: 23 | response = await config.service.getDiscoveryItemsForHomepage({}); 24 | } 25 | 26 | sendResponse({ 27 | body: toSnake(response), 28 | req, 29 | res, 30 | status: OK, 31 | }); 32 | }); 33 | 34 | export default getDiscoveryItems; 35 | -------------------------------------------------------------------------------- /src/constants/routes.ts: -------------------------------------------------------------------------------- 1 | export const API_V1 = '/api/v1'; 2 | 3 | export const ROOT = '/'; 4 | export const AUTH = '/auth'; 5 | export const LOGIN = '/login'; 6 | export const REGISTER = '/register'; 7 | export const RESET_PASSWORD = '/reset-password'; 8 | export const REMIND_PASSWORD = '/remind-password'; 9 | export const VERIFY_ACCOUNT = '/verify-account'; 10 | export const RESEND_VERIFY_TOKEN = '/resend-verify-token'; 11 | 12 | export const USERS = '/users'; 13 | export const ASSIGN_USER_ROLE = '/:user_id/roles'; 14 | export const REVOKE_USER_ROLE = '/:user_id/roles/:role_id'; 15 | 16 | export const ROLES = '/roles'; 17 | export const ASSIGN_ROLE_PERMISSION = '/:role_id/permissions'; 18 | export const REVOKE_ROLE_PERMISSION = '/:role_id/permissions/:permission_id'; 19 | 20 | export const PERMISSIONS = '/permissions'; 21 | 22 | export const COURSES = '/courses'; 23 | export const CATEGORIES = '/categories'; 24 | export const ENROLMENTS = '/enrolments'; 25 | export const SECTIONS = '/sections'; 26 | export const UNITS = '/units'; 27 | export const COMMENTS = '/comments'; 28 | export const DISCOVERY_ITEMS = '/discovery-items'; 29 | export const AUTOCOMPLETE = '/autocomplete'; 30 | -------------------------------------------------------------------------------- /src/presenter/commander/functions/dbSeed/functions/createRoles/index.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | import { v4 as uuid } from 'uuid'; 3 | import getUtcDate from '../../../../../../utils/helpers/date/getUtcDate'; 4 | import FactoryConfig from '../../../../presenterFactory/FactoryConfig'; 5 | 6 | const createRoles = (config: FactoryConfig) => async (roles: string[] = []) => { 7 | console.log(`--------------------------------------------------------`); 8 | console.log(`Creating roles [${roles.join(', ')}] created successfuly!`); 9 | 10 | const rolesPromises = roles.map(async name => { 11 | const roleId = uuid(); 12 | 13 | return config.service.roles.createItem({ 14 | id: roleId, 15 | item: { 16 | createdAt: getUtcDate(), 17 | id: roleId, 18 | name, 19 | }, 20 | }); 21 | }); 22 | 23 | const items = await Promise.all(rolesPromises); 24 | 25 | const rolesIds = items.map(({ item }) => item.id); 26 | 27 | console.log(`Roles created successfuly!`); 28 | console.log(`--------------------------------------------------------`); 29 | 30 | return Promise.resolve(rolesIds); 31 | }; 32 | 33 | export default createRoles; 34 | -------------------------------------------------------------------------------- /src/presenter/express/utils/fakeFactories/users/factory.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-magic-numbers 2 | import faker from 'faker'; 3 | import User from '../../../../../types/items/User'; 4 | import hashPassword from '../../../../../utils/helpers/auth/hashPassword'; 5 | import { TEST_UUID } from '../../tests/testData'; 6 | import baseFactory, { Options } from '../index'; 7 | 8 | const constantDate = new Date('2019-03-27T21:32:31.000Z'); 9 | 10 | const createUserItemData = async () => ({ 11 | bio: faker.lorem.sentences(10), 12 | dateOfBirth: faker.date.past(50), 13 | email: faker.internet.exampleEmail(), 14 | firstName: faker.name.firstName(), 15 | gender: Math.random() > 0.5 ? 'male' : 'female', 16 | lastName: faker.name.lastName(), 17 | loginLastAttemptAt: constantDate, 18 | loginLockoutExpiresAt: null, 19 | password: await hashPassword(faker.internet.password()), 20 | verifiedAt: constantDate, 21 | verifyToken: TEST_UUID, 22 | }); 23 | 24 | const usersFactory = async (options: Options) => { 25 | const itemData = await createUserItemData(); 26 | 27 | return baseFactory(itemData as Partial)(options); 28 | }; 29 | 30 | export default usersFactory; 31 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createRolePermissionTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_role_permission_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('role_permission', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table 13 | .string('roleId', UUID_LENGTH) 14 | .references('id') 15 | .inTable('roles') 16 | .onDelete('cascade'); 17 | table 18 | .string('permissionId', UUID_LENGTH) 19 | .references('id') 20 | .inTable('permissions') 21 | .onDelete('cascade'); 22 | table.dateTime('createdAt').notNullable(); 23 | table.dateTime('updatedAt').nullable(); 24 | }); 25 | 26 | await Promise.resolve(query); 27 | }; 28 | 29 | const down = async () => { 30 | const connection = await db(); 31 | 32 | await Promise.resolve(connection.schema.dropTable('role_permission')); 33 | }; 34 | 35 | return { key, up, down }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/constants/permissions/index.ts: -------------------------------------------------------------------------------- 1 | import { CATEGORIES_GET_ITEM, CATEGORIES_GET_ITEMS, catergoriesPermissions } from "./categories"; 2 | import { COURSES_GET_ITEM, COURSES_GET_ITEMS, coursesPermissions } from "./courses"; 3 | import { ENROLMENTS_GET_ITEM, ENROLMENTS_GET_ITEMS, enrolmentsPermissions } from "./enrolments"; 4 | import { permissionsPermissions } from "./permissions"; 5 | import { rolesPermissions } from "./roles"; 6 | import { USERS_GET_ITEM, USERS_GET_ITEMS, usersPermissions } from "./users"; 7 | 8 | export const basicUsersPermissions = [ 9 | ENROLMENTS_GET_ITEM, 10 | ENROLMENTS_GET_ITEMS, 11 | USERS_GET_ITEM, 12 | USERS_GET_ITEMS, 13 | COURSES_GET_ITEM, 14 | COURSES_GET_ITEMS, 15 | CATEGORIES_GET_ITEM, 16 | CATEGORIES_GET_ITEMS, 17 | ]; 18 | 19 | export const STUDENT_PERMISSIONS = [...basicUsersPermissions]; 20 | 21 | export const INSTRUCTOR_PERMISSIONS = [...basicUsersPermissions]; 22 | 23 | export const ADMIN_PERMISSIONS = [ 24 | ...usersPermissions, 25 | ...enrolmentsPermissions, 26 | ...rolesPermissions, 27 | ...permissionsPermissions, 28 | ...coursesPermissions, 29 | ...catergoriesPermissions, 30 | // tslint:disable-next-line:max-file-line-count 31 | ]; 32 | -------------------------------------------------------------------------------- /src/presenter/commander/presenterFactory/factory.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | import * as sourceMapSupport from 'source-map-support'; 3 | sourceMapSupport.install(); 4 | import commanderMigrationsPresenterFactory from '@js-migrations/commander/dist/factory'; 5 | import defaultLog from '@js-migrations/commander/dist/utils/defaultLog'; 6 | import handleMigrationError from '@js-migrations/commander/dist/utils/handleError'; 7 | import dbSeed from '../functions/dbSeed'; 8 | import FactoryConfig from './FactoryConfig'; 9 | 10 | export default (factoryConfig: FactoryConfig) => { 11 | const { program, service, logger } = factoryConfig; 12 | 13 | commanderMigrationsPresenterFactory({ 14 | handleError: err => { 15 | handleMigrationError(err, (message: string, ...args: any[]) => { 16 | logger.error(message, args); 17 | }); 18 | }, 19 | log: status => { 20 | defaultLog(status, message => { 21 | logger.info(status, message); 22 | }); 23 | }, 24 | program, 25 | service: service.migrations, 26 | }); 27 | 28 | program 29 | .command('seed') 30 | .description('Seeds database with initial data') 31 | .action(dbSeed(factoryConfig)); 32 | }; 33 | -------------------------------------------------------------------------------- /k8s/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "kube-ts-server.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "kube-ts-server.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "kube-ts-server.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories/photography.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const photographyCategory = { 4 | courses: [ 5 | { 6 | slug: 'introduction-to-photography', 7 | title: 'Intruduction to photography', 8 | }, 9 | { 10 | slug: 'wedding-photography', 11 | title: 'Wedding photography', 12 | }, 13 | { 14 | slug: 'food-photography', 15 | title: 'Food photography', 16 | }, 17 | { 18 | slug: 'black-and-white-photography', 19 | title: 'Black and white photography', 20 | }, 21 | { 22 | slug: 'how-to-hire-great-employees', 23 | title: 'How to hire great employees', 24 | }, 25 | { 26 | slug: 'learn-imovie', 27 | title: 'Learn imovie', 28 | }, 29 | { 30 | slug: 'action-photography', 31 | title: 'Action photography', 32 | }, 33 | { 34 | slug: 'mastering-photoshop', 35 | title: 'Mastering photoshop', 36 | }, 37 | { 38 | slug: 'adobe-lightroom-cc', 39 | title: 'Adobe lightroom CC+', 40 | }, 41 | ], 42 | id: uuid(), 43 | slug: 'photography', 44 | title: 'Photography', 45 | }; 46 | 47 | export default photographyCategory; 48 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/enrolments/functions/beforeCreateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 5 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 6 | import transactionWrapper, { 7 | HookOptions, 8 | } from '../../../../../../utils/handlers/transactionWrapper'; 9 | import rules, { 10 | beforeCreateSchema, 11 | } from '../../../../../../utils/schemas/enrolments/createItem'; 12 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 13 | 14 | const defaultTransactionHandler = (config: BaseFactoryConfig) => 15 | transactionWrapper({ 16 | beforeHandler: async ({ req }: HookOptions) => { 17 | const user = await getAuthenticatedUser({ req, config }); 18 | 19 | await hasPermission({ req, user, config }); 20 | 21 | const payload: any = _pick(Object.keys(beforeCreateSchema), req.body); 22 | 23 | validateData(rules)(payload); 24 | 25 | req.body.user_id = user.id; 26 | }, 27 | config, 28 | }); 29 | 30 | export default defaultTransactionHandler; 31 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/users/functions/revokeUserRole/index.ts: -------------------------------------------------------------------------------- 1 | import { NO_CONTENT } from 'http-status-codes'; 2 | import _defaultTo from 'ramda/src/defaultTo'; 3 | import _isNil from 'ramda/src/isNil'; 4 | import _pick from 'ramda/src/pick'; 5 | import validateData from 'rulr/validateData'; 6 | import Config from '../../../../../../presenterFactory/Config'; 7 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 8 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 9 | import catchErrors from '../../../../../../utils/errors/catchErrors'; 10 | import rules from '../../../../../../utils/schemas/users/assignUserRole'; 11 | 12 | const revokeUserRole = (config: Config) => 13 | catchErrors(config, async (req, res) => { 14 | const user = await getAuthenticatedUser({ req, config }); 15 | 16 | await hasPermission({ req, user, config }); 17 | 18 | const { user_id, role_id } = req.params; 19 | 20 | validateData(rules)({ user_id, role_id }); 21 | 22 | await config.service.revokeUserRole({ 23 | roleId: role_id, 24 | userId: user_id, 25 | }); 26 | 27 | res.status(NO_CONTENT); 28 | }); 29 | 30 | export default revokeUserRole; 31 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeUpdateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getUtcDate from '../../../../../../../utils/helpers/date/getUtcDate'; 5 | import getAuthenticatedUser from '../../../../../utils/auth/getAuthenticatedUser'; 6 | import hasPermission from '../../../../../utils/auth/hasPermission'; 7 | import transactionWrapper, { 8 | HookOptions, 9 | } from '../../../../../utils/handlers/transactionWrapper'; 10 | import { BaseFactoryConfig } from '../baseFactory'; 11 | 12 | const beforeUpdateItem = (config: BaseFactoryConfig) => 13 | transactionWrapper({ 14 | beforeHandler: async ({ req }: HookOptions) => { 15 | const user = await getAuthenticatedUser({ req, config }); 16 | 17 | if (req.params.id !== user.id) { 18 | await hasPermission({ req, user, config }); 19 | } 20 | 21 | const payload: any = _pick(Object.keys(config.beforeUpdateSchema), req.body); 22 | 23 | validateData(config.beforeUpdateRules)(payload); 24 | 25 | req.body.updatedAt = getUtcDate(); 26 | }, 27 | config, 28 | }); 29 | 30 | export default beforeUpdateItem; 31 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/beforeReplaceItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getUtcDate from '../../../../../../../utils/helpers/date/getUtcDate'; 5 | import getAuthenticatedUser from '../../../../../utils/auth/getAuthenticatedUser'; 6 | import hasPermission from '../../../../../utils/auth/hasPermission'; 7 | import transactionWrapper, { 8 | HookOptions, 9 | } from '../../../../../utils/handlers/transactionWrapper'; 10 | import { BaseFactoryConfig } from '../baseFactory'; 11 | 12 | const beforeReplaceItem = (config: BaseFactoryConfig) => 13 | transactionWrapper({ 14 | beforeHandler: async ({ req }: HookOptions) => { 15 | const user = await getAuthenticatedUser({ req, config }); 16 | 17 | if (req.params.id !== user.id) { 18 | await hasPermission({ req, user, config }); 19 | } 20 | 21 | const payload: any = _pick(Object.keys(config.beforeReplaceSchema), req.body); 22 | 23 | validateData(config.beforeReplaceRules)(payload); 24 | 25 | req.body.updatedAt = getUtcDate(); 26 | }, 27 | config, 28 | }); 29 | 30 | export default beforeReplaceItem; 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | # next.js build output 60 | .next 61 | 62 | dist 63 | 64 | /k8s/override.yaml 65 | -------------------------------------------------------------------------------- /src/presenter/express/utils/translations/locales/getAcceptedLanguagesFromHeader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Created by Marcus Spiegel on 2011-03-25. 3 | * @link https://github.com/mashpie/i18n-node 4 | * @license http://opensource.org/licenses/MIT 5 | * Get a sorted list of accepted languages from the HTTP Accept-Language header 6 | */ 7 | // tslint:disable:no-magic-numbers 8 | 9 | const getAcceptedLanguagesFromHeader = (header: string) => { 10 | const languages = header.split(','); 11 | const preferences: { [key: string]: any } = {}; 12 | 13 | return languages 14 | .map(item => { 15 | const preferenceParts: any = item.trim().split(';q='); 16 | 17 | if (preferenceParts.length < 2) { 18 | preferenceParts[1] = 1.0; 19 | } else { 20 | const quality = parseFloat(preferenceParts[1]); 21 | preferenceParts[1] = !isNaN(quality) ? quality : 0.0; 22 | } 23 | 24 | preferences[preferenceParts[0]] = preferenceParts[1]; 25 | 26 | return preferenceParts[0]; 27 | }) 28 | .filter(lang => preferences[lang] > 0) 29 | .sort((a, b) => preferences[b] - preferences[a]); 30 | }; 31 | 32 | export default getAcceptedLanguagesFromHeader; 33 | -------------------------------------------------------------------------------- /src/utils/helpers/auth/createExtractTokenFromRequest/extractors/createAuthSchemeExtractor/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import authConfig from '../../../../../../config/subconfigs/auth'; 3 | import isString from '../../../../commons/isString'; 4 | // @credits: https://github.com/themikenicholson/passport-jwt 5 | 6 | export interface AuthSchemeExtractorOptions { 7 | authHeaderName?: string; 8 | authSchemeName?: string; 9 | } 10 | 11 | export const createAuthSchemeExtractor = ({ 12 | authSchemeName = authConfig.jwt.authSchemeName, 13 | authHeaderName = authConfig.jwt.authHeaderName, 14 | }: AuthSchemeExtractorOptions) => (req: Request): string | null => { 15 | const authScheme: string = authSchemeName.toLowerCase(); 16 | const header: any = req.headers[authHeaderName]; 17 | 18 | if (!isString(header)) { 19 | return null; 20 | } 21 | 22 | const [, matchAuthScheme, matchAuthHeader] = header.match(/(\S+)\s+(\S+)/); 23 | 24 | if ( 25 | isString(matchAuthScheme) && 26 | isString(matchAuthHeader) && 27 | matchAuthScheme.toLowerCase() === authScheme 28 | ) { 29 | return matchAuthHeader; 30 | } 31 | 32 | return null; 33 | }; 34 | 35 | export default createAuthSchemeExtractor; 36 | -------------------------------------------------------------------------------- /src/config/subconfigs/repo/model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | KNEX_CLIENT, 3 | KNEX_DATABASE, 4 | KNEX_HOST, 5 | KNEX_PASSWORD, 6 | KNEX_USER, 7 | MODELS_REPO_TYPE, 8 | } from '../../../constants'; 9 | import getStringValue from '../../../utils/helpers/config/getStringValue'; 10 | 11 | export interface ConnectionConfig { 12 | readonly database: string; 13 | readonly host: string; 14 | readonly password: string; 15 | readonly user: string; 16 | } 17 | 18 | export interface KnexConfig { 19 | readonly client: string; 20 | readonly connection: ConnectionConfig; 21 | } 22 | 23 | export interface ModelConfig { 24 | readonly type: string; 25 | readonly knex: KnexConfig; 26 | } 27 | 28 | const config: ModelConfig = { 29 | knex: { 30 | client: getStringValue(process.env.KNEX_CLIENT, KNEX_CLIENT), 31 | connection: { 32 | database: getStringValue(process.env.KNEX_DATABASE, KNEX_DATABASE), 33 | host: getStringValue(process.env.KNEX_HOST, KNEX_HOST), 34 | password: getStringValue(process.env.KNEX_PASSWORD, KNEX_PASSWORD), 35 | user: getStringValue(process.env.KNEX_USER, KNEX_USER), 36 | }, 37 | }, 38 | type: getStringValue(process.env.MODELS_REPO_TYPE, MODELS_REPO_TYPE), 39 | }; 40 | 41 | export default config; 42 | -------------------------------------------------------------------------------- /src/presenter/express/utils/translations/mapValidationErrorsToResponse/index.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import ValidationError from 'rulr/ValidationError'; 3 | import translatorFactory from '../../../../../translator/factory'; 4 | import MatchValidationError from '../../../../../utils/errors/validation/MatchValidationError'; 5 | import translateValidationError from '../translateValidationError'; 6 | 7 | export interface Options { 8 | req: Request; 9 | translator: ReturnType; 10 | errors: ValidationError[]; 11 | } 12 | 13 | export const getFieldsProperties = (error: ValidationError) => { 14 | if (error instanceof MatchValidationError) { 15 | return { 16 | fields: [error.fieldOne, error.fieldTwo], 17 | }; 18 | } 19 | 20 | return { 21 | field: error.getPath(), 22 | }; 23 | }; 24 | 25 | const mapValidationErrorsToResponse = ({ 26 | errors, 27 | req, 28 | translator, 29 | }: Options) => { 30 | const translation = translator({ req }); 31 | 32 | return { 33 | errors: errors.map(error => ({ 34 | ...getFieldsProperties(error), 35 | message: translateValidationError({ translation, error }), 36 | })), 37 | }; 38 | }; 39 | 40 | export default mapValidationErrorsToResponse; 41 | -------------------------------------------------------------------------------- /src/service/functions/getCourseDetails/index.ts: -------------------------------------------------------------------------------- 1 | import { Filter, ItemNotFoundError } from '@js-items/foundation'; 2 | import _pluck from 'ramda/src/pluck'; 3 | import Course from '../../../types/items/Course'; 4 | import getVisibleUserProperties from '../../../utils/helpers/model/getVisibleUserProperties'; 5 | import Config from '../../FactoryConfig'; 6 | 7 | export interface Options { 8 | readonly filter: Filter; 9 | } 10 | 11 | export default ({ repo }: Config) => async ({ filter }: Options) => { 12 | const { items: courses } = await repo.courses.getItems({ 13 | filter, 14 | }); 15 | 16 | if (courses.length === 0) { 17 | throw new ItemNotFoundError('Course'); 18 | } 19 | 20 | const course = courses[0]; 21 | 22 | const { item: category } = await repo.categories.getItem({ 23 | id: course.categoryId, 24 | }); 25 | 26 | const { item: user } = await repo.users.getItem({ 27 | id: course.userId, 28 | }); 29 | 30 | const { items: sections } = await repo.sections.getItems({ 31 | filter: { 32 | courseId: course.id 33 | }, 34 | }); 35 | 36 | return { 37 | course: { 38 | ...course, 39 | category, 40 | sections, 41 | user: getVisibleUserProperties(user), 42 | }, 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/categories/functions/beforeCreateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 5 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 6 | import transactionWrapper, { 7 | HookOptions, 8 | } from '../../../../../../utils/handlers/transactionWrapper'; 9 | import rules, { 10 | beforeCreateSchema, 11 | } from '../../../../../../utils/schemas/courses/createItem'; 12 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 13 | 14 | const defaultTransactionHandler = (config: BaseFactoryConfig) => 15 | transactionWrapper({ 16 | beforeHandler: async ({ req }: HookOptions) => { 17 | const user = await getAuthenticatedUser({ req, config }); 18 | 19 | await hasPermission({ req, user, config }); 20 | 21 | const payload: any = _pick(Object.keys(beforeCreateSchema), req.body); 22 | 23 | validateData(rules)(payload); 24 | 25 | // user who has permission to create an article is an author 26 | req.body.user_id = user.id; 27 | }, 28 | config, 29 | }); 30 | 31 | export default defaultTransactionHandler; 32 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/courses/functions/beforeCreateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 5 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 6 | import transactionWrapper, { 7 | HookOptions, 8 | } from '../../../../../../utils/handlers/transactionWrapper'; 9 | import rules, { 10 | beforeCreateSchema, 11 | } from '../../../../../../utils/schemas/courses/createItem'; 12 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 13 | 14 | const defaultTransactionHandler = (config: BaseFactoryConfig) => 15 | transactionWrapper({ 16 | beforeHandler: async ({ req }: HookOptions) => { 17 | const user = await getAuthenticatedUser({ req, config }); 18 | 19 | await hasPermission({ req, user, config }); 20 | 21 | const payload: any = _pick(Object.keys(beforeCreateSchema), req.body); 22 | 23 | validateData(rules)(payload); 24 | 25 | // user who has permission to create an article is an author 26 | req.body.user_id = user.id; 27 | }, 28 | config, 29 | }); 30 | 31 | export default defaultTransactionHandler; 32 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/roles/functions/revokeRolePermission/index.ts: -------------------------------------------------------------------------------- 1 | import { NO_CONTENT } from 'http-status-codes'; 2 | import _defaultTo from 'ramda/src/defaultTo'; 3 | import _isNil from 'ramda/src/isNil'; 4 | import _pick from 'ramda/src/pick'; 5 | import validateData from 'rulr/validateData'; 6 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 7 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 8 | import catchErrors from '../../../../../../utils/errors/catchErrors'; 9 | import rules from '../../../../../../utils/schemas/roles/revokeRolePermission'; 10 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 11 | 12 | const revokeRolePermission = (config: BaseFactoryConfig) => 13 | catchErrors(config, async (req, res) => { 14 | const user = await getAuthenticatedUser({ req, config }); 15 | 16 | await hasPermission({ req, user, config }); 17 | 18 | const { permission_id, role_id } = req.params; 19 | 20 | validateData(rules)({ role_id, permission_id }); 21 | 22 | await config.service.revokeRolePermission({ 23 | permissionId: permission_id, 24 | roleId: role_id, 25 | }); 26 | 27 | res.status(NO_CONTENT); 28 | }); 29 | 30 | export default revokeRolePermission; 31 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/sections/functions/beforeCreateItem/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 5 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 6 | import transactionWrapper, { 7 | HookOptions, 8 | } from '../../../../../../utils/handlers/transactionWrapper'; 9 | import rules, { 10 | beforeCreateSchema, 11 | } from '../../../../../../utils/schemas/sections/createItem'; 12 | import { BaseFactoryConfig } from '../../../utils/baseFactory'; 13 | 14 | const defaultTransactionHandler = (config: BaseFactoryConfig) => 15 | transactionWrapper({ 16 | beforeHandler: async ({ req }: HookOptions) => { 17 | const user = await getAuthenticatedUser({ req, config }); 18 | 19 | await hasPermission({ req, user, config }); 20 | 21 | const payload: any = _pick(Object.keys(beforeCreateSchema), req.body); 22 | 23 | validateData(rules)(payload); 24 | 25 | // user who has permission to create an article is an author 26 | req.body.user_id = user.id; 27 | }, 28 | config, 29 | }); 30 | 31 | export default defaultTransactionHandler; 32 | -------------------------------------------------------------------------------- /src/repo/model/knex/migrations/tables/createEnrolmentsTable.ts: -------------------------------------------------------------------------------- 1 | import { UUID_LENGTH } from '../../../../../constants'; 2 | import { RepoConfig } from '../../factory'; 3 | 4 | export default ({ db }: RepoConfig) => { 5 | const key = 'create_enrolments_table'; 6 | 7 | const up = async () => { 8 | const connection = await db(); 9 | 10 | const query = connection.schema.createTable('enrolments', table => { 11 | table.string('id', UUID_LENGTH).primary(); 12 | table 13 | .string('userId', UUID_LENGTH) 14 | .references('id') 15 | .inTable('users') 16 | .onDelete('cascade'); 17 | table 18 | .string('courseId', UUID_LENGTH) 19 | .references('id') 20 | .inTable('courses') 21 | .onDelete('cascade'); 22 | table 23 | .dateTime('createdAt') 24 | .notNullable(); 25 | table.dateTime('updatedAt').nullable(); 26 | table.dateTime('deletedAt').nullable(); 27 | table.unique(['courseId', 'userId']) 28 | }); 29 | 30 | await Promise.resolve(query); 31 | }; 32 | 33 | const down = async () => { 34 | const connection = await db(); 35 | 36 | await Promise.resolve(connection.schema.dropTable('enrolments')); 37 | }; 38 | 39 | return { key, up, down }; 40 | }; 41 | -------------------------------------------------------------------------------- /src/service/functions/auth/verifyAccount/index.ts: -------------------------------------------------------------------------------- 1 | import _isNil from 'ramda/src/isNil'; 2 | import AccountAlreadyVerifiedError from '../../../../utils/errors/auth/AccountAlreadyVerifiedError'; 3 | import InvalidVerifyAccountTokenError from '../../../../utils/errors/auth/InvalidVerifyAccountTokenError'; 4 | import getUtcDate from '../../../../utils/helpers/date/getUtcDate'; 5 | import Config from '../../../FactoryConfig'; 6 | 7 | export interface Options { 8 | readonly email: string; 9 | readonly token: string; 10 | } 11 | 12 | export default ({ repo }: Config) => async ({ email, token }: Options) => { 13 | const { items } = await repo.users.getItems({ 14 | filter: { 15 | email, 16 | verifyToken: token, 17 | }, 18 | }); 19 | 20 | if (items.length === 0) { 21 | throw new InvalidVerifyAccountTokenError(email); 22 | } 23 | 24 | const user = items[0]; 25 | 26 | if(!_isNil(user.verifiedAt)){ 27 | throw new AccountAlreadyVerifiedError(email); 28 | } 29 | 30 | await repo.users.updateItem({ 31 | id: user.id, 32 | patch: { 33 | updatedAt: getUtcDate(), 34 | verifiedAt: getUtcDate(), 35 | verifyAttempts: 0, 36 | verifyLastAttemptAt: getUtcDate(), 37 | verifyLockoutExpiresAt: null, 38 | }, 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/translator/default/translations/interfaces/Validation.ts: -------------------------------------------------------------------------------- 1 | import { StringValidationError } from 'rulr/String'; 2 | import ValidationError from 'rulr/ValidationError'; 3 | import DateValidationError from '../../../../utils/errors/validation/DateValidationError'; 4 | import EmailValidationError from '../../../../utils/errors/validation/EmailValidationError'; 5 | import EnumValidationError from '../../../../utils/errors/validation/EnumValidationError'; 6 | import MatchValidationError from '../../../../utils/errors/validation/MatchValidationError'; 7 | import PasswordValidationError from '../../../../utils/errors/validation/PasswordValidationError'; 8 | 9 | export default interface Validation { 10 | readonly enumValidationError: (error: EnumValidationError) => string; 11 | readonly dateValidationError: (error: DateValidationError) => string; 12 | readonly validationFailed: () => string; 13 | readonly stringValidationError: (error: StringValidationError) => string; 14 | readonly emailValidationError: (error: EmailValidationError) => string; 15 | readonly passwordValidationError: (error: PasswordValidationError) => string; 16 | readonly unknownValidationError: (error: ValidationError) => string; 17 | readonly matchValidationError: (error: MatchValidationError) => string; 18 | } 19 | -------------------------------------------------------------------------------- /src/translator/default/translations/interfaces/Errors.ts: -------------------------------------------------------------------------------- 1 | import { ItemNotFoundError } from "@js-items/foundation"; 2 | import ConflictError from "../../../../utils/errors/http/ConflictError"; 3 | 4 | export default interface ErrorsTranslations { 5 | readonly accountLocked: () => string; 6 | readonly verifyFunctionalityLocked: () => string; 7 | readonly verifyTokenSent: () => string; 8 | readonly conflict: (error: ConflictError) => string; 9 | readonly expiredJwtToken: () => string; 10 | readonly invalidJwtToken: () => string; 11 | readonly invalidResetPasswordtoken: () => string; 12 | readonly invalidVerifyAccountToken: () => string; 13 | readonly accountAlreadyVerified: () => string; 14 | readonly expiredResetPasswordtoken: () => string; 15 | readonly missingJwtToken: () => string; 16 | readonly missingJwtTokenExtractor: () => string; 17 | readonly unauthenticated: () => string; 18 | readonly forbidden: () => string; 19 | readonly serverError: () => string; 20 | readonly invalidCredentials: () => string; 21 | readonly unverifiedAccount: () => string; 22 | readonly unsupportedMediaType: () => string; 23 | readonly notFound: () => string; 24 | readonly serviceIsUnavailable: () => string; 25 | readonly itemNotFound: (error: ItemNotFoundError) => string; 26 | } 27 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories/personalDevelopment.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const personalDevelopmentCategory = { 4 | courses: [ 5 | { 6 | slug: 'life-coach-training', 7 | title: 'Life coach training', 8 | }, 9 | { 10 | slug: 'master-your-cv', 11 | title: 'Master your CV', 12 | }, 13 | { 14 | slug: 'speed-reading', 15 | title: 'Speed reading', 16 | }, 17 | { 18 | slug: 'nlp-practitioner', 19 | title: 'NLP practitioner', 20 | }, 21 | { 22 | slug: 'public-speaking', 23 | title: 'Public speaking', 24 | }, 25 | { 26 | slug: 'negotiation-techniques', 27 | title: 'Negotiation-techniques', 28 | }, 29 | { 30 | slug: 'time-management', 31 | title: 'Time management', 32 | }, 33 | { 34 | slug: 'body-language', 35 | title: 'Body language', 36 | }, 37 | { 38 | slug: 'micro-expressions-training', 39 | title: 'Micro expressions training', 40 | }, 41 | { 42 | slug: 'learn-memory-techniques', 43 | title: 'Learn memory techniques', 44 | }, 45 | ], 46 | id: uuid(), 47 | slug: 'personal-development', 48 | title: 'Personal development', 49 | }; 50 | 51 | export default personalDevelopmentCategory; 52 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories/design.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const designCategory = { 4 | courses: [ 5 | { 6 | slug: 'drawing-course-for-beginners', 7 | title: 'Drawing course for beginners', 8 | }, 9 | { 10 | slug: 'learn-3d-animations', 11 | title: 'Learn 3D animations', 12 | }, 13 | { 14 | slug: 'advanced-photoshop-training', 15 | title: 'Advanced photoshop training', 16 | }, 17 | { 18 | slug: 'ux-for-beginners', 19 | title: 'User experience for beginners', 20 | }, 21 | { 22 | slug: 'drawing-comics', 23 | title: 'Drawing comics', 24 | }, 25 | { 26 | slug: 'animations-masterclass', 27 | title: 'Animations masterclass', 28 | }, 29 | { 30 | slug: 'adobe-illustrator-for-beginners', 31 | title: 'Adobe Illustrator for beginners', 32 | }, 33 | { 34 | slug: 'web-design-basics', 35 | title: 'Web design basics', 36 | }, 37 | { 38 | slug: 'sketch-fundamentals', 39 | title: 'Sketch fundamentals', 40 | }, 41 | { 42 | slug: 'mobile-app-design', 43 | title: 'Mobile App design', 44 | }, 45 | ], 46 | id: uuid(), 47 | slug: 'design', 48 | title: 'Design', 49 | }; 50 | 51 | export default designCategory; 52 | -------------------------------------------------------------------------------- /src/presenter/express/utils/fakeFactories/index.ts: -------------------------------------------------------------------------------- 1 | import { Item } from '@js-items/foundation'; 2 | import Facade from '@js-items/foundation/dist/Facade'; 3 | import _times from 'ramda/src/times'; 4 | import { v4 as uuid } from 'uuid'; 5 | import getUtcDate from '../../../../utils/helpers/date/getUtcDate'; 6 | 7 | export interface OverrideInterface { 8 | readonly [key: string]: any; 9 | } 10 | 11 | export interface Options { 12 | readonly times?: number; 13 | readonly overrides?: Partial; 14 | readonly service: Facade; 15 | } 16 | 17 | const baseFactory = (itemData: Partial) => async ({ 18 | overrides = {}, 19 | times = 1, 20 | service, 21 | }: Options): Promise => { 22 | const models: Partial[] = []; 23 | 24 | _times(() => { 25 | models.push(itemData); 26 | }, times); 27 | 28 | const promises = models.map(async model => { 29 | const id = uuid(); 30 | const item: any = { 31 | ...model, 32 | createdAt: getUtcDate(), 33 | id, 34 | updatedAt: undefined, 35 | ...overrides, 36 | }; 37 | 38 | return service.createItem({ 39 | id: item.id, 40 | item, 41 | }); 42 | }); 43 | 44 | return (await Promise.all(promises)).map(({ item }) => item); 45 | }; 46 | 47 | export default baseFactory; 48 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/permissions/factory.ts: -------------------------------------------------------------------------------- 1 | import _pick from 'ramda/src/pick'; 2 | import Permission from '../../../../../../types/items/Permission'; 3 | import Config from '../../../../presenterFactory/Config'; 4 | import beforeCreateRules, { 5 | beforeCreateSchema, 6 | } from '../../../../utils/schemas/permissions/createItem'; 7 | import beforeReplaceRules, { 8 | beforeReplaceSchema, 9 | } from '../../../../utils/schemas/permissions/replaceItem'; 10 | import beforeUpdateRules, { 11 | beforeUpdateSchema, 12 | } from '../../../../utils/schemas/permissions/updateItem'; 13 | import baseFactory from '../utils/baseFactory'; 14 | import convertDocumentIntoItem from './functions/convertDocumentIntoItem'; 15 | 16 | const permissionsFactory = (config: Config) => { 17 | const enhancedConfig = { 18 | ...config, 19 | beforeCreateRules, 20 | beforeCreateSchema, 21 | beforeReplaceRules, 22 | beforeReplaceSchema, 23 | beforeUpdateRules, 24 | beforeUpdateSchema, 25 | }; 26 | const router = baseFactory({ 27 | config: enhancedConfig, 28 | factoryConfig: { 29 | convertDocumentIntoItem: convertDocumentIntoItem(enhancedConfig), 30 | }, 31 | service: config.service.permissions, 32 | }); 33 | 34 | return router; 35 | }; 36 | 37 | export default permissionsFactory; 38 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories/marketing.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const marketingCategory = { 4 | courses: [ 5 | { 6 | slug: 'google-ads-introduction', 7 | title: 'Google Ads Introduction', 8 | }, 9 | { 10 | slug: 'social-media-marketing', 11 | title: 'Social media marketing', 12 | }, 13 | { 14 | slug: 'branding-techniques', 15 | title: 'Branding techniques', 16 | }, 17 | { 18 | slug: 'introduction-to-copywriting', 19 | title: 'Introduction to copywriting', 20 | }, 21 | { 22 | slug: 'email-marketing-for-beginners', 23 | title: 'Email marketing for beginners', 24 | }, 25 | { 26 | slug: 'affiliate-marketing', 27 | title: 'Affiliate marketing', 28 | }, 29 | { 30 | slug: 'branding-strategy', 31 | title: 'Branding strategy', 32 | }, 33 | { 34 | slug: 'google-tag-manager-training', 35 | title: 'Google tag manager training', 36 | }, 37 | { 38 | slug: 'web-content-writing', 39 | title: 'Web content writing', 40 | }, 41 | { 42 | slug: 'seo-masterclass', 43 | title: 'SEO Master class', 44 | }, 45 | ], 46 | id: uuid(), 47 | slug: 'marketing', 48 | title: 'Marketing', 49 | }; 50 | 51 | export default marketingCategory; 52 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/autocompleteHandler/index.ts: -------------------------------------------------------------------------------- 1 | import sendResponse from '@js-items/express/dist/utils/sendResponse'; 2 | import { toSnake } from 'convert-keys'; 3 | import { OK } from 'http-status-codes'; 4 | import Record from 'rulr/Record'; 5 | import validateData from 'rulr/validateData'; 6 | import { SAFE_URL_LENGTH } from '../../../../../../constants'; 7 | import String from '../../../../../../utils/validation/rules/String'; 8 | import Config from '../../../../presenterFactory/Config'; 9 | import catchErrors from '../../../../utils/errors/catchErrors'; 10 | 11 | 12 | const autocompleteHandler = (config: Config) => 13 | catchErrors(config, async (req, res) => { 14 | const name = config.appConfig.http.client.autocompleteQueryParam; 15 | 16 | const validationSchema = { 17 | [name]: String(0, SAFE_URL_LENGTH), 18 | }; 19 | 20 | const rules = Record(validationSchema); 21 | const value = req.query[name]; 22 | 23 | const payload: any = {[name]:value}; 24 | 25 | validateData(rules)(payload); 26 | 27 | const response = await config.service.autocomplete({ 28 | query: value 29 | }); 30 | 31 | sendResponse({ 32 | body: toSnake(response), 33 | req, 34 | res, 35 | status: OK, 36 | }); 37 | }); 38 | 39 | export default autocompleteHandler; 40 | -------------------------------------------------------------------------------- /src/service/functions/getDiscoveryItemsForHomepage/index.ts: -------------------------------------------------------------------------------- 1 | import _pluck from 'ramda/src/pluck'; 2 | import Course from '../../../types/items/Course'; 3 | import Config from '../../FactoryConfig'; 4 | 5 | // tslint:disable-next-line:no-empty-interface 6 | export interface Options {} 7 | 8 | // tslint:disable-next-line:arrow-return-shorthand 9 | export default ({ repo }: Config) => async (_options: Options) => { 10 | const { items: categories } = await repo.categories.getItems({ 11 | pagination: { 12 | limit: 10000, 13 | }, 14 | }); 15 | 16 | const { items: courses } = await repo.courses.getItems({ 17 | pagination: { 18 | limit: 10000, 19 | }, 20 | }); 21 | 22 | const { items: users } = await repo.users.getItems({ 23 | pagination: { 24 | limit: 10000, 25 | }, 26 | }); 27 | 28 | const enhancedCourses = courses.map((course: Course) => ({ 29 | ...course, 30 | user: users.filter(user => user.id === course.userId)[0], 31 | })); 32 | 33 | return { 34 | bestSellers: { 35 | // TODO: in the future basing on number of enrolments choose the most popular one 36 | categories, 37 | courses: enhancedCourses, 38 | }, 39 | mostViewed: { 40 | // TODO: in the future choose the most viewed ones 41 | courses: enhancedCourses, 42 | }, 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories/finance.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const financeCategory = { 4 | courses: [ 5 | { 6 | slug: 'invest-in-stocks', 7 | title: 'Invest in stocks', 8 | }, 9 | { 10 | slug: 'introduction-to-blockchain', 11 | title: 'Introduction to blockchain', 12 | }, 13 | { 14 | slug: 'options-trading-fundamentals', 15 | title: 'Options trading fundamentals', 16 | }, 17 | { 18 | slug: 'introduction-to-investing', 19 | title: 'Introduction-to-investing', 20 | }, 21 | { 22 | slug: 'tax-optimisation', 23 | title: 'Tax optimisation', 24 | }, 25 | { 26 | slug: 'introduction-to-balance-sheet', 27 | title: 'Introduction to balance sheet', 28 | }, 29 | { 30 | slug: 'budgeting-basics', 31 | title: 'Budgeting basics', 32 | }, 33 | { 34 | slug: 'healthy cash flow', 35 | title: 'Healthy cash flow', 36 | }, 37 | { 38 | slug: 'learn-how-to-create-property-portfolio', 39 | title: 'Learn how to create property portfolio', 40 | }, 41 | { 42 | slug: 'hedge-and-mutual-funds', 43 | title: 'Hedge and mutual funds', 44 | }, 45 | ], 46 | id: uuid(), 47 | slug: 'finance', 48 | title: 'Finance', 49 | }; 50 | 51 | export default financeCategory; 52 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/auth/resetPassword/index.ts: -------------------------------------------------------------------------------- 1 | import { OK } from 'http-status-codes'; 2 | import _pick from 'ramda/src/pick'; 3 | import validateData from 'rulr/validateData'; 4 | import { BaseOptions } from '../../../../../../../repo/mail/nodemailer/functions/sendEmail'; 5 | import Config from '../../../../../presenterFactory/Config'; 6 | import catchErrors from '../../../../../utils/errors/catchErrors'; 7 | import rules, { schema } from '../../../../../utils/schemas/auth/resetPassword'; 8 | 9 | export default (config: Config) => 10 | catchErrors(config, async (req, res) => { 11 | const payload: any = _pick(Object.keys(schema), req.body); 12 | 13 | const { token, password } = validateData(rules)(payload); 14 | const { appConfig, translator } = config; 15 | 16 | const translations = translator({ req }); 17 | 18 | const mailOptions: BaseOptions = { 19 | from: appConfig.repo.mail.from, 20 | html: translations.resetPasswordHtml(), 21 | subject: translations.resetPasswordSubject(), 22 | text: translations.resetPasswordText(), 23 | }; 24 | 25 | await config.service.auth.resetPassword({ 26 | mailOptions, 27 | password, 28 | token, 29 | }); 30 | 31 | const message = translations.passwordChangedSuccessfully(); 32 | 33 | res.status(OK).json({ message }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/utils/describeApi/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`@presenter/describeApi describes API metadata 1`] = ` 4 | Object { 5 | "available_routes": Object { 6 | "auth": Object { 7 | "login": "/api/v1/auth/login", 8 | "register": "/api/v1/auth/register", 9 | "remind_password": "/api/v1/auth/remind-password", 10 | "resend_verify_token": "/api/v1/auth/resend-verify-token", 11 | "reset_password": "/api/v1/auth/reset-password", 12 | "verify_account": "/api/v1/auth/verify-account", 13 | }, 14 | "autocomplete": "/api/v1/autocomplete", 15 | "categories": "/api/v1/categories", 16 | "checks": Object { 17 | "liveness": "/health/liveness", 18 | "readiness": "/health/readiness", 19 | "version": "/version", 20 | }, 21 | "courses": "/api/v1/courses", 22 | "discovery_items": "/api/v1/discovery-items", 23 | "enrolments": "/api/v1/enrolments", 24 | "permissions": "/api/v1/permissions", 25 | "roles": "/api/v1/roles", 26 | "sections": "/api/v1/sections", 27 | "users": "/api/v1/users", 28 | }, 29 | "docs": "https://kubetsserver.docs.apiary.io", 30 | "issues": "https://github.com/kube-js/kube-ts-server/issues", 31 | "repo": "https://github.com/kube-js/kube-ts-server", 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /src/presenter/express/index.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | import dotenv from 'dotenv'; 3 | import sourceMapSupport from 'source-map-support'; 4 | import stoppable from 'stoppable'; 5 | sourceMapSupport.install(); 6 | import handleUnexpectedEvents from './utils/errors/handleUnexpectedEvents'; 7 | dotenv.config(); 8 | import express from 'express'; 9 | import { createServer } from 'http'; 10 | import config from '../../config'; 11 | import app from './app'; 12 | 13 | const expressApp: express.Application = express(); 14 | 15 | if (config.http.express.trustProxy) { 16 | // see: https://stackoverflow.com/questions/23413401/what-does-trust-proxy-actually-do-in-express-js-and-do-i-need-to-use-it 17 | expressApp.enable('trust proxy'); 18 | } 19 | 20 | const { service, logger, presenter } = app({ 21 | auth: config.auth, 22 | http: config.http, 23 | logger: config.logger, 24 | repo: config.repo, 25 | translator: config.translator, 26 | }); 27 | 28 | expressApp.all('*', presenter); 29 | 30 | const server = stoppable(createServer(expressApp)); 31 | 32 | /* @credits: https://github.com/banzaicloud/node-service-tools */ 33 | handleUnexpectedEvents({ server, service, logger }); 34 | 35 | server.listen(config.http.express.port, () => { 36 | logger.info( 37 | `Listening on ${config.http.express.host}:${config.http.express.port}` 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /src/presenter/express/utils/errors/handleUnexpectedEvents/gracefulShutDown/index.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | /* @credits: https://github.com/banzaicloud/node-service-tools */ 3 | import { StoppableServer } from 'stoppable'; 4 | import { processExitTimeout } from '../../../../../commander/functions/dbSeed'; 5 | import { Config } from '../index'; 6 | 7 | export interface Options { 8 | readonly config: Config; 9 | readonly reason: string; 10 | } 11 | 12 | const stopServer = async (server: StoppableServer) => 13 | new Promise((resolve, reject) => { 14 | server.stop((err: any, gracefully) => { 15 | if (err) { 16 | reject(err); 17 | } 18 | resolve(gracefully); 19 | }); 20 | }); 21 | 22 | const gracefulShutDown = ({ config, reason }: Options) => async () => { 23 | const { server, service, logger } = config; 24 | 25 | try { 26 | logger.warn(`Gracefully shutting down all resources caused by ${reason}`); 27 | 28 | await stopServer(server); 29 | 30 | await service.closeDbConnection(); 31 | 32 | setTimeout(() => { 33 | process.exit(0); 34 | }, processExitTimeout); 35 | } catch (err) { 36 | logger.error(`Failed to close all resources ${err}`); 37 | 38 | setTimeout(() => { 39 | process.exit(1); 40 | }, processExitTimeout); 41 | } 42 | }; 43 | 44 | export default gracefulShutDown; 45 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/users/functions/assignUserRole/index.ts: -------------------------------------------------------------------------------- 1 | import { CREATED } from 'http-status-codes'; 2 | import _defaultTo from 'ramda/src/defaultTo'; 3 | import _isNil from 'ramda/src/isNil'; 4 | import _pick from 'ramda/src/pick'; 5 | import validateData from 'rulr/validateData'; 6 | import Config from '../../../../../../presenterFactory/Config'; 7 | import getAuthenticatedUser from '../../../../../../utils/auth/getAuthenticatedUser'; 8 | import hasPermission from '../../../../../../utils/auth/hasPermission'; 9 | import catchErrors from '../../../../../../utils/errors/catchErrors'; 10 | import rules from '../../../../../../utils/schemas/users/assignUserRole'; 11 | 12 | const assignUserRole = (config: Config) => 13 | catchErrors(config, async (req, res) => { 14 | const user = await getAuthenticatedUser({ req, config }); 15 | 16 | await hasPermission({ req, user, config }); 17 | 18 | const { user_id } = req.params; 19 | const { role_id } = req.body; 20 | 21 | validateData(rules)({ user_id, role_id }); 22 | 23 | await config.service.assignUserRole({ 24 | roleId: role_id, 25 | userId: user_id, 26 | }); 27 | 28 | const translations = config.translator({ req }); 29 | 30 | res.status(CREATED).json({ 31 | message: translations.created(), 32 | }); 33 | }); 34 | 35 | export default assignUserRole; 36 | -------------------------------------------------------------------------------- /k8s/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullname := include "kube-ts-server.fullname" . -}} 3 | {{- $ingressPaths := .Values.ingress.paths -}} 4 | {{- $servicePort := .Values.service.port }} 5 | {{- $ingressPath := .Values.ingress.path }} 6 | apiVersion: extensions/v1beta1 7 | kind: Ingress 8 | metadata: 9 | name: {{ $fullname }} 10 | labels: 11 | app.kubernetes.io/name: {{ include "kube-ts-server.name" . }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | app.kubernetes.io/managed-by: {{ .Release.Service }} 14 | helm.sh/chart: {{ include "kube-ts-server.chart" . }} 15 | {{- with .Values.ingress.annotations }} 16 | annotations: 17 | certmanager.k8s.io/cluster-issuer: letsencrypt-prod 18 | {{- toYaml . | nindent 4 }} 19 | {{- end }} 20 | spec: 21 | {{- if .Values.ingress.tls }} 22 | tls: 23 | {{- range .Values.ingress.tls }} 24 | - hosts: 25 | {{- range .hosts }} 26 | - {{ . | quote }} 27 | {{- end }} 28 | secretName: letsencrypt-prod-kube-ts-server 29 | {{- end }} 30 | {{- end }} 31 | rules: 32 | {{- range .Values.ingress.hosts }} 33 | - host: {{ . | quote }} 34 | http: 35 | paths: 36 | - path: {{ $ingressPath }} 37 | backend: 38 | serviceName: {{ $fullname }} 39 | servicePort: {{ $servicePort }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /src/presenter/commander/functions/dbSeed/functions/connectPermissionsToRoles/index.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | import { v4 as uuid } from 'uuid'; 3 | import getUtcDate from '../../../../../../utils/helpers/date/getUtcDate'; 4 | import FactoryConfig from '../../../../presenterFactory/FactoryConfig'; 5 | 6 | export interface Options { 7 | readonly permissionsIds: string[]; 8 | readonly roleId: string; 9 | readonly roleName: string; 10 | } 11 | 12 | const connectPermissionsToRoles = (config: FactoryConfig) => async ({ 13 | permissionsIds, 14 | roleId, 15 | roleName, 16 | }: Options) => { 17 | console.log(`--------------------------------------------------------`); 18 | console.log(`Connecting role [${roleName}] to permissions...`); 19 | 20 | const permissionsPromises = permissionsIds.map(async permissionId => { 21 | const rolePermissionId = uuid(); 22 | 23 | return config.service.rolePermission.createItem({ 24 | id: rolePermissionId, 25 | item: { 26 | createdAt: getUtcDate(), 27 | id: rolePermissionId, 28 | permissionId, 29 | roleId, 30 | }, 31 | }); 32 | }); 33 | 34 | await Promise.all(permissionsPromises); 35 | 36 | console.log(`Connected role to permissions successfuly!`); 37 | console.log(`--------------------------------------------------------`); 38 | }; 39 | 40 | export default connectPermissionsToRoles; 41 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/courses/factory.ts: -------------------------------------------------------------------------------- 1 | import _pick from 'ramda/src/pick'; 2 | import Course from '../../../../../../types/items/Course'; 3 | import Config from '../../../../presenterFactory/Config'; 4 | import beforeCreateRules, { 5 | beforeCreateSchema, 6 | } from '../../../../utils/schemas/courses/createItem'; 7 | import beforeReplaceRules, { 8 | beforeReplaceSchema, 9 | } from '../../../../utils/schemas/courses/replaceItem'; 10 | import beforeUpdateRules, { 11 | beforeUpdateSchema, 12 | } from '../../../../utils/schemas/courses/updateItem'; 13 | import baseFactory from '../utils/baseFactory'; 14 | import beforeCreateItem from './functions/beforeCreateItem'; 15 | import convertDocumentIntoItem from './functions/convertDocumentIntoItem'; 16 | 17 | const coursesFactory = (config: Config) => { 18 | const enhancedConfig = { 19 | ...config, 20 | beforeCreateRules, 21 | beforeCreateSchema, 22 | beforeReplaceRules, 23 | beforeReplaceSchema, 24 | beforeUpdateRules, 25 | beforeUpdateSchema, 26 | }; 27 | const router = baseFactory({ 28 | config: enhancedConfig, 29 | factoryConfig: { 30 | beforeCreateItem: beforeCreateItem(enhancedConfig), 31 | convertDocumentIntoItem: convertDocumentIntoItem(enhancedConfig), 32 | }, 33 | service: config.service.courses, 34 | }); 35 | 36 | return router; 37 | }; 38 | 39 | export default coursesFactory; 40 | -------------------------------------------------------------------------------- /src/presenter/express/api/v1/routes/sections/factory.ts: -------------------------------------------------------------------------------- 1 | import _pick from 'ramda/src/pick'; 2 | import Section from '../../../../../../types/items/Section'; 3 | import Config from '../../../../presenterFactory/Config'; 4 | import beforeCreateRules, { 5 | beforeCreateSchema, 6 | } from '../../../../utils/schemas/sections/createItem'; 7 | import beforeReplaceRules, { 8 | beforeReplaceSchema, 9 | } from '../../../../utils/schemas/sections/replaceItem'; 10 | import beforeUpdateRules, { 11 | beforeUpdateSchema, 12 | } from '../../../../utils/schemas/sections/updateItem'; 13 | import baseFactory from '../utils/baseFactory'; 14 | import beforeCreateItem from './functions/beforeCreateItem'; 15 | import convertDocumentIntoItem from './functions/convertDocumentIntoItem'; 16 | 17 | const sectionsFactory = (config: Config) => { 18 | const enhancedConfig = { 19 | ...config, 20 | beforeCreateRules, 21 | beforeCreateSchema, 22 | beforeReplaceRules, 23 | beforeReplaceSchema, 24 | beforeUpdateRules, 25 | beforeUpdateSchema, 26 | }; 27 | const router = baseFactory
({ 28 | config: enhancedConfig, 29 | factoryConfig: { 30 | beforeCreateItem: beforeCreateItem(enhancedConfig), 31 | convertDocumentIntoItem: convertDocumentIntoItem(enhancedConfig), 32 | }, 33 | service: config.service.sections, 34 | }); 35 | 36 | return router; 37 | }; 38 | 39 | export default sectionsFactory; 40 | -------------------------------------------------------------------------------- /src/presenter/commander/data/categories/business.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | const businessCategory = { 4 | courses: [ 5 | { 6 | slug: 'introduction-to-project-management', 7 | title: 'Intruduction to project management', 8 | }, 9 | { 10 | slug: 'complete-guide-to-forex', 11 | title: 'Complete guide to forex', 12 | }, 13 | { 14 | slug: 'business-analysis-fundamentals', 15 | title: 'Business analysis fundamentals', 16 | }, 17 | { 18 | slug: 'advanced-sales-techniques', 19 | title: 'Advanced sales techniques', 20 | }, 21 | { 22 | slug: 'from-employee-to-entrepreneur', 23 | title: 'From employee to entrepreneur', 24 | }, 25 | { 26 | slug: 'running-e-commerce-website', 27 | title: 'Running e-commerce website', 28 | }, 29 | { 30 | slug: 'business-development-for-beginners', 31 | title: 'Business development for beginners', 32 | }, 33 | { 34 | slug: 'introduction-to-freelancing', 35 | title: 'Introduction to freelancing', 36 | }, 37 | { 38 | slug: 'run-business-remotely', 39 | title: 'Run business remotely', 40 | }, 41 | { 42 | slug: 'real-estate-investing', 43 | title: 'Real estate investing', 44 | }, 45 | ], 46 | id: uuid(), 47 | slug: 'business', 48 | title: 'Business', 49 | }; 50 | 51 | export default businessCategory; 52 | --------------------------------------------------------------------------------