From 399c4424cc0bbdaaeb5a0630fc88278955035e1a Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Mon, 4 Dec 2023 15:38:54 +0200 Subject: [PATCH 01/11] add sponsors --- README.md | 14 ++++++++++++++ docs/assets/sponsors/atlassian.png | Bin 0 -> 4633 bytes docs/assets/sponsors/cortip.png | Bin 0 -> 9254 bytes docs/assets/sponsors/sentry.png | Bin 0 -> 8654 bytes 4 files changed, 14 insertions(+) create mode 100644 docs/assets/sponsors/atlassian.png create mode 100644 docs/assets/sponsors/cortip.png create mode 100644 docs/assets/sponsors/sentry.png diff --git a/README.md b/README.md index 2a9f06d..f7f27d1 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,20 @@ innovators, and let's co-create the ultimate Javascript/Typescript boilerplate for the next generation. Join the ISOMERA family and let's craft the future, one line of code at a time. ๐Ÿš€ +## Thanks for our sponsors + +

+ + Cortip + + + Atlassian + + + Sentry + +

+ ## Preview - Landing page https://www.isomera.org/ diff --git a/docs/assets/sponsors/atlassian.png b/docs/assets/sponsors/atlassian.png new file mode 100644 index 0000000000000000000000000000000000000000..59eac98f380caeec7b2c96022c040dbe968f1217 GIT binary patch literal 4633 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 literal 0 HcmV?d00001 diff --git a/docs/assets/sponsors/cortip.png b/docs/assets/sponsors/cortip.png new file mode 100644 index 0000000000000000000000000000000000000000..37354ed83640911a9055ddbd5e41eb09b1a1a5a5 GIT binary patch literal 9254 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 ak;QtEi1ym*Z0euz2Te;&2UM$K9r+*Nu)nGR literal 0 HcmV?d00001 diff --git a/docs/assets/sponsors/sentry.png b/docs/assets/sponsors/sentry.png new file mode 100644 index 0000000000000000000000000000000000000000..eeac2c8842c8d2f30f7e1e1441dc865797b6e25d GIT binary patch literal 8654 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 Date: Mon, 4 Dec 2023 15:40:21 +0200 Subject: [PATCH 02/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7f27d1..c7aa261 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ line of code at a time. ๐Ÿš€ ## Thanks for our sponsors -

+

Cortip From dbff1689da84898219a635afd8b98f567d2ba8fb Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Mon, 4 Dec 2023 16:07:03 +0200 Subject: [PATCH 03/11] Update README.md --- README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c7aa261..9e44dee 100644 --- a/README.md +++ b/README.md @@ -46,16 +46,10 @@ line of code at a time. ๐Ÿš€ ## Thanks for our sponsors -

- - Cortip - - - Atlassian - - - Sentry - +

+ Cortip + Atlassian + Sentry

## Preview From 3801b94c2252659ac793369cff426a63c7e60f8d Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Tue, 5 Dec 2023 11:47:57 +0200 Subject: [PATCH 04/11] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e44dee..512a3ad 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ all-encompassing Javascript/Typescript starter boilerplate monorepo. Designed for visionary developers, ISOMERA streamlines the creation of enterprise-grade applications by providing a robust, modular, and scalable foundation. Whether you're kickstarting a project with React, Next, Nest, or diving deep into -TypeORM, Redux, and Material UI, our platform is your ticket to efficiency. +TypeORM, React Query, and Material UI, our platform is your ticket to efficiency. Beyond a mere boilerplate, ISOMERA offers comprehensive documentation, training materials, and a community-driven approach. Contribute to the revolution, join a community of like-minded developers, and redefine the standards of SaaS @@ -80,7 +80,7 @@ line of code at a time. ๐Ÿš€ - TypeScript - React -- Redux-toolkit +- React Query - Next.js - Nest.js - TypeORM @@ -104,7 +104,7 @@ line of code at a time. ๐Ÿš€ - [x] Generated documentation for API with Compodoc - [x] Swagger for API endpoints - [x] DTO as shared lib -- [ ] Redux-toolkit for app data storage +- [ ] React Query for app data storage - [x] Interfaces as shared lib - [x] Utils as shared lib (for example time formatting) - [ ] TypeORM & DB migrations From d10ead095b30f31f39381d47057d265d1c421aea Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Tue, 5 Dec 2023 11:48:14 +0200 Subject: [PATCH 05/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 512a3ad..3698208 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ innovators, and let's co-create the ultimate Javascript/Typescript boilerplate for the next generation. Join the ISOMERA family and let's craft the future, one line of code at a time. ๐Ÿš€ -## Thanks for our sponsors +## Thanks to our sponsors

Cortip From 5d3addb0f63d4e278b56e3dd050d56e625288705 Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Tue, 5 Dec 2023 11:49:03 +0200 Subject: [PATCH 06/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3698208..1c3e055 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ line of code at a time. ๐Ÿš€ - Nest.js - TypeORM - Formik -- Yup +- Class-validator (for Formik validations & DTOs) - Luxon ## SDLC ToDo From a859faa54bc869675d8cd8fd0621f891a727023d Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Tue, 5 Dec 2023 20:53:30 +0200 Subject: [PATCH 07/11] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..08f6a72 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots / Loom / Video** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 3f6cb97cb03cc0f212a6b499b28b4a607ce1bd83 Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Wed, 6 Dec 2023 16:01:48 +0200 Subject: [PATCH 08/11] just make it pretty --- .github/ISSUE_TEMPLATE/bug_report.md | 35 ++++--- .github/ISSUE_TEMPLATE/feature_request.md | 17 ++-- CONTRIBUTING.md | 109 +++++++++++++++------- README.md | 10 +- 4 files changed, 105 insertions(+), 66 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 08f6a72..0fb3a3e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,35 +4,34 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- -**Describe the bug** -A clear and concise description of what the bug is. +**Describe the bug** A clear and concise description of what the bug is. + +**To Reproduce** Steps to reproduce the behavior: -**To Reproduce** -Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** -A clear and concise description of what you expected to happen. +**Expected behavior** A clear and concise description of what you expected to +happen. -**Screenshots / Loom / Video** -If applicable, add screenshots to help explain your problem. +**Screenshots / Loom / Video** If applicable, add screenshots to help explain +your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] -**Additional context** -Add any other context about the problem here. +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] + +**Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..2866f79 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,17 +4,16 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +**Is your feature request related to a problem? Please describe.** A clear and +concise description of what the problem is. Ex. I'm always frustrated when [...] -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +**Describe the solution you'd like** A clear and concise description of what you +want to happen. -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +**Describe alternatives you've considered** A clear and concise description of +any alternative solutions or features you've considered. -**Additional context** -Add any other context or screenshots about the feature request here. +**Additional context** Add any other context or screenshots about the feature +request here. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e2f346..412075f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,57 +46,98 @@ community standards will help us redefine the future of SaaS development! ## Getting started -If you would like to become an Isomera contributor, the first step is to read this document in its entirety. The second step is to review the [README guidelines here](https://github.com/cortip/isomera/blob/main/README.md) to understand our coding philosophy and for a general overview of the code repository (i.e. how to run the app locally, testing, storage, our app philosophy, etc). Please read both documents before asking questions, as they may be covered within the documentation. +If you would like to become an Isomera contributor, the first step is to read +this document in its entirety. The second step is to review the +[README guidelines here](https://github.com/cortip/isomera/blob/main/README.md) +to understand our coding philosophy and for a general overview of the code +repository (i.e. how to run the app locally, testing, storage, our app +philosophy, etc). Please read both documents before asking questions, as they +may be covered within the documentation. ## Discord channels -All contributors should be a member of a shared Discord channel called #general -- this channel is used to ask general questions, facilitate discussions, and make feature requests. +All contributors should be a member of a shared Discord channel called #general +-- this channel is used to ask general questions, facilitate discussions, and +make feature requests. -Before requesting an invite to Discord please ensure your Upwork account is active, since we only pay via Upwork (see below). To request an invite to Slack, email contributors@isomera.com with the subject Discord Channel Invites. We'll send you an invite! Or you can make it much faster and just click this link https://discord.gg/T3CBgm8yPT . +Before requesting an invite to Discord please ensure your Upwork account is +active, since we only pay via Upwork (see below). To request an invite to Slack, +email contributors@isomera.com with the subject Discord Channel Invites. We'll +send you an invite! Or you can make it much faster and just click this link +https://discord.gg/T3CBgm8yPT . -Note: Do not send direct messages to the Isomera team in Discord or Isomera Chat, they will not be able to respond. +Note: Do not send direct messages to the Isomera team in Discord or Isomera +Chat, they will not be able to respond. -Note: if you are hired for an Upwork job and have any job-specific questions, please ask in the GitHub issue or pull request. This will ensure that the person addressing your question has as much context as possible. +Note: if you are hired for an Upwork job and have any job-specific questions, +please ask in the GitHub issue or pull request. This will ensure that the person +addressing your question has as much context as possible. ## Code of Conduct -This project and everyone participating in it is governed by the [Isomera Code of Conduct](https://github.com/cortip/isomera/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to contributors@isomera.com. +This project and everyone participating in it is governed by the +[Isomera Code of Conduct](https://github.com/cortip/isomera/blob/main/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report +unacceptable behavior to contributors@isomera.com. ## Restrictions -At this time, we are not hiring contractors in Crimea, North Korea, Russia, Iran, Cuba, or Syria. +At this time, we are not hiring contractors in Crimea, North Korea, Russia, +Iran, Cuba, or Syria. ## Payment for Contributions -> Please note, that normally we invite to contribute enthusiasts for free, but also, sometimes to make things move faster we take some paid help from Upwork. This section does not mean that all contributions are paid. - -> Important: we are building a website to showcase Isomera contributor profiles. Contributors who contributed as contractors (hired) will not be listed there. - -We hire and pay external contributors via Upwork.com. If you'd like to be paid for contributing, please create an Upwork account, apply for an [available job in GitHub](https://github.com/cortip/isomera/labels/help%20wanted), and finally apply for the job in Upwork once your proposal gets selected in GitHub. Please make sure your Upwork profile is fully verified before applying, otherwise, you run the risk of not being paid. If you think your compensation should be increased for a specific job, you can request a reevaluation by commenting on the GitHub issue where the Upwork job was posted. - -Payment for your contributions will be made no less than 7 days after the pull request is deployed to production to allow for regression testing. If you have not received payment after 8 days of the PR being deployed to production, and there are no regressions, please add a comment to the issue mentioning the vygandas team member. - -New contributors are limited to working on one job at a time, however experienced contributors may work on numerous jobs simultaneously. - -Please be aware that compensation for any support in solving an issue is provided entirely at Isomeraโ€™s discretion. Personal time or resources applied towards investigating a proposal will not guarantee compensation. Compensation is only guaranteed to those who propose a solution and get hired for that job. We understand there may be cases where a selected proposal may take inspiration from a previous proposal. Unfortunately, itโ€™s not possible for us to evaluate every individual case and we have no process that can efficiently do so. Issues with higher rewards come with higher risk factors so try to keep things civil and make the best proposal you can. Once again, any information provided may not necessarily lead to you getting hired for that issue or compensated in any way. - -Important: Payment amounts are variable, dependent on if there are any regressions. Your PR will be reviewed by a Contributor+ (C+) team member and an internal engineer. All tests must pass and all code must pass lint checks before a merge. +> Please note, that normally we invite to contribute enthusiasts for free, but +> also, sometimes to make things move faster we take some paid help from Upwork. +> This section does not mean that all contributions are paid. + +> Important: we are building a website to showcase Isomera contributor profiles. +> Contributors who contributed as contractors (hired) will not be listed there. + +We hire and pay external contributors via Upwork.com. If you'd like to be paid +for contributing, please create an Upwork account, apply for an +[available job in GitHub](https://github.com/cortip/isomera/labels/help%20wanted), +and finally apply for the job in Upwork once your proposal gets selected in +GitHub. Please make sure your Upwork profile is fully verified before applying, +otherwise, you run the risk of not being paid. If you think your compensation +should be increased for a specific job, you can request a reevaluation by +commenting on the GitHub issue where the Upwork job was posted. + +Payment for your contributions will be made no less than 7 days after the pull +request is deployed to production to allow for regression testing. If you have +not received payment after 8 days of the PR being deployed to production, and +there are no regressions, please add a comment to the issue mentioning the +vygandas team member. + +New contributors are limited to working on one job at a time, however +experienced contributors may work on numerous jobs simultaneously. + +Please be aware that compensation for any support in solving an issue is +provided entirely at Isomeraโ€™s discretion. Personal time or resources applied +towards investigating a proposal will not guarantee compensation. Compensation +is only guaranteed to those who propose a solution and get hired for that job. +We understand there may be cases where a selected proposal may take inspiration +from a previous proposal. Unfortunately, itโ€™s not possible for us to evaluate +every individual case and we have no process that can efficiently do so. Issues +with higher rewards come with higher risk factors so try to keep things civil +and make the best proposal you can. Once again, any information provided may not +necessarily lead to you getting hired for that issue or compensated in any way. + +Important: Payment amounts are variable, dependent on if there are any +regressions. Your PR will be reviewed by a Contributor+ (C+) team member and an +internal engineer. All tests must pass and all code must pass lint checks before +a merge. ### Regressions -If a PR causes a regression at any point within the regression period (starting when the code is merged and ending 168 hours (that's 7 days) after being deployed to production): - -- payments will be issued 7 days after all regressions are fixed (ie: deployed to production) -- a 50% penalty will be applied to the Contributor and Contributor+ for each regression on an issue - -The 168 hours (aka 7 days) will be measured by calculating the time between when the PR is merged. - - - - - - - - +If a PR causes a regression at any point within the regression period (starting +when the code is merged and ending 168 hours (that's 7 days) after being +deployed to production): +- payments will be issued 7 days after all regressions are fixed (ie: deployed + to production) +- a 50% penalty will be applied to the Contributor and Contributor+ for each + regression on an issue +The 168 hours (aka 7 days) will be measured by calculating the time between when +the PR is merged. diff --git a/README.md b/README.md index 1c3e055..babd8ab 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ all-encompassing Javascript/Typescript starter boilerplate monorepo. Designed for visionary developers, ISOMERA streamlines the creation of enterprise-grade applications by providing a robust, modular, and scalable foundation. Whether you're kickstarting a project with React, Next, Nest, or diving deep into -TypeORM, React Query, and Material UI, our platform is your ticket to efficiency. -Beyond a mere boilerplate, ISOMERA offers comprehensive documentation, training -materials, and a community-driven approach. Contribute to the revolution, join a -community of like-minded developers, and redefine the standards of SaaS -development. +TypeORM, React Query, and Material UI, our platform is your ticket to +efficiency. Beyond a mere boilerplate, ISOMERA offers comprehensive +documentation, training materials, and a community-driven approach. Contribute +to the revolution, join a community of like-minded developers, and redefine the +standards of SaaS development. Every developer understands the journey: starting with basic functionalities and, over time, integrating advanced features like 2FA, role management, or From 6577927ba88f51db241877761e71e89c3c39d870 Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Thu, 7 Dec 2023 13:24:56 +0200 Subject: [PATCH 09/11] #69 Sign up & sign in with email+password (#122) * tiny dummy change * password reset confirmation step * now can register! * use Swagger ApiProperty on DTOs and keep using DTOs in frontend without epic crash --- .../mailer/templates/email-confirmation.hbs | 9 ++ .../mailer/templates/password-reset-code.hbs | 9 ++ .../templates/mailer/templates/welcome.hbs | 13 +++ apps/api/nest-cli.json | 11 ++- apps/api/src/auth/auth.controller.ts | 26 +++++- apps/api/src/auth/auth.service.ts | 52 ++++++++--- apps/api/src/entities/confirm-code.entity.ts | 9 +- apps/api/src/entities/user.entity.ts | 5 + apps/api/src/mailer/mailer.module.ts | 20 ++-- apps/api/src/mailer/mailer.service.ts | 1 + apps/api/src/mailer/templates/welcome.hbs | 4 +- ...6992176-create-confirmation-codes-table.ts | 48 ++++++++++ ...foreign-key-users-to-confirmation-codes.ts | 26 ++++++ apps/api/src/user/confirm-code.service.ts | 9 +- apps/api/src/user/user.service.ts | 10 ++ apps/platform/ApiProperty.override.ts | 10 ++ apps/platform/src/router/router.tsx | 16 +++- .../views/auth/passwordResetConfirm.view.tsx | 55 +++++++++++ apps/platform/src/views/auth/signUp.view.tsx | 93 ++++++++++++++++++- apps/platform/webpack.config.js | 5 + .../src/auth/request/ConfirmationCode.dto.ts | 3 + .../request/ForgotPasswordResetRequest.dto.ts | 2 + .../auth/request/ResetPasswordRequest.dto.ts | 5 +- .../request/SignInWithEmailCredentials.dto.ts | 3 + .../request/SignUpWithEmailCredentials.dto.ts | 19 +++- libs/dtos/src/utils/formik.validate.ts | 2 +- libs/impl/src/constants/apiRoutes.ts | 3 +- libs/impl/src/constants/pages.ts | 3 + .../auth/usePasswordResetPerform.hook.ts | 19 ++++ .../auth/usePasswordResetPerformForm.hook.ts | 61 ++++++++++++ .../auth/usePasswordResetRequest.hook.ts | 2 +- .../auth/usePasswordResetRequestForm.hook.ts | 4 + libs/impl/src/hooks/auth/useSignUp.hook.ts | 21 +++++ .../impl/src/hooks/auth/useSignUpForm.hook.ts | 58 ++++++++++++ libs/impl/src/hooks/index.ts | 4 + .../auth/passwordResetPerform.service.ts | 18 ++++ ...ice.ts => passwordResetRequest.service.ts} | 4 +- libs/impl/src/services/auth/signUp.service.ts | 17 ++++ .../src/auth/confirmationCode.interface.ts | 13 +++ .../auth/passwordResetPerform.interface.ts | 5 + .../auth/passwordResetRequest.interface.ts | 5 + .../src/auth/signUpResponse.interface.ts | 5 + libs/interfaces/src/generic/Status.type.ts | 4 + libs/interfaces/src/index.ts | 5 + libs/interfaces/src/user/User.interface.ts | 3 + package.json | 1 + 46 files changed, 672 insertions(+), 48 deletions(-) create mode 100644 apps/api/mailer/templates/mailer/templates/email-confirmation.hbs create mode 100644 apps/api/mailer/templates/mailer/templates/password-reset-code.hbs create mode 100644 apps/api/mailer/templates/mailer/templates/welcome.hbs create mode 100644 apps/api/src/migrations/1701936992176-create-confirmation-codes-table.ts create mode 100644 apps/api/src/migrations/1701937743981-add-foreign-key-users-to-confirmation-codes.ts create mode 100644 apps/platform/ApiProperty.override.ts create mode 100644 apps/platform/src/views/auth/passwordResetConfirm.view.tsx create mode 100644 libs/impl/src/hooks/auth/usePasswordResetPerform.hook.ts create mode 100644 libs/impl/src/hooks/auth/usePasswordResetPerformForm.hook.ts create mode 100644 libs/impl/src/hooks/auth/useSignUp.hook.ts create mode 100644 libs/impl/src/hooks/auth/useSignUpForm.hook.ts create mode 100644 libs/impl/src/services/auth/passwordResetPerform.service.ts rename libs/impl/src/services/auth/{passwordReset.service.ts => passwordResetRequest.service.ts} (77%) create mode 100644 libs/impl/src/services/auth/signUp.service.ts create mode 100644 libs/interfaces/src/auth/confirmationCode.interface.ts create mode 100644 libs/interfaces/src/auth/passwordResetPerform.interface.ts create mode 100644 libs/interfaces/src/auth/passwordResetRequest.interface.ts create mode 100644 libs/interfaces/src/auth/signUpResponse.interface.ts create mode 100644 libs/interfaces/src/generic/Status.type.ts diff --git a/apps/api/mailer/templates/mailer/templates/email-confirmation.hbs b/apps/api/mailer/templates/mailer/templates/email-confirmation.hbs new file mode 100644 index 0000000..0802c59 --- /dev/null +++ b/apps/api/mailer/templates/mailer/templates/email-confirmation.hbs @@ -0,0 +1,9 @@ +

+

Hello {{name}}

+ +

+ {{code}} +

+ +

If you did not request this email you can safely ignore it.

+
\ No newline at end of file diff --git a/apps/api/mailer/templates/mailer/templates/password-reset-code.hbs b/apps/api/mailer/templates/mailer/templates/password-reset-code.hbs new file mode 100644 index 0000000..22758fe --- /dev/null +++ b/apps/api/mailer/templates/mailer/templates/password-reset-code.hbs @@ -0,0 +1,9 @@ +
+

Hello {{name}}

+ +

+ {{code}} +

+ +

If you did not request this email you can safely ignore it.

+
\ No newline at end of file diff --git a/apps/api/mailer/templates/mailer/templates/welcome.hbs b/apps/api/mailer/templates/mailer/templates/welcome.hbs new file mode 100644 index 0000000..1c41e91 --- /dev/null +++ b/apps/api/mailer/templates/mailer/templates/welcome.hbs @@ -0,0 +1,13 @@ +
Dear, {{user.firstName}}
, + +

+ Welcome to Isomera, a SaaS starter! +
+ Kind regards, +
+ Isomera team +
+ hi@isomera.com +
+ https://isomera.com +

\ No newline at end of file diff --git a/apps/api/nest-cli.json b/apps/api/nest-cli.json index e79bf94..29202da 100644 --- a/apps/api/nest-cli.json +++ b/apps/api/nest-cli.json @@ -3,7 +3,16 @@ "collection": "@nestjs/schematics", "sourceRoot": "apps/api/src", "compilerOptions": { - "tsConfigPath": "apps/api/tsconfig.app.json" + "tsConfigPath": "apps/api/tsconfig.app.json", + "assets": [ + { + "include": "**/*.hbs", + "watchAssets": true, + "outDir": "dist/apps/api/src" + }, + { "include": "**/*.txt" } + ], + "plugins": ["@nestjs/swagger/plugin"] }, "entryFile": "../dist/apps/api/src/main" } diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 56b0b13..0ac9c2b 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -14,6 +14,9 @@ import { AuthUser } from '../user/user.decorator' import { UserEntity } from '../entities/user.entity' import { AuthService } from './auth.service' import { + ConfirmationCodeDto, + ForgotPasswordResetRequestDto, + ResetPasswordRequestDto, SignInWithEmailCredentialsDto, SignUpWithEmailCredentialsDto } from '@isomera/dtos' @@ -22,9 +25,12 @@ import { LocalAuthGuard } from './guards/local-auth.guard' import { SessionAuthGuard } from './guards/session-auth.guard' import { TokenInterceptor } from './interceptors/token.interceptor' import { ConfirmCodeService } from '../user/confirm-code.service' -import { Pure } from '@isomera/interfaces' -import { ConfirmationCodeDto } from '@isomera/dtos' -import { ForgotPasswordResetRequestDto } from '@isomera/dtos' +import { + PasswordResetPerformInterface, + PasswordResetRequestInterface, + Pure, + StatusType +} from '@isomera/interfaces' @Controller('auth') export class AuthController { @@ -33,6 +39,7 @@ export class AuthController { private readonly confirmCodeService: ConfirmCodeService ) {} + // @Post('register') @HttpCode(HttpStatus.CREATED) @UseInterceptors(TokenInterceptor) @@ -79,8 +86,17 @@ export class AuthController { @HttpCode(HttpStatus.OK) async requestPasswordReset( @Body() body: Pure - ): Promise<{ status: string }> { + ): Promise { await this.authService.requestPasswordReset(body.email) - return { status: 'ok' } + return { status: StatusType.OK } + } + + @Post('/request-password-reset/confirm') + @HttpCode(HttpStatus.OK) + async resetPassword( + @Body() body: Pure + ): Promise { + const result = await this.authService.setNewPassword(body) + return { status: result ? StatusType.OK : StatusType.FAIL } } } diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index ee04b7e..f1b9f20 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -7,7 +7,10 @@ import { import { JwtService } from '@nestjs/jwt' import { UserEntity } from '../entities/user.entity' -import { SignUpWithEmailCredentialsDto } from '@isomera/dtos' +import { + ResetPasswordRequestDto, + SignUpWithEmailCredentialsDto +} from '@isomera/dtos' import { JwtPayload } from '@isomera/interfaces' import { UserService } from '../user/user.service' import { MailerService } from '../mailer/mailer.service' @@ -102,20 +105,45 @@ export class AuthService { return this.mailerService.sendEmail(user, 'Welcome!', 'welcome', { user }) } - async requestPasswordReset(email: string) { + async requestPasswordReset(email: string): Promise { + /** + * Do not fail if user is not found. We do not want people to know whether email they + * provided is actually registered or not. + */ + try { + const user: UserEntity = await this.userService.findOne({ + where: { email } + }) + if (user?.id) { + const passwordResetCode = generateRandomStringUtil(32) + await this.userService.setPasswordResetCode(user.id, passwordResetCode) + void this.mailerService.sendEmail( + user, + 'Password reset code', + 'password-reset-code', + { user, code: passwordResetCode } + ) + return true + } + } catch (e) { + return false + } + return false + } + + async setNewPassword( + resetPasswordRequestDto: Pure + ): Promise { const user: UserEntity = await this.userService.findOne({ - where: { email } + where: { passwordResetCode: resetPasswordRequestDto.passwordResetCode } }) - if (user) { - //passwordResetCode - const passwordResetCode = generateRandomStringUtil(32) - await this.userService.setPasswordResetCode(user.id, passwordResetCode) - void this.mailerService.sendEmail( - user, - 'Password reset code', - 'password-reset-code', - { user, code: passwordResetCode } + if (user?.id) { + const updateResult = await this.userService.setNewPassword( + user.id, + resetPasswordRequestDto.newPassword ) + return updateResult.affected > 0 } + return false } } diff --git a/apps/api/src/entities/confirm-code.entity.ts b/apps/api/src/entities/confirm-code.entity.ts index d1ac7e3..fa8fc6d 100644 --- a/apps/api/src/entities/confirm-code.entity.ts +++ b/apps/api/src/entities/confirm-code.entity.ts @@ -2,14 +2,14 @@ import { Column, CreateDateColumn, Entity, - JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm' import { UserEntity } from './user.entity' +import { ConfirmationCodeInterface } from '@isomera/interfaces' @Entity('confirmation-codes') -export class ConfirmCodeEntity { +export class ConfirmCodeEntity implements ConfirmationCodeInterface { @PrimaryGeneratedColumn() id: number @@ -19,10 +19,9 @@ export class ConfirmCodeEntity { @CreateDateColumn() createdAt: Date - @Column({ type: 'time', comment: 'Limit time to use this code' }) + @Column({ type: 'timestamp', comment: 'Limit time to use this code' }) expiresIn: Date - @ManyToOne(() => UserEntity) - @JoinColumn() + @ManyToOne(() => UserEntity, user => user.id) user: UserEntity } diff --git a/apps/api/src/entities/user.entity.ts b/apps/api/src/entities/user.entity.ts index 6f83b96..db4e780 100644 --- a/apps/api/src/entities/user.entity.ts +++ b/apps/api/src/entities/user.entity.ts @@ -4,11 +4,13 @@ import { Column, CreateDateColumn, Entity, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' import { UserInterface } from '@isomera/interfaces' import bcrypt from 'bcryptjs' +import { ConfirmCodeEntity } from './confirm-code.entity' @Entity({ name: 'users' }) export class UserEntity implements UserInterface { @@ -45,6 +47,9 @@ export class UserEntity implements UserInterface { @Column() passwordResetCode: string | null + @OneToMany(() => ConfirmCodeEntity, confirmCode => confirmCode.user) + confirmationCodes: ConfirmCodeEntity[] + @BeforeInsert() @BeforeUpdate() async hashPassword(): Promise { diff --git a/apps/api/src/mailer/mailer.module.ts b/apps/api/src/mailer/mailer.module.ts index bae31da..058ca04 100644 --- a/apps/api/src/mailer/mailer.module.ts +++ b/apps/api/src/mailer/mailer.module.ts @@ -3,6 +3,9 @@ import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handleba import { Module } from '@nestjs/common' import { MailerService } from './mailer.service' import { ConfigModule, ConfigService } from '@nestjs/config' +import { config as dotenvConfig } from 'dotenv' + +dotenvConfig({ path: '.env' }) @Module({ imports: [ @@ -11,19 +14,22 @@ import { ConfigModule, ConfigService } from '@nestjs/config' inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ transport: { - host: configService.get('MAIL_HOST'), + host: configService.get('MAIL_HOST', 'localhost'), + port: configService.get('MAIL_PORT', 1025), secure: false, - auth: { - user: configService.get('MAIL_USER'), - pass: configService.get('MAIL_PASSWORD') - }, - port: configService.get('MAIL_PORT') + ...(configService.get('MAIL_USER') && { + auth: { + type: 'login', + user: configService.get('MAIL_USER'), + pass: configService.get('MAIL_PASSWORD') + } + }) }, defaults: { from: '"No Reply" ' }, template: { - dir: __dirname + '/mailer' + '/templates', + dir: __dirname + '/templates', adapter: new HandlebarsAdapter(), options: { strict: true diff --git a/apps/api/src/mailer/mailer.service.ts b/apps/api/src/mailer/mailer.service.ts index dcde879..d7152b7 100644 --- a/apps/api/src/mailer/mailer.service.ts +++ b/apps/api/src/mailer/mailer.service.ts @@ -17,6 +17,7 @@ export class MailerService { } }) } catch (err) { + console.error(err) throw new HttpException( 'Email could not be sent', HttpStatus.INTERNAL_SERVER_ERROR diff --git a/apps/api/src/mailer/templates/welcome.hbs b/apps/api/src/mailer/templates/welcome.hbs index c54b6d9..8d27dd8 100644 --- a/apps/api/src/mailer/templates/welcome.hbs +++ b/apps/api/src/mailer/templates/welcome.hbs @@ -1,7 +1,7 @@
Dear, {{user.firstName}}
,

- Welcome to Isomera, a SaaS starter! + Welcome to Isomera, a SaaS starter!!!
Kind regards,
@@ -9,5 +9,5 @@
hi@isomera.com
- https://www.isomera.com + https://isomera.com

\ No newline at end of file diff --git a/apps/api/src/migrations/1701936992176-create-confirmation-codes-table.ts b/apps/api/src/migrations/1701936992176-create-confirmation-codes-table.ts new file mode 100644 index 0000000..61aa15f --- /dev/null +++ b/apps/api/src/migrations/1701936992176-create-confirmation-codes-table.ts @@ -0,0 +1,48 @@ +import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm' + +export class CreateConfirmationCodesTable1701936992176 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: 'confirmation-codes', + columns: [ + new TableColumn({ + name: 'id', + type: 'int', + isPrimary: true, + isGenerated: true, + generationStrategy: 'increment' + }), + new TableColumn({ + name: 'code', + type: 'VARCHAR(7)', + isNullable: true, + default: null + }), + new TableColumn({ + name: 'createdAt', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + isNullable: false + }), + new TableColumn({ + name: 'expiresIn', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + isNullable: false + }), + new TableColumn({ + name: 'userId', + type: 'int' + }) + ] + }) + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable('confirmation-codes') + } +} diff --git a/apps/api/src/migrations/1701937743981-add-foreign-key-users-to-confirmation-codes.ts b/apps/api/src/migrations/1701937743981-add-foreign-key-users-to-confirmation-codes.ts new file mode 100644 index 0000000..9b8493d --- /dev/null +++ b/apps/api/src/migrations/1701937743981-add-foreign-key-users-to-confirmation-codes.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner, TableForeignKey } from 'typeorm' + +export class AddForeignKeyUsersToConfirmationCodes1701937743981 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createForeignKey( + 'confirmation-codes', + new TableForeignKey({ + name: 'FK_users_of_confirmation_codes', + columnNames: ['userId'], + referencedColumnNames: ['id'], + referencedTableName: 'users', + onDelete: 'CASCADE' + }) + ) + } + + public async down(queryRunner: QueryRunner): Promise { + const table = await queryRunner.getTable('confirmation-codes') + const foreignKey = table.foreignKeys.find( + fk => fk.columnNames.indexOf('userId') !== -1 + ) + await queryRunner.dropForeignKey('confirmation-codes', foreignKey) + } +} diff --git a/apps/api/src/user/confirm-code.service.ts b/apps/api/src/user/confirm-code.service.ts index 51ff8d2..f06434d 100644 --- a/apps/api/src/user/confirm-code.service.ts +++ b/apps/api/src/user/confirm-code.service.ts @@ -1,10 +1,11 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common' import { InjectRepository } from '@nestjs/typeorm' import { MoreThan, Repository } from 'typeorm' -import { generateRandomNumber } from '@isomera/utils' +import { generateRandomStringUtil } from '@isomera/utils' import { format } from 'date-fns' import { ConfirmCodeEntity } from '../entities/confirm-code.entity' import { UserEntity } from '../entities/user.entity' +import { DateTime } from 'luxon' @Injectable() export class ConfirmCodeService { @@ -18,13 +19,13 @@ export class ConfirmCodeService { public async genNewCode(user: UserEntity): Promise { await this.invalidateOlderCodes(user) // If there are other codes, we want to invalidate them - const code = generateRandomNumber(7).toString() + const code = generateRandomStringUtil(7) const createCode = new ConfirmCodeEntity() createCode.code = code createCode.user = user - createCode.expiresIn = new Date(new Date().getTime() + 1000 * 60 * 30) // Half hour - + createCode.expiresIn = DateTime.now().plus({ minute: 30 }).toJSDate() + console.log('xxx createCode.expiresIn', createCode.expiresIn) await this.confirmCodeRepository.save(createCode) return createCode diff --git a/apps/api/src/user/user.service.ts b/apps/api/src/user/user.service.ts index ba1763a..28c3288 100644 --- a/apps/api/src/user/user.service.ts +++ b/apps/api/src/user/user.service.ts @@ -55,4 +55,14 @@ export class UserService { return await this.userRepository.update({ id }, { passwordResetCode }) } + + async setNewPassword(id: number, password: string): Promise { + const user = await this.userRepository.findOneBy({ id }) + + if (!user) { + throw new NotFoundException(`There isn't any user with id: ${id}`) + } + + return await this.userRepository.update({ id }, { password }) + } } diff --git a/apps/platform/ApiProperty.override.ts b/apps/platform/ApiProperty.override.ts new file mode 100644 index 0000000..bbb75d3 --- /dev/null +++ b/apps/platform/ApiProperty.override.ts @@ -0,0 +1,10 @@ +/** + * Using Nest.js @ApiProperty() annotations on DTOs causes issues on front-end + * focused projects like this, so we are aliasing this annotator to a dummy empty + * function. + */ +export const ApiProperty = () => { + return () => { + // + } +} diff --git a/apps/platform/src/router/router.tsx b/apps/platform/src/router/router.tsx index ff4211a..045f6e4 100644 --- a/apps/platform/src/router/router.tsx +++ b/apps/platform/src/router/router.tsx @@ -4,6 +4,7 @@ import { SignInView } from '../views/auth/signIn.view' import { SignUpView } from '../views/auth/signUp.view' import { pages } from '@isomera/impl' import { PasswordResetView } from '../views/auth/passwordReset.view' +import { PasswordResetConfirmView } from '../views/auth/passwordResetConfirm.view' export function Router() { return ( @@ -15,9 +16,7 @@ export function Router() {
Sign Up - - Forgot password - + Forgot password
} /> @@ -31,7 +30,7 @@ export function Router() { } /> @@ -39,6 +38,15 @@ export function Router() { } /> + + + Sign In + + } + /> ) diff --git a/apps/platform/src/views/auth/passwordResetConfirm.view.tsx b/apps/platform/src/views/auth/passwordResetConfirm.view.tsx new file mode 100644 index 0000000..7a9c129 --- /dev/null +++ b/apps/platform/src/views/auth/passwordResetConfirm.view.tsx @@ -0,0 +1,55 @@ +import { usePasswordResetPerformForm } from '@isomera/impl' + +export const PasswordResetConfirmView = () => { + const { + values, + handleChange, + handleBlur, + errors, + touched, + handleSubmit, + isSubmitting + } = usePasswordResetPerformForm() + + return ( +
+
+ + + {touched.newPassword && errors.newPassword && ( + {errors.newPassword} + )} +
+
+ + + {touched.passwordResetCode && errors.passwordResetCode && ( + {errors.passwordResetCode} + )} +
+
+ +
+
+ ) +} diff --git a/apps/platform/src/views/auth/signUp.view.tsx b/apps/platform/src/views/auth/signUp.view.tsx index d99e729..b1890b9 100644 --- a/apps/platform/src/views/auth/signUp.view.tsx +++ b/apps/platform/src/views/auth/signUp.view.tsx @@ -1,3 +1,94 @@ +import { useSignUpFormHook } from '@isomera/impl' + export const SignUpView = () => { - return
...
+ const { + values, + handleChange, + handleBlur, + errors, + touched, + handleSubmit, + isSubmitting + } = useSignUpFormHook() + console.log('val', values) + return ( +
+
+ + + {touched.firstName && errors.firstName && ( + {errors.firstName} + )} +
+
+ + + {touched.lastName && errors.lastName && {errors.lastName}} +
+
+ + + {touched.email && errors.email && {errors.email}} +
+
+ + + {touched.password && errors.password && {errors.password}} +
+
+ + + {touched.isPrivacyPolicyAccepted && errors.isPrivacyPolicyAccepted && ( + {errors.isPrivacyPolicyAccepted} + )} +
+
+ +
+
+ ) } diff --git a/apps/platform/webpack.config.js b/apps/platform/webpack.config.js index 1d90865..04fc6cd 100644 --- a/apps/platform/webpack.config.js +++ b/apps/platform/webpack.config.js @@ -1,9 +1,14 @@ const { composePlugins, withNx } = require('@nx/webpack') const { withReact } = require('@nx/react') +const path = require('path') // Nx plugins for webpack. module.exports = composePlugins(withNx(), withReact(), config => { // Update the webpack config as needed here. // e.g. `config.plugins.push(new MyPlugin())` + config.resolve.alias['@nestjs/swagger'] = path.resolve( + __dirname, + 'ApiProperty.override.ts' + ) return config }) diff --git a/libs/dtos/src/auth/request/ConfirmationCode.dto.ts b/libs/dtos/src/auth/request/ConfirmationCode.dto.ts index 5046255..57ae933 100644 --- a/libs/dtos/src/auth/request/ConfirmationCode.dto.ts +++ b/libs/dtos/src/auth/request/ConfirmationCode.dto.ts @@ -1,10 +1,13 @@ import { IsNotEmpty, IsString } from 'class-validator' +import { ApiProperty } from '@nestjs/swagger' export class ConfirmationCodeDto { + @ApiProperty() @IsString() @IsNotEmpty() public code?: string + @ApiProperty() @IsString() @IsNotEmpty() public email?: string diff --git a/libs/dtos/src/auth/request/ForgotPasswordResetRequest.dto.ts b/libs/dtos/src/auth/request/ForgotPasswordResetRequest.dto.ts index 55d3670..7b7956c 100644 --- a/libs/dtos/src/auth/request/ForgotPasswordResetRequest.dto.ts +++ b/libs/dtos/src/auth/request/ForgotPasswordResetRequest.dto.ts @@ -1,5 +1,6 @@ import { IsEmail } from 'class-validator' import { ValidateableDto } from '../../generics/Validateable.dto' +import { ApiProperty } from '@nestjs/swagger' /** * @openapi @@ -16,6 +17,7 @@ import { ValidateableDto } from '../../generics/Validateable.dto' * email: john@doe.com */ export class ForgotPasswordResetRequestDto extends ValidateableDto { + @ApiProperty() @IsEmail() email!: string } diff --git a/libs/dtos/src/auth/request/ResetPasswordRequest.dto.ts b/libs/dtos/src/auth/request/ResetPasswordRequest.dto.ts index 580ce26..39cd78f 100644 --- a/libs/dtos/src/auth/request/ResetPasswordRequest.dto.ts +++ b/libs/dtos/src/auth/request/ResetPasswordRequest.dto.ts @@ -1,6 +1,7 @@ import { Length, IsStrongPassword } from 'class-validator' import { authConfig } from '../../../../../config/auth.config' import { ValidateableDto } from '../../generics/Validateable.dto' +import { ApiProperty } from '@nestjs/swagger' /** * @openapi @@ -18,9 +19,11 @@ import { ValidateableDto } from '../../generics/Validateable.dto' * type: string */ export class ResetPasswordRequestDto extends ValidateableDto { + @ApiProperty() @IsStrongPassword(authConfig.isStrongPasswordOptions) newPassword!: string + @ApiProperty() @Length(32) - resetToken!: string + passwordResetCode!: string } diff --git a/libs/dtos/src/auth/request/SignInWithEmailCredentials.dto.ts b/libs/dtos/src/auth/request/SignInWithEmailCredentials.dto.ts index 5d03061..fa7e699 100644 --- a/libs/dtos/src/auth/request/SignInWithEmailCredentials.dto.ts +++ b/libs/dtos/src/auth/request/SignInWithEmailCredentials.dto.ts @@ -2,14 +2,17 @@ import { IsEmail, IsStrongPassword } from 'class-validator' import { ValidateableDto } from '../../generics/Validateable.dto' import { authConfig } from '../../../../../config/auth.config' import { UserInterface } from '@isomera/interfaces' +import { ApiProperty } from '@nestjs/swagger' export class SignInWithEmailCredentialsDto extends ValidateableDto implements Partial { + @ApiProperty() @IsEmail() email?: string + @ApiProperty() @IsStrongPassword(authConfig.isStrongPasswordOptions) password?: string } diff --git a/libs/dtos/src/auth/request/SignUpWithEmailCredentials.dto.ts b/libs/dtos/src/auth/request/SignUpWithEmailCredentials.dto.ts index 696b43f..4469e04 100644 --- a/libs/dtos/src/auth/request/SignUpWithEmailCredentials.dto.ts +++ b/libs/dtos/src/auth/request/SignUpWithEmailCredentials.dto.ts @@ -1,24 +1,41 @@ -import { IsBoolean, IsEmail, IsString, IsStrongPassword } from 'class-validator' +import { + IsBoolean, + IsEmail, + IsString, + IsStrongPassword, + IsNotEmpty, + Equals +} from 'class-validator' import { ValidateableDto } from '../../generics/Validateable.dto' import { authConfig } from '../../../../../config/auth.config' import { UserInterface } from '@isomera/interfaces' +import { ApiProperty } from '@nestjs/swagger' export class SignUpWithEmailCredentialsDto extends ValidateableDto implements Partial { + @ApiProperty() @IsString() firstName?: string + @ApiProperty() @IsString() lastName?: string + @ApiProperty() @IsEmail() email?: string + @ApiProperty() @IsStrongPassword(authConfig.isStrongPasswordOptions) password?: string + @ApiProperty() + @IsNotEmpty() @IsBoolean() + @Equals(true, { + message: 'You mut read and accept our Privacy Policy terms' + }) isPrivacyPolicyAccepted?: boolean } diff --git a/libs/dtos/src/utils/formik.validate.ts b/libs/dtos/src/utils/formik.validate.ts index ca3388e..4261767 100644 --- a/libs/dtos/src/utils/formik.validate.ts +++ b/libs/dtos/src/utils/formik.validate.ts @@ -6,7 +6,7 @@ import { getEnumKeyByEnumValue, pascalToSnakeCase } from '@isomera/utils' export type Ret = { [key: string]: string | Ret } export type Class = { new (...args: any[]): any } -export type Data = { [key: string]: string | number } +export type Data = { [key: string]: string | number | boolean } export const formikValidate = (model: Class, data: Data) => { console.log({ model, data }) diff --git a/libs/impl/src/constants/apiRoutes.ts b/libs/impl/src/constants/apiRoutes.ts index cb92530..141a0ee 100644 --- a/libs/impl/src/constants/apiRoutes.ts +++ b/libs/impl/src/constants/apiRoutes.ts @@ -7,9 +7,10 @@ // Authentication export const API_LOGIN_ROUTE = '/auth/login' - export const API_FORGOT_PASSWORD_REQUEST_RESET_ROUTE = '/auth/request-password-reset' +export const API_FORGOT_PASSWORD_PERFORM_RESET_ROUTE = + '/request-password-reset/confirm' export const API_REGISTER_ROUTE = '/auth/register' export const API_CONFIRM_ROUTE = '/auth/confirm' export const API_LOGOUT_ROUTE = '/auth/logout' diff --git a/libs/impl/src/constants/pages.ts b/libs/impl/src/constants/pages.ts index 5447b58..b7efe66 100644 --- a/libs/impl/src/constants/pages.ts +++ b/libs/impl/src/constants/pages.ts @@ -11,6 +11,9 @@ export const pages = { verificationCode: { path: '/verification-code' }, + passwordResetRequest: { + path: '/reset-password' + }, passwordResetRequestConfirmation: { path: '/reset-password/confirm' }, diff --git a/libs/impl/src/hooks/auth/usePasswordResetPerform.hook.ts b/libs/impl/src/hooks/auth/usePasswordResetPerform.hook.ts new file mode 100644 index 0000000..a3689f5 --- /dev/null +++ b/libs/impl/src/hooks/auth/usePasswordResetPerform.hook.ts @@ -0,0 +1,19 @@ +import { useMutation } from 'react-query' + +import { Pure } from '@isomera/interfaces' +import { ResetPasswordRequestDto } from '@isomera/dtos' +import { passwordResetPerformService } from '../../services/auth/passwordResetPerform.service' + +export const usePasswordResetPerformHook = () => { + const { mutateAsync: performReset, isLoading } = useMutation( + async (body: Pure) => { + const data = await passwordResetPerformService(body) + return data + } + ) + + return { + performReset, + isLoading + } +} diff --git a/libs/impl/src/hooks/auth/usePasswordResetPerformForm.hook.ts b/libs/impl/src/hooks/auth/usePasswordResetPerformForm.hook.ts new file mode 100644 index 0000000..7d97f7e --- /dev/null +++ b/libs/impl/src/hooks/auth/usePasswordResetPerformForm.hook.ts @@ -0,0 +1,61 @@ +import { useFormik } from 'formik' + +import { useNavigate } from 'react-router-dom' + +import { pages } from '../../constants/pages' +import { formikValidate, ResetPasswordRequestDto } from '@isomera/dtos' +import { useHandleErrorHook } from '../error/useHandleError.hook' +import { Pure, StatusType } from '@isomera/interfaces' +import { usePasswordResetPerformHook } from './usePasswordResetPerform.hook' +import { toast } from 'react-toastify' + +const initialValues: Pure = { + newPassword: '', + passwordResetCode: '' +} + +export const usePasswordResetPerformForm = () => { + const { performReset } = usePasswordResetPerformHook() + const { handleError } = useHandleErrorHook() + const navigate = useNavigate() + + const onSubmit = async (values: typeof initialValues) => { + try { + const result = await performReset(values) + if (result.status === StatusType.OK) { + toast.success( + 'Password changed successfully. Please log in with your new password.' + ) + } else { + toast.error('Password change failed. Please try again.') + } + navigate(pages.login.path) + } catch (error) { + handleError(error, { view: 'login' }) + } + } + + const { + values, + handleChange, + handleBlur, + errors, + touched, + handleSubmit, + isSubmitting + } = useFormik({ + initialValues, + validate: values => formikValidate(ResetPasswordRequestDto, values), + onSubmit + }) + + return { + values, + handleChange, + handleBlur, + errors, + touched, + handleSubmit, + isSubmitting + } +} diff --git a/libs/impl/src/hooks/auth/usePasswordResetRequest.hook.ts b/libs/impl/src/hooks/auth/usePasswordResetRequest.hook.ts index f79ea01..71d78ca 100644 --- a/libs/impl/src/hooks/auth/usePasswordResetRequest.hook.ts +++ b/libs/impl/src/hooks/auth/usePasswordResetRequest.hook.ts @@ -2,7 +2,7 @@ import { useMutation } from 'react-query' import { Pure } from '@isomera/interfaces' import { ForgotPasswordResetRequestDto } from '@isomera/dtos' -import { passwordResetRequestService } from '../../services/auth/passwordReset.service' +import { passwordResetRequestService } from '../../services/auth/passwordResetRequest.service' export const usePasswordResetRequestHook = () => { const { mutateAsync: requestReset, isLoading } = useMutation( diff --git a/libs/impl/src/hooks/auth/usePasswordResetRequestForm.hook.ts b/libs/impl/src/hooks/auth/usePasswordResetRequestForm.hook.ts index a55f1db..9b55d9a 100644 --- a/libs/impl/src/hooks/auth/usePasswordResetRequestForm.hook.ts +++ b/libs/impl/src/hooks/auth/usePasswordResetRequestForm.hook.ts @@ -8,6 +8,7 @@ import { useHandleErrorHook } from '../error/useHandleError.hook' import { Pure } from '@isomera/interfaces' import { usePasswordResetRequestHook } from './usePasswordResetRequest.hook' import { ForgotPasswordResetRequestDto } from '@isomera/dtos' +import { toast } from 'react-toastify' const initialValues: Pure = { email: '' @@ -21,6 +22,9 @@ export const usePasswordResetRequestForm = () => { const onSubmit = async (values: typeof initialValues) => { try { await requestReset(values) + toast.success( + `If there was such user registered with email ${values.email}, then you'll receive confirmation code.` + ) navigate(pages.passwordResetRequestConfirmation.path) } catch (error) { handleError(error, { view: 'login' }) diff --git a/libs/impl/src/hooks/auth/useSignUp.hook.ts b/libs/impl/src/hooks/auth/useSignUp.hook.ts new file mode 100644 index 0000000..09d9061 --- /dev/null +++ b/libs/impl/src/hooks/auth/useSignUp.hook.ts @@ -0,0 +1,21 @@ +import { useMutation } from 'react-query' + +import { SignUpWithEmailCredentialsDto } from '@isomera/dtos' + +import { Pure } from '@isomera/interfaces' +import { signUpService } from '../../services/auth/signUp.service' + +export const useSignUpHook = () => { + const { mutateAsync: register, isLoading } = useMutation( + async (body: Pure) => { + const data = await signUpService(body) + + return data?.status + } + ) + + return { + register, + isLoading + } +} diff --git a/libs/impl/src/hooks/auth/useSignUpForm.hook.ts b/libs/impl/src/hooks/auth/useSignUpForm.hook.ts new file mode 100644 index 0000000..c855af8 --- /dev/null +++ b/libs/impl/src/hooks/auth/useSignUpForm.hook.ts @@ -0,0 +1,58 @@ +import { useFormik } from 'formik' + +import { useNavigate } from 'react-router-dom' + +import { pages } from '../../constants/pages' +import { formikValidate, SignUpWithEmailCredentialsDto } from '@isomera/dtos' +import { useHandleErrorHook } from '../error/useHandleError.hook' +import { Pure } from '@isomera/interfaces' +import { useSignUpHook } from './useSignUp.hook' + +const initialValues: Pure = { + email: '', + password: '', + firstName: '', + lastName: '', + isPrivacyPolicyAccepted: undefined +} + +export const useSignUpFormHook = () => { + const { register } = useSignUpHook() + const { handleError } = useHandleErrorHook() + const navigate = useNavigate() + + const onSubmit = async (values: typeof initialValues) => { + try { + await register(values) + navigate(pages.login.path) + } catch (error) { + handleError(error, { view: 'login' }) + } + } + + const { + values, + handleChange, + handleBlur, + errors, + touched, + handleSubmit, + setFieldValue, + isSubmitting + } = useFormik({ + initialValues, + validate: values => formikValidate(SignUpWithEmailCredentialsDto, values), + onSubmit + }) + + return { + values, + handleChange, + handleBlur, + errors, + touched, + handleSubmit, + setFieldValue, + isSubmitting + } +} diff --git a/libs/impl/src/hooks/index.ts b/libs/impl/src/hooks/index.ts index 7cdfaf2..fde3c4c 100644 --- a/libs/impl/src/hooks/index.ts +++ b/libs/impl/src/hooks/index.ts @@ -2,5 +2,9 @@ export * from './auth/useSignIn.hook' export * from './auth/useSignInForm.hook' export * from './auth/usePasswordResetRequest.hook' export * from './auth/usePasswordResetRequestForm.hook' +export * from './auth/usePasswordResetPerform.hook' +export * from './auth/usePasswordResetPerformForm.hook' export * from './error/useHandleError.hook' export * from './user/useUser.hook' +export * from './auth/useSignUp.hook' +export * from './auth/useSignUpForm.hook' diff --git a/libs/impl/src/services/auth/passwordResetPerform.service.ts b/libs/impl/src/services/auth/passwordResetPerform.service.ts new file mode 100644 index 0000000..ff575db --- /dev/null +++ b/libs/impl/src/services/auth/passwordResetPerform.service.ts @@ -0,0 +1,18 @@ +import { API_FORGOT_PASSWORD_PERFORM_RESET_ROUTE } from '../../constants' +import { axiosInstance } from '../../utils' +import { getRoute } from '../../utils' +import { PasswordResetPerformInterface, Pure } from '@isomera/interfaces' +import { ResetPasswordRequestDto } from '@isomera/dtos' +import { AxiosResponse } from 'axios' + +export const passwordResetPerformService = async ( + body: Pure +) => { + const response: AxiosResponse = + await axiosInstance.post( + getRoute(API_FORGOT_PASSWORD_PERFORM_RESET_ROUTE), + body + ) + + return response?.data +} diff --git a/libs/impl/src/services/auth/passwordReset.service.ts b/libs/impl/src/services/auth/passwordResetRequest.service.ts similarity index 77% rename from libs/impl/src/services/auth/passwordReset.service.ts rename to libs/impl/src/services/auth/passwordResetRequest.service.ts index 6c6d915..290e9cf 100644 --- a/libs/impl/src/services/auth/passwordReset.service.ts +++ b/libs/impl/src/services/auth/passwordResetRequest.service.ts @@ -3,13 +3,13 @@ import { AxiosResponse } from 'axios' import { API_FORGOT_PASSWORD_REQUEST_RESET_ROUTE } from '../../constants' import { axiosInstance } from '../../utils' import { getRoute } from '../../utils' -import { Pure, SignInResponseInterface } from '@isomera/interfaces' +import { PasswordResetRequestInterface, Pure } from '@isomera/interfaces' import { ForgotPasswordResetRequestDto } from '@isomera/dtos' export const passwordResetRequestService = async ( body: Pure ) => { - const response: AxiosResponse = + const response: AxiosResponse = await axiosInstance.post( getRoute(API_FORGOT_PASSWORD_REQUEST_RESET_ROUTE), body diff --git a/libs/impl/src/services/auth/signUp.service.ts b/libs/impl/src/services/auth/signUp.service.ts new file mode 100644 index 0000000..d960335 --- /dev/null +++ b/libs/impl/src/services/auth/signUp.service.ts @@ -0,0 +1,17 @@ +import { AxiosResponse } from 'axios' + +import { SignUpWithEmailCredentialsDto } from '@isomera/dtos' + +import { API_REGISTER_ROUTE } from '../../constants' +import { axiosInstance } from '../../utils' +import { getRoute } from '../../utils' +import { Pure, SignUpResponseInterface } from '@isomera/interfaces' + +export const signUpService = async ( + body: Pure +) => { + const response: AxiosResponse = + await axiosInstance.post(getRoute(API_REGISTER_ROUTE), body) + + return response?.data +} diff --git a/libs/interfaces/src/auth/confirmationCode.interface.ts b/libs/interfaces/src/auth/confirmationCode.interface.ts new file mode 100644 index 0000000..e62ab85 --- /dev/null +++ b/libs/interfaces/src/auth/confirmationCode.interface.ts @@ -0,0 +1,13 @@ +import { UserInterface } from '../user/User.interface' + +export interface ConfirmationCodeInterface { + id?: number + + code: string + + createdAt: Date + + expiresIn: Date + + user: UserInterface +} diff --git a/libs/interfaces/src/auth/passwordResetPerform.interface.ts b/libs/interfaces/src/auth/passwordResetPerform.interface.ts new file mode 100644 index 0000000..361479f --- /dev/null +++ b/libs/interfaces/src/auth/passwordResetPerform.interface.ts @@ -0,0 +1,5 @@ +import { StatusType } from '../generic/Status.type' + +export interface PasswordResetPerformInterface { + status: StatusType +} diff --git a/libs/interfaces/src/auth/passwordResetRequest.interface.ts b/libs/interfaces/src/auth/passwordResetRequest.interface.ts new file mode 100644 index 0000000..641e514 --- /dev/null +++ b/libs/interfaces/src/auth/passwordResetRequest.interface.ts @@ -0,0 +1,5 @@ +import { StatusType } from '../generic/Status.type' + +export interface PasswordResetRequestInterface { + status: StatusType +} diff --git a/libs/interfaces/src/auth/signUpResponse.interface.ts b/libs/interfaces/src/auth/signUpResponse.interface.ts new file mode 100644 index 0000000..70ff763 --- /dev/null +++ b/libs/interfaces/src/auth/signUpResponse.interface.ts @@ -0,0 +1,5 @@ +import { StatusType } from '../generic/Status.type' + +export interface SignUpResponseInterface { + status: StatusType +} diff --git a/libs/interfaces/src/generic/Status.type.ts b/libs/interfaces/src/generic/Status.type.ts new file mode 100644 index 0000000..2c4c236 --- /dev/null +++ b/libs/interfaces/src/generic/Status.type.ts @@ -0,0 +1,4 @@ +export enum StatusType { + OK = 'ok', + FAIL = 'fail' +} diff --git a/libs/interfaces/src/index.ts b/libs/interfaces/src/index.ts index 4379695..92c3b8e 100644 --- a/libs/interfaces/src/index.ts +++ b/libs/interfaces/src/index.ts @@ -2,4 +2,9 @@ export * from './user/User.interface' export * from './auth/signInResponse.interface' export * from './error/ErrorMessage.enum' export * from './generic/Pure.type' +export * from './generic/Status.type' export * from './auth/jwtPayload.interface' +export * from './auth/passwordResetRequest.interface' +export * from './auth/passwordResetPerform.interface' +export * from './auth/signUpResponse.interface' +export * from './auth/confirmationCode.interface' diff --git a/libs/interfaces/src/user/User.interface.ts b/libs/interfaces/src/user/User.interface.ts index 45dddde..9d81cf1 100644 --- a/libs/interfaces/src/user/User.interface.ts +++ b/libs/interfaces/src/user/User.interface.ts @@ -1,3 +1,5 @@ +import { ConfirmationCodeInterface } from '../auth/confirmationCode.interface' + export interface UserInterface { id?: number firstName: string @@ -8,4 +10,5 @@ export interface UserInterface { refreshToken?: string passwordResetCode?: string | null active: boolean + confirmationCodes?: ConfirmationCodeInterface[] } diff --git a/package.json b/package.json index 1f4a763..e899681 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@types/cookie-parser": "^1.4.6", "@types/express-session": "^1.17.10", "@types/jest": "^29.4.0", + "@types/luxon": "^3.3.7", "@types/node": "20.10.0", "@types/nodemailer": "^6.4.14", "@types/passport": "^1.0.16", From fb7ecf063ba2176d8a10193206b666731acc0284 Mon Sep 17 00:00:00 2001 From: Nguyen Duy Long <68091468+llong2195@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:34:14 +0700 Subject: [PATCH 10/11] #107 [API] [PLATFORM] Add build phase to CI (#120) * feat: add ci * feat: detect code change & run action * feat: disable ci web * small adjustments --------- Co-authored-by: Vygandas Pliasas --- ...cted_code.yml => pr_all_affected_test.yml} | 2 +- .github/workflows/pr_push_api_build.yml | 36 +++++++++++++++++++ .github/workflows/pr_push_platform_build.yml | 36 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) rename .github/workflows/{pr_affected_code.yml => pr_all_affected_test.yml} (96%) create mode 100644 .github/workflows/pr_push_api_build.yml create mode 100644 .github/workflows/pr_push_platform_build.yml diff --git a/.github/workflows/pr_affected_code.yml b/.github/workflows/pr_all_affected_test.yml similarity index 96% rename from .github/workflows/pr_affected_code.yml rename to .github/workflows/pr_all_affected_test.yml index 82e8ff4..aac6add 100644 --- a/.github/workflows/pr_affected_code.yml +++ b/.github/workflows/pr_all_affected_test.yml @@ -1,4 +1,4 @@ -name: Nx Affected CI +name: PR ALL Affected Test on: pull_request: branches: [main] diff --git a/.github/workflows/pr_push_api_build.yml b/.github/workflows/pr_push_api_build.yml new file mode 100644 index 0000000..3630b62 --- /dev/null +++ b/.github/workflows/pr_push_api_build.yml @@ -0,0 +1,36 @@ +name: 'PR & Push API Build' +on: + pull_request: + branches: + - '*' + paths: + - 'libs/**' + - 'apps/api/**' + push: + branches: + - '*' + paths: + - 'libs/**' + - 'apps/api/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: 'Install dependencies' + run: yarn install + + - name: 'Build Api' + run: npx nx run api:build diff --git a/.github/workflows/pr_push_platform_build.yml b/.github/workflows/pr_push_platform_build.yml new file mode 100644 index 0000000..8f6b621 --- /dev/null +++ b/.github/workflows/pr_push_platform_build.yml @@ -0,0 +1,36 @@ +name: 'PR & Push Platform Build' +on: + pull_request: + branches: + - '*' + paths: + - 'libs/**' + - 'apps/platform/**' + push: + branches: + - '*' + paths: + - 'libs/**' + - 'apps/platform/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: 'Install dependencies' + run: yarn install + + - name: 'Build Platform' + run: npx nx run platform:build From a03e57d2c8e45d54fb3ac199f43cb8100f47781f Mon Sep 17 00:00:00 2001 From: Vygandas Pliasas Date: Thu, 7 Dec 2023 13:44:35 +0200 Subject: [PATCH 11/11] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index babd8ab..9914e4e 100644 --- a/README.md +++ b/README.md @@ -104,16 +104,16 @@ line of code at a time. ๐Ÿš€ - [x] Generated documentation for API with Compodoc - [x] Swagger for API endpoints - [x] DTO as shared lib -- [ ] React Query for app data storage +- [x] React Query for app data storage - [x] Interfaces as shared lib - [x] Utils as shared lib (for example time formatting) -- [ ] TypeORM & DB migrations +- [x] TypeORM & DB migrations - [ ] Plan a strict rules for timestamps (this is frequent problem for projects where users has to interact across multiple timezones) - [ ] Connect S3 or other object bucket - [ ] Production Dockerfiles - [ ] SSL in development -- [ ] Transactional emails and templates (prod/dev) +- [x] Transactional emails and templates (prod/dev) - [ ] Websockets for notifications and updates - [ ] GDPR compliance - [ ] Easy solution for roles and access management