├── .gitignore ├── settings.gradle ├── src ├── main │ ├── resources │ │ ├── static │ │ │ ├── images │ │ │ │ ├── 1.jpg │ │ │ │ ├── ps.jpeg │ │ │ │ ├── comp.jpeg │ │ │ │ ├── index.png │ │ │ │ ├── wanted.jpg │ │ │ │ ├── credit.jpeg │ │ │ │ ├── sheriff.jpeg │ │ │ │ ├── transfer.png │ │ │ │ ├── card_debts.jpeg │ │ │ │ ├── credit-card.png │ │ │ │ ├── arrow-down_red.png │ │ │ │ ├── arrow-up_green.png │ │ │ │ ├── slider │ │ │ │ │ ├── wall3.jpg │ │ │ │ │ └── wall4.jpg │ │ │ │ ├── Money_Bag_updated.png │ │ │ │ ├── Save_money-updated.png │ │ │ │ ├── 1483559872_save_money.png │ │ │ │ └── 1483559887_save_money.png │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── JosefinSlab-Bold.ttf │ │ │ │ ├── JosefinSlab-Regular.ttf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ └── JosefinSlab-SemiBoldItalic.ttf │ │ │ ├── less │ │ │ │ ├── fixed-width.less │ │ │ │ ├── screen-reader.less │ │ │ │ ├── list.less │ │ │ │ ├── core.less │ │ │ │ ├── larger.less │ │ │ │ ├── font-awesome.less │ │ │ │ ├── stacked.less │ │ │ │ ├── rotated-flipped.less │ │ │ │ ├── animated.less │ │ │ │ ├── path.less │ │ │ │ ├── bordered-pulled.less │ │ │ │ └── mixins.less │ │ │ ├── scss │ │ │ │ ├── _fixed-width.scss │ │ │ │ ├── _screen-reader.scss │ │ │ │ ├── _list.scss │ │ │ │ ├── _larger.scss │ │ │ │ ├── _core.scss │ │ │ │ ├── font-awesome.scss │ │ │ │ ├── _stacked.scss │ │ │ │ ├── _animated.scss │ │ │ │ ├── _bordered-pulled.scss │ │ │ │ ├── _path.scss │ │ │ │ ├── _rotated-flipped.scss │ │ │ │ └── _mixins.scss │ │ │ ├── included │ │ │ │ ├── result.html │ │ │ │ ├── head.html │ │ │ │ ├── congratulation.html │ │ │ │ ├── confirm.html │ │ │ │ ├── error.html │ │ │ │ ├── credit.html │ │ │ │ ├── footer.html │ │ │ │ ├── forget.html │ │ │ │ ├── navbar.html │ │ │ │ ├── transactions.html │ │ │ │ ├── signin.html │ │ │ │ └── signup.html │ │ │ ├── js │ │ │ │ ├── request.js │ │ │ │ ├── accounts.js │ │ │ │ ├── offer.js │ │ │ │ ├── contact.js │ │ │ │ ├── subscribe.js │ │ │ │ ├── forgot.js │ │ │ │ ├── common.js │ │ │ │ ├── credit.js │ │ │ │ ├── validate.js │ │ │ │ ├── transaction.js │ │ │ │ └── user.js │ │ │ ├── error.html │ │ │ ├── confirm.html │ │ │ ├── sign.html │ │ │ ├── reset.html │ │ │ ├── contact.html │ │ │ └── private.html │ │ ├── templates │ │ │ ├── recovery.html │ │ │ └── registration.html │ │ └── application.yml │ └── java │ │ └── com │ │ └── cbank │ │ ├── domain │ │ ├── credit │ │ │ ├── CreditState.java │ │ │ ├── CreditDirection.java │ │ │ ├── Credit.java │ │ │ └── CreditType.java │ │ ├── transaction │ │ │ ├── TransactionDirection.java │ │ │ ├── Transaction.java │ │ │ └── TransactionAccountProjection.java │ │ ├── security │ │ │ ├── BaseTokenType.java │ │ │ ├── RememberMeToken.java │ │ │ └── BaseToken.java │ │ ├── Persistable.java │ │ ├── message │ │ │ ├── MessageTemplate.java │ │ │ ├── Feedback.java │ │ │ └── Message.java │ │ ├── RegistrationForm.java │ │ ├── Subscriber.java │ │ ├── Client.java │ │ ├── Account.java │ │ └── user │ │ │ └── User.java │ │ ├── exceptions │ │ ├── TokenExpiredException.java │ │ ├── InsufficientFundsException.java │ │ ├── AuthenticationProcessException.java │ │ └── ValidationException.java │ │ ├── services │ │ ├── PersistableService.java │ │ ├── AuthenticationService.java │ │ ├── CreditService.java │ │ ├── TariffService.java │ │ ├── impl │ │ │ ├── message │ │ │ │ ├── MessageTemplateFactory.java │ │ │ │ ├── MessageTextTemplateFactoryImpl.java │ │ │ │ └── MessageServiceImpl.java │ │ │ ├── transaction │ │ │ │ └── BalanceServiceImpl.java │ │ │ ├── tariff │ │ │ │ ├── TariffResolver.java │ │ │ │ └── TariffHolder.java │ │ │ ├── SubscribeServiceImpl.java │ │ │ ├── credit │ │ │ │ ├── CreditFactory.java │ │ │ │ └── CreditServiceImpl.java │ │ │ ├── user │ │ │ │ ├── AuthenticationServiceImpl.java │ │ │ │ ├── RegistrationServiceImpl.java │ │ │ │ └── UserServiceImpl.java │ │ │ ├── ClientServiceImpl.java │ │ │ ├── TokenServiceImpl.java │ │ │ └── AccountServiceImpl.java │ │ ├── ClientService.java │ │ ├── RegistrationService.java │ │ ├── SubscribeService.java │ │ ├── TokenService.java │ │ ├── BalanceService.java │ │ ├── MessageService.java │ │ ├── TransactionService.java │ │ ├── AccountService.java │ │ └── UserService.java │ │ ├── validators │ │ ├── Validator.java │ │ ├── FeedbackValidator.java │ │ ├── ValidationUtils.java │ │ ├── RegistrationValidator.java │ │ └── CreditValidator.java │ │ ├── repositories │ │ ├── MessageRepository.java │ │ ├── FeedbackRepository.java │ │ ├── UserRepository.java │ │ ├── ClientRepository.java │ │ ├── CreditRepository.java │ │ ├── AccountRepository.java │ │ ├── TransactionRepository.java │ │ ├── SubscriberRepository.java │ │ ├── BaseTokenRepository.java │ │ └── RememberMeTokenRepository.java │ │ ├── config │ │ ├── MailProperties.java │ │ ├── AuthFailureHandler.java │ │ ├── AuthSuccessHandler.java │ │ ├── Sha256AuthenticationProvider.java │ │ └── SecurityConfiguration.java │ │ ├── controllers │ │ ├── AggregationModel.java │ │ ├── ErrorMessage.java │ │ ├── StatementController.java │ │ ├── FeedbackController.java │ │ ├── UserDataAggregationAdvice.java │ │ ├── CreditController.java │ │ ├── TransactionController.java │ │ ├── NavigationController.java │ │ ├── AccountController.java │ │ ├── ErrorController.java │ │ ├── SubscriberController.java │ │ └── UserController.java │ │ ├── utils │ │ ├── RandomUtils.java │ │ └── MapUtils.java │ │ └── CountryBankApplication.java └── test │ └── groovy │ └── com │ └── cbank │ ├── utils │ ├── RandomUtilsUnitTest.groovy │ └── MapUtilsUnitTest.groovy │ ├── services │ └── impl │ │ ├── user │ │ ├── RegistrationServiceIntTest.groovy │ │ ├── RegistrationServiceImplTest.groovy │ │ ├── AuthenticationServiceImplUnitTest.groovy │ │ └── UserServiceImplUnitTest.groovy │ │ ├── credit │ │ ├── CreditFactoryUnitTest.groovy │ │ └── CreditServiceImplUnitTest.groovy │ │ ├── message │ │ ├── MessageTextTemplateFactoryImplIntTest.groovy │ │ └── MessageServiceImplUnitTest.groovy │ │ ├── SubscribeServiceImplUnitTest.groovy │ │ ├── transaction │ │ ├── BalanceServiceImplUnitTest.groovy │ │ └── TransactionServiceImplUnitTest.groovy │ │ ├── tariff │ │ └── TariffResolverUnitTest.groovy │ │ ├── ClientServiceImplUnitTest.groovy │ │ ├── TokenServiceImplUnitTest.groovy │ │ └── AccountServiceImplUnitTest.groovy │ ├── config │ ├── AuthFailureHandlerUnitTest.groovy │ ├── AuthSuccessHandlerUnitTest.groovy │ └── Sha256AuthenticationProviderUnitTest.groovy │ └── validators │ ├── FeedbackValidatorUnitTest.groovy │ ├── CreditValidatorUnitTest.groovy │ └── RegistrationValidatorUnitTest.groovy ├── Dockerfile ├── .gitattributes ├── docker-compose.yml ├── Jenkinsfile ├── .travis.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.gradle/ 3 | /.idea/ 4 | build/ 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'country_bank' 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/static/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/ps.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/ps.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/images/comp.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/comp.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/images/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/index.png -------------------------------------------------------------------------------- /src/main/resources/static/images/wanted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/wanted.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/credit.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/credit.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/images/sheriff.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/sheriff.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/images/transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/transfer.png -------------------------------------------------------------------------------- /src/main/resources/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/resources/static/images/card_debts.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/card_debts.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/images/credit-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/credit-card.png -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/credit/CreditState.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.credit; 2 | 3 | 4 | public enum CreditState { 5 | OPENED, CLOSED 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/static/images/arrow-down_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/arrow-down_red.png -------------------------------------------------------------------------------- /src/main/resources/static/images/arrow-up_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/arrow-up_green.png -------------------------------------------------------------------------------- /src/main/resources/static/images/slider/wall3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/slider/wall3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/slider/wall4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/slider/wall4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/fonts/JosefinSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/JosefinSlab-Bold.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/JosefinSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/JosefinSlab-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/images/Money_Bag_updated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/Money_Bag_updated.png -------------------------------------------------------------------------------- /src/main/resources/static/images/Save_money-updated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/Save_money-updated.png -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/images/1483559872_save_money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/1483559872_save_money.png -------------------------------------------------------------------------------- /src/main/resources/static/images/1483559887_save_money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/images/1483559887_save_money.png -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/transaction/TransactionDirection.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.transaction; 2 | 3 | 4 | public enum TransactionDirection { 5 | IN, OUT 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/static/fonts/JosefinSlab-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaiswaladi246/CountryBank/HEAD/src/main/resources/static/fonts/JosefinSlab-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/main/resources/static/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/static/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { 5 | .sr-only(); 6 | } 7 | 8 | .sr-only-focusable { 9 | .sr-only-focusable(); 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-jre 2 | MAINTAINER Nikita Podshivalov 3 | 4 | ADD ./build/libs/country_bank-1.0.jar /application/ 5 | CMD ["java", "-jar", "/application/country_bank-1.0.jar"] 6 | 7 | EXPOSE 8000 8 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/exceptions/TokenExpiredException.java: -------------------------------------------------------------------------------- 1 | package com.cbank.exceptions; 2 | 3 | /** 4 | * @author Podshivalov N.A. 5 | * @since 21.11.2017. 6 | */ 7 | public class TokenExpiredException extends RuntimeException {} 8 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { 5 | @include sr-only(); 6 | } 7 | 8 | .sr-only-focusable { 9 | @include sr-only-focusable(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/credit/CreditDirection.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.credit; 2 | 3 | /** 4 | * @author Podshivalov N.A. 5 | * @since 25.11.2017. 6 | */ 7 | public enum CreditDirection { 8 | CREDIT, DEPOSIT 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/PersistableService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | /** 4 | * @author Podshivalov N.A. 5 | * @since 21.11.2017. 6 | */ 7 | public interface PersistableService { 8 | T save (T t); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/exceptions/InsufficientFundsException.java: -------------------------------------------------------------------------------- 1 | package com.cbank.exceptions; 2 | 3 | /** 4 | * @author Podshivalov N.A. 5 | * @since 21.11.2017. 6 | */ 7 | public class InsufficientFundsException extends RuntimeException{ 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/validators/Validator.java: -------------------------------------------------------------------------------- 1 | package com.cbank.validators; 2 | 3 | /** 4 | * @author Podshivalov N.A. 5 | * @since 21.11.2017. 6 | */ 7 | public interface Validator { 8 | 9 | void validate(T t); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/security/BaseTokenType.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.security; 2 | 3 | /** 4 | * @author Podshivalov N.A. 5 | * @since 21.11.2017. 6 | */ 7 | public enum BaseTokenType { 8 | RESET_PASSWORD, REGISTRATION 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/static/included/result.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 |

Thank you for choosing our bank!

5 |
6 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/services/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | public interface AuthenticationService { 4 | int MAX_ATTEMPTS = 3; 5 | 6 | enum UserFailStatus { 7 | WRONG, BLOCK 8 | } 9 | 10 | UserFailStatus failed(String login); 11 | 12 | void success(String login); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/CreditService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.credit.Credit; 4 | 5 | /** 6 | * @author Podshivalov N.A. 7 | * @since 20.12.2017. 8 | */ 9 | public interface CreditService{ 10 | 11 | Credit create(Credit credit); 12 | 13 | void withdraw(Credit credit); 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.message.Message; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 21.11.2017. 9 | */ 10 | public interface MessageRepository extends CrudRepository{ 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/TariffService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.transaction.Transaction; 4 | 5 | import java.math.BigDecimal; 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 21.11.2017. 10 | */ 11 | public interface TariffService { 12 | 13 | BigDecimal evaluate(Transaction transaction); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/FeedbackRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.message.Feedback; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 21.11.2017. 9 | */ 10 | public interface FeedbackRepository extends CrudRepository { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/static/included/head.html: -------------------------------------------------------------------------------- 1 | 2 | Country Bank 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/templates/recovery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Country Bank 6 | 7 | 8 |

Please, click the link to change your password

9 | 10 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/utils/RandomUtilsUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.utils 2 | 3 | import spock.lang.Specification 4 | 5 | class RandomUtilsUnitTest extends Specification { 6 | 7 | 8 | def "GenerateAccountNum"() { 9 | expect: 10 | RandomUtils.generateAccountNum().length() == 16 11 | where: 12 | i << (1..100) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.user.User; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Optional; 7 | 8 | 9 | public interface UserRepository extends CrudRepository { 10 | Optional findByUsername(String username); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/static/included/congratulation.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Congratulation
4 |

You account was successful saved

5 |

Email was sent on your email. Please, confirm the registration

6 |
7 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/message/MessageTemplateFactory.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.message; 2 | 3 | import com.cbank.domain.message.MessageTemplate; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 25.12.2017. 10 | */ 11 | public interface MessageTemplateFactory { 12 | 13 | String create(MessageTemplate template, Map context); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/static/included/confirm.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Confirmation
4 |

Do your confirm your choice?

5 | 6 | 7 |
8 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/services/ClientService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.Client; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 21.11.2017. 10 | */ 11 | public interface ClientService extends PersistableService{ 12 | 13 | Optional byUserId(String username); 14 | 15 | void accessRecovery(String loginOrEmail); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/config/MailProperties.java: -------------------------------------------------------------------------------- 1 | package com.cbank.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 22.12.2017. 9 | */ 10 | @Data 11 | @ConfigurationProperties("spring.mail") 12 | public class MailProperties { 13 | private Boolean enable = Boolean.FALSE; 14 | private String username; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/exceptions/AuthenticationProcessException.java: -------------------------------------------------------------------------------- 1 | package com.cbank.exceptions; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | /** 6 | * @author Podshivalov N.A. 7 | * @since 23.11.2017. 8 | */ 9 | public class AuthenticationProcessException extends AuthenticationException { 10 | 11 | public AuthenticationProcessException(String msg) { 12 | super(msg); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/static/included/error.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Something was wrong
4 |

Do you want to try again?

5 | 6 | 7 |
8 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/exceptions/ValidationException.java: -------------------------------------------------------------------------------- 1 | package com.cbank.exceptions; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author Podshivalov N.A. 7 | * @since 21.11.2017. 8 | */ 9 | @Getter 10 | public class ValidationException extends RuntimeException{ 11 | private String message; 12 | 13 | public ValidationException(String message) { 14 | super(message); 15 | this.message = message; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/Persistable.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.Id; 7 | import javax.persistence.MappedSuperclass; 8 | 9 | /** 10 | * @author Podshivalov N.A. 11 | * @since 21.11.2017. 12 | */ 13 | @Data 14 | @MappedSuperclass 15 | public class Persistable { 16 | 17 | @Id 18 | @GeneratedValue 19 | private Long id; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/RegistrationService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.Account; 4 | import com.cbank.domain.RegistrationForm; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 21.11.2017. 9 | */ 10 | public interface RegistrationService { 11 | 12 | Account register(RegistrationForm form); 13 | 14 | void confirm(String token); 15 | 16 | boolean check(String usernameOrEmail); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/SubscribeService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.Subscriber; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * {@link com.bank.service.impl.SubscribeServiceImpl} 9 | */ 10 | public interface SubscribeService { 11 | 12 | Subscriber subscribe(Subscriber subscriber); 13 | 14 | Optional byEmail(String email); 15 | 16 | void unsubscribe(String email) ; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/AggregationModel.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author Podshivalov N.A. 10 | * @since 21.11.2017. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.TYPE) 14 | @interface AggregationModel { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/static/js/request.js: -------------------------------------------------------------------------------- 1 | const POST = "POST"; 2 | const GET = "GET"; 3 | const PUT = "PUT"; 4 | const PATCH = "PATCH"; 5 | const DELETE = "DELETE"; 6 | 7 | const request = (config, onSuccess, onError) => { 8 | $.ajax({ 9 | url: config.url, 10 | contentType: 'application/json', 11 | type: config.type, 12 | data: JSON.stringify(config.data), 13 | success: onSuccess, 14 | error: onError 15 | }); 16 | }; -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import lombok.Data; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * @author Podshivalov N.A. 10 | * @since 21.11.2017. 11 | */ 12 | @Data 13 | @RequiredArgsConstructor(staticName = "create") 14 | public class ErrorMessage{ 15 | private final String message; 16 | private LocalDateTime timestamps = LocalDateTime.now(); 17 | } -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.security.BaseToken; 4 | import com.cbank.domain.security.BaseTokenType; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 21.11.2017. 9 | */ 10 | public interface TokenService { 11 | 12 | BaseToken create(String username, BaseTokenType tokenType); 13 | 14 | //todo to task 15 | int checkForExpired(); 16 | 17 | BaseToken get(String uuid); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/utils/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package com.cbank.utils; 2 | 3 | import lombok.val; 4 | 5 | import java.util.Random; 6 | 7 | public class RandomUtils { 8 | 9 | private static final Random random = new Random(); 10 | 11 | public static String generateAccountNum(){ 12 | val accountNum = "" + ((long) (random.nextDouble() * 1000_0000_0000_0000L)); 13 | return accountNum + (long) Math.pow(10, 15 - accountNum.length()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/BalanceService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.Account; 4 | 5 | import java.math.BigDecimal; 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 21.11.2017. 10 | */ 11 | public interface BalanceService { 12 | 13 | default BigDecimal balance(Account account){ 14 | return balance(account.getNum()); 15 | } 16 | 17 | BigDecimal balance(String accountNum); 18 | 19 | BigDecimal balance(Long accountId); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.message.Feedback; 4 | import com.cbank.domain.message.Message; 5 | import com.cbank.domain.message.MessageTemplate; 6 | 7 | import java.util.Map; 8 | 9 | 10 | public interface MessageService extends PersistableService { 11 | 12 | Message send(String recipient, MessageTemplate template, Map context); 13 | 14 | Feedback persist(Feedback feedback); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/ClientRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.Client; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * @author Podshivalov N.A. 10 | * @since 21.11.2017. 11 | */ 12 | public interface ClientRepository extends CrudRepository { 13 | 14 | Optional findByUserId(String userId); 15 | 16 | Optional findByEmail(String email); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/static/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { 9 | position: relative; 10 | } 11 | } 12 | 13 | .@{fa-css-prefix}-li { 14 | position: absolute; 15 | left: -@fa-li-width; 16 | width: @fa-li-width; 17 | top: (2em / 14); 18 | text-align: center; 19 | &.@{fa-css-prefix}-lg { 20 | left: (-@fa-li-width + (4em / 14)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { 9 | position: relative; 10 | } 11 | } 12 | 13 | .#{$fa-css-prefix}-li { 14 | position: absolute; 15 | left: -$fa-li-width; 16 | width: $fa-li-width; 17 | top: (2em / 14); 18 | text-align: center; 19 | &.#{$fa-css-prefix}-lg { 20 | left: -$fa-li-width + (4em / 14); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/static/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/static/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | 11 | .@{fa-css-prefix}-2x { 12 | font-size: 2em; 13 | } 14 | 15 | .@{fa-css-prefix}-3x { 16 | font-size: 3em; 17 | } 18 | 19 | .@{fa-css-prefix}-4x { 20 | font-size: 4em; 21 | } 22 | 23 | .@{fa-css-prefix}-5x { 24 | font-size: 5em; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | 11 | .#{$fa-css-prefix}-2x { 12 | font-size: 2em; 13 | } 14 | 15 | .#{$fa-css-prefix}-3x { 16 | font-size: 3em; 17 | } 18 | 19 | .#{$fa-css-prefix}-4x { 20 | font-size: 4em; 21 | } 22 | 23 | .#{$fa-css-prefix}-5x { 24 | font-size: 5em; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/templates/registration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Country Bank 6 | 7 | 8 |

Country Bank is greeting to you, !

9 |

You have been successfully registered, and now one point left

10 |

Please, click the link to confirm your registration

11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | @import "variables"; 6 | @import "mixins"; 7 | @import "path"; 8 | @import "core"; 9 | @import "larger"; 10 | @import "fixed-width"; 11 | @import "list"; 12 | @import "bordered-pulled"; 13 | @import "animated"; 14 | @import "rotated-flipped"; 15 | @import "stacked"; 16 | @import "icons"; 17 | @import "screen-reader"; 18 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/message/MessageTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.message; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 21.11.2017. 9 | */ 10 | @Getter 11 | @AllArgsConstructor 12 | public enum MessageTemplate { 13 | REGISTRATION_CONFIRMATION("You are welcomed to Country Bank!", "registration"), 14 | ACCESS_RECOVERY("Recovery of bank access", "recovery" ); 15 | 16 | public final String title; 17 | public final String filename; 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/main/resources/static/scss/* linguist-vendored 2 | src/main/resources/static/less/* linguist-vendored 3 | src/main/resources/static/fonts/* linguist-vendored 4 | src/main/resources/static/css/animate.min.css linguist-vendored 5 | src/main/resources/static/css/bootstrap.min.css linguist-vendored 6 | src/main/resources/static/css/font-awesome.min.css linguist-vendored 7 | src/main/resources/static/js/bootstrap.min.js linguist-vendored 8 | src/main/resources/static/js/owl.carousel.min.js linguist-vendored 9 | src/main/resources/static/js/jquery-1.11.1.min.js linguist-vendored -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | cb: 4 | image: nikitap/countrybank 5 | ports: 6 | - "8000:8000" 7 | environment: 8 | - CONFIG_MAIL_HOST=${CONFIG_MAIL_HOST} 9 | - CONFIG_MAIL_PORT=${CONFIG_MAIL_PORT} 10 | - CONFIG_MAIL_SMTP_AUTH=${CONFIG_MAIL_SMTP_AUTH} 11 | - CONFIG_MAIL_STARTTLS=${CONFIG_MAIL_STARTTLS} 12 | - CONFIG_MAIL_USERNAME=${CONFIG_MAIL_USERNAME} 13 | - CONFIG_MAIL_PASSWORD=${CONFIG_MAIL_PASSWORD} 14 | logging: 15 | options: 16 | max-size: "20m" 17 | max-file: "10" -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/CreditRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.credit.Credit; 4 | import com.cbank.domain.credit.CreditType; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.Collection; 8 | import java.util.Set; 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 20.12.2017. 13 | */ 14 | public interface CreditRepository extends JpaRepository { 15 | 16 | Collection findAllByAccountIdAndTypeIn(Long accountId, Set creditType); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/RegistrationForm.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | /** 7 | * @author Podshivalov N.A. 8 | * @since 21.11.2017. 9 | */ 10 | @Data 11 | @ToString(exclude = "password") 12 | public class RegistrationForm { 13 | 14 | private String username; 15 | private String password; 16 | private String email; 17 | private String name; 18 | private String address; 19 | 20 | public Client toClient(){ 21 | return new Client(name, address, email, username); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/static/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | @import "variables.less"; 6 | @import "mixins.less"; 7 | @import "path.less"; 8 | @import "core.less"; 9 | @import "larger.less"; 10 | @import "fixed-width.less"; 11 | @import "list.less"; 12 | @import "bordered-pulled.less"; 13 | @import "animated.less"; 14 | @import "rotated-flipped.less"; 15 | @import "stacked.less"; 16 | @import "icons.less"; 17 | @import "screen-reader.less"; 18 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.Account; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Collection; 7 | import java.util.Optional; 8 | 9 | /** 10 | * @author Podshivalov N.A. 11 | * @since 21.11.2017. 12 | */ 13 | public interface AccountRepository extends JpaRepository{ 14 | 15 | Optional findByClientIdAndCurrentIsTrue(Long clientId); 16 | 17 | Optional findByNum(String accountNum); 18 | 19 | Collection findAllByClientId(Long clientId); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/utils/MapUtils.java: -------------------------------------------------------------------------------- 1 | package com.cbank.utils; 2 | 3 | import com.google.common.collect.Maps; 4 | import lombok.val; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 22.11.2017. 13 | */ 14 | public final class MapUtils { 15 | 16 | @SafeVarargs 17 | public static Map from(Map.Entry... entries){ 18 | val map = new HashMap(entries.length); 19 | for (Map.Entry entry : entries) map.put(entry.getKey(), entry.getValue()); 20 | return map; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/TransactionRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.transaction.Transaction; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author Podshivalov N.A. 11 | * @since 21.11.2017. 12 | */ 13 | public interface TransactionRepository extends JpaRepository{ 14 | 15 | @Query("select t from Transaction t where t.payer = ?1 or t.recipient = ?1") 16 | Collection findAllByAccountNum(String accountNum); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/static/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | 13 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 14 | position: absolute; 15 | left: 0; 16 | width: 100%; 17 | text-align: center; 18 | } 19 | 20 | .@{fa-css-prefix}-stack-1x { 21 | line-height: inherit; 22 | } 23 | 24 | .@{fa-css-prefix}-stack-2x { 25 | font-size: 2em; 26 | } 27 | 28 | .@{fa-css-prefix}-inverse { 29 | color: @fa-inverse; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | 13 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 14 | position: absolute; 15 | left: 0; 16 | width: 100%; 17 | text-align: center; 18 | } 19 | 20 | .#{$fa-css-prefix}-stack-1x { 21 | line-height: inherit; 22 | } 23 | 24 | .#{$fa-css-prefix}-stack-2x { 25 | font-size: 2em; 26 | } 27 | 28 | .#{$fa-css-prefix}-inverse { 29 | color: $fa-inverse; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/static/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

Something was wrong!

9 | 10 | 11 |

May be coyotes have gnashed wires

12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/SubscriberRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.Subscriber; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Modifying; 6 | import org.springframework.transaction.annotation.Transactional; 7 | 8 | import java.util.Optional; 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 21.11.2017. 13 | */ 14 | public interface SubscriberRepository extends JpaRepository { 15 | 16 | Optional findByEmail(String email); 17 | 18 | @Transactional 19 | @Modifying 20 | void deleteByEmail(String email); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/static/js/accounts.js: -------------------------------------------------------------------------------- 1 | $("#open-account-btn").bind("click", () => makeNewAccount()); 2 | 3 | const makeNewAccount = () => showConfirmation(() => 4 | request({ 5 | url: "/accounts", 6 | type: POST, 7 | }, () => showResult("#accounts", "Account was created successfully") , 8 | (xhr) => showResult("#accounts", xhr.responseJSON.message))); 9 | 10 | const markAccountAsCurrent = () => showConfirmation(() => 11 | request({ 12 | url: "/accounts", 13 | type: PATCH, 14 | data: {accountNum : $("#account-num").text()} 15 | }, 16 | () => redirect('/private'), 17 | () => redirect('/error') 18 | )); -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/TransactionService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.Account; 4 | import com.cbank.domain.credit.Credit; 5 | import com.cbank.domain.transaction.Transaction; 6 | import com.cbank.domain.transaction.TransactionAccountProjection; 7 | 8 | import java.util.Collection; 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 21.11.2017. 13 | */ 14 | public interface TransactionService { 15 | 16 | Transaction create(Transaction transaction); 17 | 18 | Collection byAccount(String accountNum); 19 | 20 | Transaction creditWithdraw(Account account, Credit credit); 21 | 22 | Transaction credit(Account account, Credit credit); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/validators/FeedbackValidator.java: -------------------------------------------------------------------------------- 1 | package com.cbank.validators; 2 | 3 | import com.cbank.domain.message.Feedback; 4 | import com.cbank.exceptions.ValidationException; 5 | import org.springframework.stereotype.Component; 6 | 7 | import static org.springframework.util.StringUtils.hasLength; 8 | 9 | @Component 10 | public class FeedbackValidator implements Validator { 11 | 12 | private static final String EMPTY_BODY = "Message is empty"; 13 | 14 | @Override 15 | public void validate(Feedback feedback) { 16 | ValidationUtils.email(feedback.getEmail()); 17 | ValidationUtils.name(feedback.getName()); 18 | if (!hasLength(feedback.getBody())) throw new ValidationException(EMPTY_BODY); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/BaseTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.security.BaseToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Modifying; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 21.11.2017. 14 | */ 15 | public interface BaseTokenRepository extends JpaRepository { 16 | 17 | @Modifying 18 | @Transactional 19 | @Query("UPDATE BaseToken t SET t.valid = false WHERE t.createdAt <= ?1 ") 20 | int expire(LocalDateTime date); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/AccountService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.Account; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.Collection; 7 | import java.util.Optional; 8 | 9 | /** 10 | * @author Podshivalov N.A. 11 | * @since 21.11.2017. 12 | */ 13 | public interface AccountService extends PersistableService { 14 | String GOVERNMENT_ACCOUNT = "9999999999999999"; 15 | String BANK_ACCOUNT = "0000000000000000"; 16 | BigDecimal NEW_ACCOUNT_PRICE = BigDecimal.valueOf(30); 17 | 18 | Optional current(Long clientId); 19 | 20 | Account asCurrent(Account current, String accountNum); 21 | 22 | Account create(Account current); 23 | 24 | Collection byClient(Long clientId); 25 | } 26 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/utils/MapUtilsUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.utils 2 | 3 | import spock.lang.Specification 4 | 5 | import static com.google.common.collect.Maps.immutableEntry 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 22.12.2017. 10 | */ 11 | class MapUtilsUnitTest extends Specification { 12 | 13 | def "test of correct filling map"() { 14 | given: 15 | def k1 = "k1" 16 | def k2 = "k2" 17 | def v1 = "v1" 18 | def v2 = "v2" 19 | when: 20 | def map = MapUtils.from(immutableEntry(k1, v1), immutableEntry(k2, v2)) 21 | then: 22 | map.containsKey(k1) 23 | map.containsKey(k2) 24 | map.get(k1) == v1 25 | map.get(k2) == v2 26 | map.size() == 2 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/Subscriber.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.val; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Id; 10 | import javax.persistence.Table; 11 | import java.util.UUID; 12 | 13 | 14 | @Entity 15 | @Table(name = "subscribers") 16 | @Data 17 | @NoArgsConstructor 18 | public class Subscriber { 19 | @Id 20 | private UUID id = UUID.randomUUID(); 21 | 22 | @Column(nullable = false, unique = true) 23 | private String email; 24 | 25 | public static Subscriber of(String email){ 26 | val subscriber = new Subscriber(); 27 | subscriber.setEmail(email); 28 | return subscriber; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/static/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { 5 | .fa-icon-rotate(90deg, 1); 6 | } 7 | 8 | .@{fa-css-prefix}-rotate-180 { 9 | .fa-icon-rotate(180deg, 2); 10 | } 11 | 12 | .@{fa-css-prefix}-rotate-270 { 13 | .fa-icon-rotate(270deg, 3); 14 | } 15 | 16 | .@{fa-css-prefix}-flip-horizontal { 17 | .fa-icon-flip(-1, 1, 0); 18 | } 19 | 20 | .@{fa-css-prefix}-flip-vertical { 21 | .fa-icon-flip(1, -1, 2); 22 | } 23 | 24 | // Hook for IE8-9 25 | // ------------------------- 26 | 27 | :root .@{fa-css-prefix}-rotate-90, 28 | :root .@{fa-css-prefix}-rotate-180, 29 | :root .@{fa-css-prefix}-rotate-270, 30 | :root .@{fa-css-prefix}-flip-horizontal, 31 | :root .@{fa-css-prefix}-flip-vertical { 32 | filter: none; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/message/Feedback.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.message; 2 | 3 | import com.cbank.domain.Persistable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.Table; 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 21.11.2017. 15 | */ 16 | @Entity 17 | @Table(name = "feedback") 18 | @Data 19 | @EqualsAndHashCode(callSuper = true) 20 | @AllArgsConstructor(staticName = "of") 21 | public class Feedback extends Persistable { 22 | 23 | @Column(nullable = false) 24 | private String name; 25 | 26 | @Column(nullable = false) 27 | private String email; 28 | 29 | @Column(nullable = false) 30 | private String body; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | stages { 5 | stage('Git Checkout') { 6 | steps { 7 | git 'https://github.com/jaiswaladi246/CountryBank.git' 8 | } 9 | } 10 | 11 | stage('OWASP Dependency Check') { 12 | steps { 13 | dependencyCheck additionalArguments: ' --scan ./ ', odcInstallation: 'DC' 14 | dependencyCheckPublisher pattern: '**/dependency-check-report.xml' 15 | } 16 | } 17 | 18 | stage('Trivy') { 19 | steps { 20 | sh "trivy fs ." 21 | } 22 | } 23 | 24 | stage('Build & deploy') { 25 | steps { 26 | sh "docker-compose up -d" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/static/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/static/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/static/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { 11 | float: left; 12 | } 13 | 14 | .@{fa-css-prefix}-pull-right { 15 | float: right; 16 | } 17 | 18 | .@{fa-css-prefix} { 19 | &.@{fa-css-prefix}-pull-left { 20 | margin-right: .3em; 21 | } 22 | &.@{fa-css-prefix}-pull-right { 23 | margin-left: .3em; 24 | } 25 | } 26 | 27 | /* Deprecated as of 4.4.0 */ 28 | .pull-right { 29 | float: right; 30 | } 31 | 32 | .pull-left { 33 | float: left; 34 | } 35 | 36 | .@{fa-css-prefix} { 37 | &.pull-left { 38 | margin-right: .3em; 39 | } 40 | &.pull-right { 41 | margin-left: .3em; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { 11 | float: left; 12 | } 13 | 14 | .#{$fa-css-prefix}-pull-right { 15 | float: right; 16 | } 17 | 18 | .#{$fa-css-prefix} { 19 | &.#{$fa-css-prefix}-pull-left { 20 | margin-right: .3em; 21 | } 22 | &.#{$fa-css-prefix}-pull-right { 23 | margin-left: .3em; 24 | } 25 | } 26 | 27 | /* Deprecated as of 4.4.0 */ 28 | .pull-right { 29 | float: right; 30 | } 31 | 32 | .pull-left { 33 | float: left; 34 | } 35 | 36 | .#{$fa-css-prefix} { 37 | &.pull-left { 38 | margin-right: .3em; 39 | } 40 | &.pull-right { 41 | margin-left: .3em; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/static/confirm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { 5 | @include fa-icon-rotate(90deg, 1); 6 | } 7 | 8 | .#{$fa-css-prefix}-rotate-180 { 9 | @include fa-icon-rotate(180deg, 2); 10 | } 11 | 12 | .#{$fa-css-prefix}-rotate-270 { 13 | @include fa-icon-rotate(270deg, 3); 14 | } 15 | 16 | .#{$fa-css-prefix}-flip-horizontal { 17 | @include fa-icon-flip(-1, 1, 0); 18 | } 19 | 20 | .#{$fa-css-prefix}-flip-vertical { 21 | @include fa-icon-flip(1, -1, 2); 22 | } 23 | 24 | // Hook for IE8-9 25 | // ------------------------- 26 | 27 | :root .#{$fa-css-prefix}-rotate-90, 28 | :root .#{$fa-css-prefix}-rotate-180, 29 | :root .#{$fa-css-prefix}-rotate-270, 30 | :root .#{$fa-css-prefix}-flip-horizontal, 31 | :root .#{$fa-css-prefix}-flip-vertical { 32 | filter: none; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/static/included/credit.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

CREDIT

6 |
7 | 8 |
9 |
10 | 11 |
12 | 13 |
14 |

Need money?

15 |

Take a credit

16 | 17 | 18 | 19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/transaction/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.transaction; 2 | 3 | import com.cbank.domain.Persistable; 4 | import lombok.*; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.Table; 9 | import java.math.BigDecimal; 10 | import java.time.LocalDateTime; 11 | 12 | @Entity 13 | @Table(name = "transactions") 14 | @EqualsAndHashCode(callSuper = false) 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Builder 19 | public class Transaction extends Persistable { 20 | 21 | @Column(nullable = false) 22 | private String payer; 23 | 24 | @Column(nullable = false) 25 | private String recipient; 26 | 27 | @Column(nullable = false) 28 | private BigDecimal amount; 29 | 30 | @Builder.Default 31 | private LocalDateTime createdAt = LocalDateTime.now(); 32 | 33 | private String details; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/static/included/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 13 |
Follow us in your favorite social
14 |
© nikitap4.92@gmail.com
15 |
16 |
17 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/CountryBankApplication.java: -------------------------------------------------------------------------------- 1 | package com.cbank; 2 | 3 | import com.cbank.config.MailProperties; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.security.web.DefaultRedirectStrategy; 9 | import org.springframework.security.web.RedirectStrategy; 10 | 11 | @Slf4j 12 | @SpringBootApplication 13 | public class CountryBankApplication { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(CountryBankApplication.class); 17 | } 18 | 19 | 20 | @Bean 21 | public RedirectStrategy redirectStrategy() { 22 | return new DefaultRedirectStrategy(); 23 | } 24 | 25 | @Bean 26 | public MailProperties emailProperties(){ 27 | return new MailProperties(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/message/MessageTextTemplateFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.message; 2 | 3 | import com.cbank.domain.message.MessageTemplate; 4 | import lombok.AllArgsConstructor; 5 | import lombok.val; 6 | import org.springframework.stereotype.Component; 7 | import org.thymeleaf.TemplateEngine; 8 | import org.thymeleaf.context.Context; 9 | 10 | import java.util.Locale; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Podshivalov N.A. 15 | * @since 21.11.2017. 16 | */ 17 | @Component 18 | @AllArgsConstructor 19 | class MessageTextTemplateFactoryImpl implements MessageTemplateFactory { 20 | private final TemplateEngine templateEngine; 21 | 22 | public String create(MessageTemplate template, Map context){ 23 | val filename = "../templates/" + template.getFilename(); 24 | return templateEngine.process(filename, new Context(Locale.getDefault(), context)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | spring: 4 | # datasource: 5 | # url: jdbc:postgresql://localhost:5432/country_bank 6 | # username: postgres 7 | # password: Qwerty1 8 | # driver: org.postgresql.Driver 9 | thymeleaf: 10 | cache: false 11 | prefix: classpath:/static/ 12 | jpa: 13 | hibernate: 14 | ddl-auto: create 15 | show-sql: false 16 | #database-platform: org.hibernate.dialect.PostgreSQL9Dialect 17 | mail: 18 | enable: false 19 | host: smtp.gmail.com 20 | port: 465 21 | starttls: 22 | enable: true 23 | username: countrybankannouncer@gmail.com 24 | password: 123456789qwerty 25 | 26 | schedule: 27 | cron: 0 0 0 * * * 28 | 29 | spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.PostgreSQL9Dialect 30 | 31 | logging: 32 | level: 33 | com.cbank.*: DEBUG 34 | 35 | --- 36 | spring: 37 | profiles: mail 38 | mail: 39 | enable: true 40 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/credit/Credit.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.credit; 2 | 3 | import com.cbank.domain.Persistable; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Entity; 9 | import java.math.BigDecimal; 10 | import java.time.LocalDateTime; 11 | 12 | 13 | @Data 14 | @Entity 15 | @EqualsAndHashCode( callSuper = true) 16 | @NoArgsConstructor 17 | public class Credit extends Persistable{ 18 | 19 | private Long accountId; 20 | private CreditType type; 21 | private BigDecimal initialAmount; 22 | private LocalDateTime openedAt = LocalDateTime.now(); 23 | private LocalDateTime updateAt; 24 | private CreditState state; 25 | private LocalDateTime closedAt; 26 | private Integer numOfWithdraws; 27 | private BigDecimal monthlySum; 28 | 29 | public String getTypeString(){ 30 | return type.getDirection().toString().toLowerCase(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/static/js/offer.js: -------------------------------------------------------------------------------- 1 | $("#credit_btn").bind("click", function () { 2 | detailOffer("#credit"); 3 | }); 4 | 5 | $("#deposit_btn").bind("click", function () { 6 | detailOffer("#deposit"); 7 | }); 8 | 9 | $("#pay_btn").bind("click", function () { 10 | detailOffer("#pay"); 11 | }); 12 | 13 | $("#transfer_btn").bind("click", function () { 14 | detailOffer("#transfer"); 15 | }); 16 | 17 | function detailOffer(id) { 18 | $("#offer").fadeOut(sec); 19 | $(id).delay(1000).fadeIn(sec, positionFooter); 20 | } 21 | 22 | 23 | 24 | function showConfirmation(func) { 25 | $("#confirmation").fadeIn(sec / 2); 26 | $("#confirm-btn-yes").bind("click", func); 27 | } 28 | 29 | function showResult(id, text) { 30 | $(id).fadeOut(sec); 31 | confirmHide(); 32 | $("#result").delay(1000).fadeIn(sec, positionFooter); 33 | $("#result_ans").html(text); 34 | } 35 | 36 | function confirmHide() { 37 | $("#confirmation").fadeOut(sec / 2); 38 | } 39 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/user/RegistrationServiceIntTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user 2 | 3 | import com.cbank.domain.RegistrationForm 4 | import com.cbank.services.RegistrationService 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.boot.test.context.SpringBootTest 7 | import spock.lang.Specification 8 | 9 | /** 10 | * @author Podshivalov N.A. 11 | * @since 22.12.2017. 12 | */ 13 | @SpringBootTest 14 | class RegistrationServiceIntTest extends Specification { 15 | 16 | @Autowired 17 | RegistrationService registrationService 18 | 19 | def "register: without exceptions"() { 20 | when: 21 | def account = registrationService.register(new RegistrationForm(name: "James Smith", address: "Ocean Avenue, 5", 22 | username: "smith65", password: "password", email: "email65@mail.com" )) 23 | then: 24 | noExceptionThrown() 25 | account?.num 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/static/js/contact.js: -------------------------------------------------------------------------------- 1 | const contactEmail = $("#contact-email"); 2 | const messageBody = $("#contact-text"); 3 | const contactName = $("#contact-name"); 4 | const err = $("#error-msg"); 5 | const contactSuccess = $("#contact-success"); 6 | const messageSendButton = $("#contact-msg"); 7 | 8 | 9 | messageSendButton.bind("click", function () { 10 | addMessage(contactEmail.val(), contactName.val(), messageBody.val())( 11 | () => { 12 | err.text(''); 13 | contactSuccess.show(); 14 | window.setTimeout(function () { 15 | redirect("/"); 16 | }, 4000); 17 | }, 18 | (xhr) => err.text(xhr.responseJSON.message) 19 | ) 20 | }); 21 | 22 | const addMessage = (email, name, body) => (success, error) => 23 | request({ 24 | url: '/feedback', 25 | type: POST, 26 | data: { 27 | email: email, 28 | name: name, 29 | body: body 30 | } 31 | }, success, error); 32 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/Client.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Table; 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 20.11.2017. 14 | */ 15 | @Data 16 | @Entity 17 | @Table(name = "clients") 18 | @EqualsAndHashCode(callSuper = false) 19 | @NoArgsConstructor 20 | public class Client extends Persistable { 21 | 22 | @Column(nullable = false) 23 | private String name; 24 | 25 | @Column(nullable = false) 26 | private String address; 27 | 28 | @Column(nullable = false) 29 | private String email; 30 | 31 | @Column(nullable = false) 32 | private String userId; 33 | 34 | Client(String name, String address, String email, String userId) { 35 | this.name = name; 36 | this.address = address; 37 | this.email = email; 38 | this.userId = userId; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/credit/CreditFactoryUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.credit 2 | 3 | import com.cbank.domain.Account 4 | import com.cbank.domain.credit.CreditState 5 | import com.cbank.domain.credit.CreditType 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author Podshivalov N.A. 10 | * @since 25.12.2017. 11 | */ 12 | class CreditFactoryUnitTest extends Specification { 13 | 14 | def creditFactory = new CreditFactory() 15 | 16 | def "create"() { 17 | given: 18 | def initialAmount = new BigDecimal(1000) 19 | when: 20 | def credit = creditFactory.create(new Account(id: 17), CreditType.PERSONAL_CREDIT, initialAmount, 5) 21 | then: 22 | credit.type == CreditType.PERSONAL_CREDIT 23 | credit.closedAt != null 24 | credit.initialAmount == initialAmount 25 | credit.state == CreditState.OPENED 26 | credit.numOfWithdraws == 5 27 | credit.accountId == 17 28 | credit.monthlySum == new BigDecimal(18) 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/static/included/forget.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 20 |
21 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/message/Message.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.message; 2 | 3 | import com.cbank.domain.Persistable; 4 | import lombok.*; 5 | import org.springframework.mail.SimpleMailMessage; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Table; 10 | 11 | 12 | @Entity 13 | @Table(name = "messages") 14 | @Data 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @EqualsAndHashCode(callSuper = true) 18 | public class Message extends Persistable{ 19 | 20 | @Column(name = "recipient", nullable = false) 21 | private String to; 22 | 23 | @Column(nullable = false) 24 | private String title; 25 | 26 | @Column(length = Integer.MAX_VALUE) 27 | private String body; 28 | 29 | public SimpleMailMessage toMailMessage(String sender) { 30 | val mailMessage = new SimpleMailMessage(); 31 | mailMessage.setTo(to); 32 | mailMessage.setFrom(sender); 33 | mailMessage.setText(body); 34 | mailMessage.setSubject(title); 35 | return mailMessage; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/Account.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain; 2 | 3 | import com.cbank.utils.RandomUtils; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.Table; 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 21.11.2017. 15 | */ 16 | @Data 17 | @Entity 18 | @Table(name = "accounts") 19 | @EqualsAndHashCode(callSuper = false) 20 | @NoArgsConstructor 21 | public class Account extends Persistable{ 22 | 23 | @Column(nullable = false) 24 | private Long clientId; 25 | 26 | @Column(unique = true, length = 16) 27 | private String num; 28 | 29 | /** 30 | * If this flag is true, all transactions expense money form current bill for account 31 | */ 32 | private Boolean current = Boolean.TRUE; 33 | 34 | public Account(Long clientId) { 35 | this.clientId = clientId; 36 | this.num = RandomUtils.generateAccountNum(); 37 | this.current = Boolean.TRUE; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/credit/CreditType.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.credit; 2 | 3 | import com.google.common.collect.Sets; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.Set; 10 | 11 | import static com.cbank.domain.credit.CreditDirection.CREDIT; 12 | import static com.cbank.domain.credit.CreditDirection.DEPOSIT; 13 | 14 | @ToString 15 | @Getter 16 | @RequiredArgsConstructor 17 | public enum CreditType { 18 | PERSONAL_CREDIT(9, CREDIT), BUSINESS_CREDIT(15, CREDIT), BUSINESS_DEPOSIT(4, DEPOSIT); 19 | 20 | public static final Set CREDITS = Sets.immutableEnumSet(PERSONAL_CREDIT, BUSINESS_CREDIT); 21 | 22 | private final int rate; 23 | private final CreditDirection direction; 24 | private BigDecimal percent; 25 | 26 | public BigDecimal getPercent(){ 27 | if (percent == null){ 28 | percent = new BigDecimal(rate ).divide( new BigDecimal(100), 2, BigDecimal.ROUND_CEILING); 29 | } 30 | return percent; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/StatementController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import com.cbank.services.TransactionService; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 21.11.2017. 15 | */ 16 | @Slf4j 17 | @Controller 18 | @AllArgsConstructor 19 | @AggregationModel 20 | @RequestMapping("/accounts/{accountNum}/statement") 21 | public class StatementController { 22 | private final TransactionService transactionService; 23 | 24 | @GetMapping 25 | public String movements(@PathVariable String accountNum, Model model) { 26 | model.addAttribute("transactions", transactionService.byAccount(accountNum)); 27 | return "included/transactions :: statement"; 28 | } 29 | 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/static/sign.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/static/js/subscribe.js: -------------------------------------------------------------------------------- 1 | $(document).ready(() => isSubscribed()); 2 | 3 | const subscribe = $("#subscribe-btn"); 4 | const unfollow = $("#unfollow-btn"); 5 | const error = $(".error-img"); 6 | const ok = $(".subscribed"); 7 | 8 | subscribe.bind("click", () => manageSubscriber(POST)); 9 | unfollow.bind("click", () => manageSubscriber(DELETE)); 10 | 11 | const manageSubscriber = (method) => request({ 12 | url: '/subscribers', 13 | type: method, 14 | data: {email: $("#subscribe-email").val()} 15 | }, 16 | () => { 17 | error.hide(); 18 | ok.show(); 19 | ok.delay(3000).fadeOut(1000); 20 | isSubscribed() 21 | }, 22 | () => { 23 | ok.hide(); 24 | error.show(); 25 | error.delay(3000).fadeOut(1000); 26 | }); 27 | 28 | 29 | const isSubscribed = () => { 30 | unfollow.hide(); 31 | subscribe.hide(); 32 | request( 33 | { 34 | url: '/subscribers', 35 | type: GET 36 | }, 37 | () => {unfollow.show()}, 38 | () => {subscribe.show()} 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/main/resources/static/included/navbar.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 19 |
20 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/FeedbackController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import com.cbank.domain.Client; 4 | import com.cbank.domain.message.Feedback; 5 | import com.cbank.services.MessageService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import lombok.val; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.Optional; 13 | 14 | @Slf4j 15 | @RestController 16 | @AllArgsConstructor 17 | @AggregationModel 18 | @RequestMapping("/feedback") 19 | public class FeedbackController { 20 | private final MessageService messageService; 21 | 22 | @PostMapping 23 | public ResponseEntity persist(@RequestBody Feedback feedback, 24 | @ModelAttribute Client client) { 25 | val toSave = Optional.ofNullable(client) 26 | .map(c -> Feedback.of(client.getName(), client.getEmail(), feedback.getBody())) 27 | .orElse(feedback); 28 | 29 | messageService.persist(toSave); 30 | return ResponseEntity.accepted().build(); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/static/js/forgot.js: -------------------------------------------------------------------------------- 1 | $("#forget_btn").bind("click", () => { 2 | let loginOrEmail = $("#email_or_login").val(); 3 | forgotPassword(loginOrEmail) 4 | }); 5 | 6 | $("#reset_btn").bind("click", () => { 7 | let password = $("#password").val(); 8 | let confirmation = $("#confirm-password").val(); 9 | if (password !== confirmation) showResult("#reset", "The passwords do not equal"); 10 | else resetPassword(password) 11 | }); 12 | 13 | const resetPassword = (password) => request({ 14 | url: "/users/password", 15 | type: POST, 16 | data: {password: password, token : getQueryVariable("token")} 17 | }, 18 | () => showResult("#reset", "New password has been set"), 19 | err => showResult("#reset", err.responseJSON.message) 20 | ); 21 | 22 | const forgotPassword = (loginOrEmail) => 23 | request({ 24 | url: '/users/password?loginOrEmail=' + loginOrEmail, 25 | type: GET, 26 | }, 27 | () => showResult("#forget", "The message has been sent to your email. Please, follow the instructions in the letter"), 28 | err => showResult("#forget", err.responseJSON.message) 29 | ); 30 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/transaction/BalanceServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.transaction; 2 | 3 | import com.cbank.repositories.AccountRepository; 4 | import com.cbank.repositories.TransactionRepository; 5 | import com.cbank.services.BalanceService; 6 | import lombok.AllArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.math.BigDecimal; 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 22.11.2017. 14 | */ 15 | @Service 16 | @AllArgsConstructor 17 | public class BalanceServiceImpl implements BalanceService { 18 | private final TransactionRepository transactionRepository; 19 | private final AccountRepository accountRepository; 20 | 21 | @Override 22 | public BigDecimal balance(String accountNum) { 23 | return transactionRepository.findAllByAccountNum(accountNum).stream() 24 | .map(tr -> tr.getRecipient().equals(accountNum) ? tr.getAmount() : tr.getAmount().negate()) 25 | .reduce(BigDecimal.ZERO, BigDecimal::add); 26 | } 27 | 28 | @Override 29 | public BigDecimal balance(Long accountId) { 30 | return balance(accountRepository.getOne(accountId)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/message/MessageTextTemplateFactoryImplIntTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.message 2 | 3 | import com.cbank.domain.Client 4 | import com.cbank.domain.message.MessageTemplate 5 | import com.cbank.domain.security.BaseToken 6 | import org.springframework.beans.factory.annotation.Autowired 7 | import org.springframework.boot.test.context.SpringBootTest 8 | import spock.lang.Specification 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 25.12.2017. 13 | */ 14 | @SpringBootTest 15 | class MessageTextTemplateFactoryImplIntTest extends Specification { 16 | 17 | @Autowired 18 | MessageTextTemplateFactoryImpl templateFactory 19 | 20 | def "create: registration template"() { 21 | when: 22 | templateFactory.create(MessageTemplate.REGISTRATION_CONFIRMATION, [token: new BaseToken(token: "1234567890"), client: new Client(name: "Mr Smith")]) 23 | then: 24 | noExceptionThrown() 25 | } 26 | 27 | def "create: access recovery"(){ 28 | when: 29 | templateFactory.create(MessageTemplate.ACCESS_RECOVERY, [token: new BaseToken(token: "1234567890")]) 30 | then: 31 | noExceptionThrown() 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/tariff/TariffResolver.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.tariff; 2 | 3 | import com.cbank.domain.transaction.Transaction; 4 | import com.cbank.services.AccountService; 5 | import com.cbank.services.TariffService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.val; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.math.BigDecimal; 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 21.11.2017. 15 | */ 16 | @Service 17 | @AllArgsConstructor 18 | public class TariffResolver implements TariffService{ 19 | 20 | @Override 21 | public BigDecimal evaluate(Transaction transaction) { 22 | if (AccountService.BANK_ACCOUNT.equals(transaction.getRecipient())){ 23 | return TariffHolder.NONE.evaluate(transaction); 24 | } 25 | 26 | val tariff = AccountService.GOVERNMENT_ACCOUNT.equals(transaction.getRecipient()) 27 | ? TariffHolder.BUDGET_TRANSACTION 28 | : AccountService.BANK_ACCOUNT.equals(transaction.getRecipient()) 29 | ? TariffHolder.NONE 30 | : TariffHolder.GENERAL_TRANSACTION; 31 | 32 | return tariff.evaluate(transaction); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/config/AuthFailureHandlerUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.config 2 | 3 | import com.cbank.services.AuthenticationService 4 | import org.springframework.mock.web.MockHttpServletRequest 5 | import org.springframework.mock.web.MockHttpServletResponse 6 | import org.springframework.security.core.userdetails.UsernameNotFoundException 7 | import org.springframework.security.web.RedirectStrategy 8 | import spock.lang.Specification 9 | 10 | class AuthFailureHandlerUnitTest extends Specification { 11 | 12 | def authenticationService = Mock(AuthenticationService) 13 | def redirectStrategy = Mock(RedirectStrategy) 14 | def authFailureHandler = new AuthFailureHandler(redirectStrategy, authenticationService) 15 | 16 | def "OnAuthenticationFailure"() { 17 | given: 18 | def login = "login" 19 | def r = new MockHttpServletRequest() 20 | r.setParameter("login", login) 21 | authenticationService.failed(login) >> AuthenticationService.UserFailStatus.WRONG 22 | when: 23 | authFailureHandler.onAuthenticationFailure(r, new MockHttpServletResponse(), new UsernameNotFoundException("text")) 24 | then: 25 | 1 * redirectStrategy.sendRedirect(*_) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/user/RegistrationServiceImplTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user 2 | 3 | import com.cbank.services.* 4 | import com.cbank.validators.RegistrationValidator 5 | import org.springframework.boot.test.context.SpringBootTest 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author Podshivalov N.A. 10 | * @since 22.12.2017. 11 | */ 12 | @SpringBootTest 13 | class RegistrationServiceImplTest extends Specification { 14 | 15 | def userService = Mock(UserService) 16 | def tokenService = Mock(TokenService) 17 | def clientService = Mock(ClientService) 18 | def accountService = Mock(AccountService) 19 | def messageService = Mock(MessageService) 20 | def registrationValidator = new RegistrationValidator(userService) 21 | 22 | def registrationService = new RegistrationServiceImpl(userService, tokenService, 23 | clientService, accountService, messageService, registrationValidator) 24 | 25 | 26 | 27 | def "confirm registration"() { 28 | given: def token = "token" 29 | when: registrationService.confirm(token) 30 | then: 1 * userService.enable(token) 31 | } 32 | 33 | def "check username or email is free"() { 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/security/RememberMeToken.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.security; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.Id; 10 | import java.util.Date; 11 | 12 | 13 | @Entity 14 | @NoArgsConstructor 15 | @Getter 16 | @EqualsAndHashCode 17 | public class RememberMeToken { 18 | 19 | @Id 20 | private String series; 21 | private String username; 22 | private String tokenValue; 23 | private Date date; 24 | 25 | public RememberMeToken(String username, String series, String tokenValue, Date date) { 26 | this.username = username; 27 | this.series = series; 28 | this.tokenValue = tokenValue; 29 | this.date = date; 30 | } 31 | 32 | public RememberMeToken(PersistentRememberMeToken token) { 33 | this(token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate()); 34 | } 35 | 36 | public PersistentRememberMeToken getPersistentToken() { 37 | return new PersistentRememberMeToken(username, series, tokenValue, date); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/SubscribeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl; 2 | 3 | 4 | import com.cbank.domain.Subscriber; 5 | import com.cbank.repositories.SubscriberRepository; 6 | import com.cbank.services.SubscribeService; 7 | import com.cbank.validators.ValidationUtils; 8 | import lombok.AllArgsConstructor; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Optional; 12 | 13 | /** 14 | * @author Podshivalov N.A. 15 | * @since 21.11.2017. 16 | */ 17 | @Service 18 | @AllArgsConstructor 19 | public class SubscribeServiceImpl implements SubscribeService { 20 | private final SubscriberRepository subscriberRepository; 21 | 22 | @Override 23 | public Subscriber subscribe(Subscriber subscriber) { 24 | ValidationUtils.email(subscriber.getEmail()); 25 | return subscriberRepository.findByEmail(subscriber.getEmail()) 26 | .orElseGet(() -> subscriberRepository.save(subscriber)); 27 | } 28 | 29 | @Override 30 | public Optional byEmail(String email) { 31 | return subscriberRepository.findByEmail(email); 32 | } 33 | 34 | @Override 35 | public void unsubscribe(String email) { 36 | subscriberRepository.deleteByEmail(email); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/UserService.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services; 2 | 3 | import com.cbank.domain.user.User; 4 | import com.google.common.hash.Hashing; 5 | import com.google.common.io.BaseEncoding; 6 | import lombok.val; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.Optional; 11 | 12 | 13 | public interface UserService extends UserDetailsService, PersistableService { 14 | 15 | Optional byUsername(String username); 16 | 17 | void resetPassword(String tokenId, String password); 18 | 19 | void enable(String token); 20 | 21 | void lock(User user); 22 | 23 | default User save(String username, String password){ 24 | val user = new User(); 25 | user.setUsername(username); 26 | return save(setPassword(user, password)); 27 | } 28 | 29 | default User setPassword(User user, String password){ 30 | user.setPassword(hashPassword(user, password)); 31 | return user; 32 | } 33 | 34 | default String hashPassword(User user, String password){ 35 | val salted = user.getSalt() + password; 36 | val hashCode = Hashing.sha256().hashBytes(salted.getBytes(StandardCharsets.UTF_8)); 37 | return BaseEncoding.base32().lowerCase().encode(hashCode.asBytes()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/SubscribeServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl 2 | 3 | import com.cbank.domain.Subscriber 4 | import com.cbank.repositories.SubscriberRepository 5 | import spock.lang.Specification 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 22.12.2017. 10 | */ 11 | class SubscribeServiceImplUnitTest extends Specification { 12 | 13 | def subscriberRepository = Mock(SubscriberRepository) 14 | def subscribeService = new SubscribeServiceImpl(subscriberRepository) 15 | 16 | def "Subscribe"() { 17 | given: 18 | def subscriber = Subscriber.of("smith65@mail.com") 19 | subscriberRepository.findByEmail(subscriber.email) >> Optional.empty() 20 | when: subscribeService.subscribe(subscriber) 21 | then: 1 * subscriberRepository.save(subscriber) 22 | } 23 | 24 | def "ByEmail"() { 25 | given: 26 | def subscriber = Subscriber.of("smith65@mail.com") 27 | subscriberRepository.findByEmail(subscriber.email) >> Optional.of(subscriber) 28 | when: def byEmail = subscribeService.byEmail(subscriber.email) 29 | then: byEmail.get() == subscriber 30 | } 31 | 32 | def "Unsubscribe"() { 33 | when: subscribeService.unsubscribe("smith65@mail.com") 34 | then: 1 * subscriberRepository.deleteByEmail("smith65@mail.com") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/UserDataAggregationAdvice.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | 4 | import com.cbank.domain.Account; 5 | import com.cbank.domain.Client; 6 | import com.cbank.services.AccountService; 7 | import com.cbank.services.ClientService; 8 | import lombok.AllArgsConstructor; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ModelAttribute; 12 | 13 | import java.util.Optional; 14 | 15 | /** 16 | * @author Podshivalov N.A. 17 | * @since 21.11.2017. 18 | */ 19 | @AllArgsConstructor 20 | @ControllerAdvice 21 | public class UserDataAggregationAdvice { 22 | private final AccountService accountService; 23 | private final ClientService clientService; 24 | 25 | 26 | @ModelAttribute 27 | public Client client(Authentication authentication) { 28 | return Optional.ofNullable(authentication) 29 | .flatMap(auth -> clientService.byUserId(auth.getName())) 30 | .orElse(null); 31 | } 32 | 33 | @ModelAttribute("account") 34 | public Account account(@ModelAttribute Client client) { 35 | return Optional.ofNullable(client) 36 | .map(Client::getId) 37 | .flatMap(accountService::current) 38 | .orElse(null); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/config/AuthFailureHandler.java: -------------------------------------------------------------------------------- 1 | package com.cbank.config; 2 | 3 | import com.cbank.services.AuthenticationService; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import lombok.val; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.web.RedirectStrategy; 9 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.servlet.ServletException; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | @Component 18 | @Slf4j 19 | @AllArgsConstructor 20 | class AuthFailureHandler implements AuthenticationFailureHandler { 21 | 22 | private RedirectStrategy redirectStrategy; 23 | private AuthenticationService failureService; 24 | 25 | @Override 26 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { 27 | val login = request.getParameter("login"); 28 | log.debug("Authentication failed for " + login + " : " + e.getMessage()); 29 | val status = failureService.failed(login); 30 | redirectStrategy.sendRedirect(request, response, "sign?error=" + status.toString()); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/validators/FeedbackValidatorUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.validators 2 | 3 | import com.cbank.domain.message.Feedback 4 | import com.cbank.exceptions.ValidationException 5 | import spock.lang.Specification 6 | 7 | /** 8 | * @author Podshivalov N.A. 9 | * @since 22.12.2017. 10 | */ 11 | class FeedbackValidatorUnitTest extends Specification { 12 | def feedbackValidator = new FeedbackValidator() 13 | 14 | def "validate: throw validation exception"() { 15 | when: 16 | feedbackValidator.validate(new Feedback(name, email , body)) 17 | then: 18 | thrown(ValidationException) 19 | where: 20 | name | email | body 21 | "Mr Smith" | "smith1873@mail.com" | null 22 | "Mr Smith" | "smith1873@mail.com" | "" 23 | "Mr Smith" | "smith1873" | "Hello! I want to invest in your bank" 24 | "M" | null | "Hello! I want to invest in your bank" 25 | "Mr^smith" | "smith1873@mail.com" | "Hello! I want to invest in your bank" 26 | null | "smith1873@mail.com" | "Hello! I want to invest in your bank" 27 | } 28 | 29 | def "validate: shouldn't throw any exception"() { 30 | when: 31 | feedbackValidator.validate(new Feedback("Mr Smith", "smith1873@mail.com", "Hello! I want to invest in your bank")) 32 | then: 33 | noExceptionThrown() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/static/js/common.js: -------------------------------------------------------------------------------- 1 | let $footer = $("#footer"); 2 | let sec = 1000; 3 | 4 | $(window).bind("load", function () { 5 | positionFooter(); 6 | resize(); 7 | }); 8 | 9 | function positionFooter() { 10 | if (($(document.body).height()) <= $(window).height()) { 11 | $footer.css({ 12 | position: "absolute", 13 | bottom: 0 14 | }) 15 | } else { 16 | $footer.css({ 17 | position: "static" 18 | }) 19 | } 20 | } 21 | 22 | $(document).ready(resize); 23 | 24 | function resize() { 25 | $(window) 26 | .scroll(positionFooter) 27 | .resize(positionFooter); 28 | } 29 | 30 | function animate(item, time, anim, delay = 0) { 31 | $.when(anim(item, time, delay)).then(function () { 32 | positionFooter(); 33 | resize(); 34 | }); 35 | } 36 | 37 | function fadeOut(item, time, delay) { 38 | item.delay(delay).fadeOut(time); 39 | } 40 | 41 | function fadeIn(item, time, delay) { 42 | item.delay(delay).fadeIn(time); 43 | } 44 | 45 | const redirect = url => window.location.replace(url); 46 | 47 | 48 | function getQueryVariable(variable) { 49 | let query = window.location.search.substring(1); 50 | let vars = query.split("&"); 51 | for (var i = 0; i < vars.length; i++) { 52 | let pair = vars[i].split("="); 53 | if (pair[0] === variable) { 54 | return pair[1]; 55 | } 56 | } 57 | return (false); 58 | } -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/security/BaseToken.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.security; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.val; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDateTime; 9 | import java.util.UUID; 10 | 11 | @NoArgsConstructor 12 | @Data 13 | @Entity 14 | @Table(name = "base_token") 15 | public class BaseToken { 16 | @Id 17 | private String token = UUID.randomUUID().toString(); 18 | 19 | @Column(nullable = false) 20 | private String username; 21 | 22 | @Column(nullable = false) 23 | private LocalDateTime createdAt = LocalDateTime.now(); 24 | 25 | @Column(nullable = false) 26 | private Boolean valid = Boolean.TRUE; 27 | 28 | @Enumerated(EnumType.STRING) 29 | @Column(nullable = false) 30 | private BaseTokenType tokenType; 31 | 32 | 33 | public static BaseToken of(String username, BaseTokenType tokenType){ 34 | val token = new BaseToken(); 35 | token.username = username; 36 | token.tokenType = tokenType; 37 | return token; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) return true; 43 | if (o == null || getClass() != o.getClass()) return false; 44 | BaseToken token1 = (BaseToken) o; 45 | return token.equals(token1.token); 46 | 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return token.hashCode(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/static/js/credit.js: -------------------------------------------------------------------------------- 1 | 2 | const credit = (type) => { 3 | if (type === BUSINESS_DEPOSIT) { 4 | let months = $("#depositMonths").val(); 5 | let amount = $("#depositAmount").val(); 6 | saveCredit(months, amount, type) 7 | }else { 8 | let months = $("#months").val(); 9 | let amount = $("#creditAmount").val(); 10 | saveCredit(months, amount, type) 11 | } 12 | }; 13 | 14 | const saveCredit = (months, amount, type) => { 15 | request( 16 | { 17 | url: '/credits', 18 | type: POST, 19 | data: { 20 | numOfMonths: months, 21 | amount: amount, 22 | type: type 23 | } 24 | }, 25 | () => showResult("#deposit", 'Successful!'), 26 | (err) => showResult("#deposit", err.responseJSON.message) 27 | ); 28 | }; 29 | 30 | $("#save_credit").bind("click", function () { 31 | let full = window.location.href; 32 | let url = full.substr(full.lastIndexOf("/") + 1); 33 | if ("personal" === url.toLowerCase()) 34 | showConfirmation(() => credit(PERSONAL_CREDIT)); 35 | else 36 | showConfirmation(() => credit(BUSINESS_CREDIT)); 37 | }); 38 | 39 | $("#save_deposit").bind("click", function () { 40 | showConfirmation(() => credit(BUSINESS_DEPOSIT)); 41 | }); 42 | 43 | const BUSINESS_CREDIT = "BUSINESS_CREDIT"; 44 | const BUSINESS_DEPOSIT = "BUSINESS_DEPOSIT"; 45 | const PERSONAL_CREDIT = "PERSONAL_CREDIT"; -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/transaction/BalanceServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.transaction 2 | 3 | import com.cbank.domain.Account 4 | import com.cbank.domain.transaction.Transaction 5 | import com.cbank.repositories.AccountRepository 6 | import com.cbank.repositories.TransactionRepository 7 | import spock.lang.Specification 8 | 9 | import static com.cbank.services.AccountService.BANK_ACCOUNT 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 22.12.2017. 14 | */ 15 | class BalanceServiceImplUnitTest extends Specification { 16 | 17 | def transactionRepository = Mock(TransactionRepository) 18 | def accountRepository = Mock(AccountRepository) 19 | def balanceService = new BalanceServiceImpl(transactionRepository, accountRepository) 20 | def accountNum = "12341234123412" 21 | def first = new Transaction(payer: BANK_ACCOUNT, recipient: accountNum, amount: new BigDecimal(145)) 22 | def second = new Transaction(payer: accountNum, recipient: BANK_ACCOUNT, amount: new BigDecimal(80)) 23 | def third = new Transaction(payer: accountNum, recipient: BANK_ACCOUNT, amount: new BigDecimal(55)) 24 | 25 | def "balance"() { 26 | given: 27 | accountRepository.getOne(1L) >> new Account(num: accountNum) 28 | transactionRepository.findAllByAccountNum(accountNum) >> [first, second, third] 29 | when: 30 | def balance = balanceService.balance(1L) 31 | then: 32 | balance == BigDecimal.TEN 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/config/AuthSuccessHandlerUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.config 2 | 3 | import com.cbank.services.AuthenticationService 4 | import org.springframework.mock.web.MockHttpServletRequest 5 | import org.springframework.mock.web.MockHttpServletResponse 6 | import org.springframework.mock.web.MockHttpSession 7 | import org.springframework.security.core.Authentication 8 | import org.springframework.security.web.RedirectStrategy 9 | import org.springframework.security.web.WebAttributes 10 | import spock.lang.Specification 11 | 12 | class AuthSuccessHandlerUnitTest extends Specification { 13 | 14 | def redirectStategy = Mock(RedirectStrategy) 15 | def authenticationService = Mock(AuthenticationService) 16 | def authSuccessHandler = new AuthSuccessHandler(authenticationService, redirectStategy) 17 | 18 | def setup(){ 19 | } 20 | 21 | def onAuthenticationSuccessTest(){ 22 | given: 23 | def login = "login" 24 | def rq = new MockHttpServletRequest() 25 | def s = new MockHttpSession() 26 | rq.setSession(s) 27 | def rp = new MockHttpServletResponse() 28 | def auth = Mock(Authentication) 29 | auth.getName() >> login 30 | when: 31 | authSuccessHandler.onAuthenticationSuccess(rq, rp, auth) 32 | then: 33 | 1 * authenticationService.success(login) 34 | 1 * redirectStategy.sendRedirect(rq, rp, _) 35 | s.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) == null 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/CreditController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | 4 | import com.cbank.domain.Account; 5 | import com.cbank.domain.credit.CreditType; 6 | import com.cbank.services.CreditService; 7 | import com.cbank.services.impl.credit.CreditFactory; 8 | import com.cbank.validators.CreditValidator; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Data; 11 | import lombok.extern.slf4j.Slf4j; 12 | import lombok.val; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import java.math.BigDecimal; 17 | 18 | import static org.springframework.http.ResponseEntity.ok; 19 | 20 | @Slf4j 21 | @RestController 22 | @AllArgsConstructor 23 | @RequestMapping("/credits") 24 | @AggregationModel 25 | public class CreditController { 26 | private final CreditService creditService; 27 | private final CreditValidator creditValidator; 28 | private final CreditFactory creditFactory; 29 | 30 | @PostMapping 31 | public ResponseEntity save(@RequestBody CreditRequest request, @ModelAttribute Account account) { 32 | val credit = creditFactory.create(account, request.type, request.amount, request.numOfMonths); 33 | creditValidator.validate(credit); 34 | return ok(creditService.create(credit)); 35 | } 36 | 37 | @Data 38 | private static class CreditRequest{ 39 | private CreditType type; 40 | private Integer numOfMonths; 41 | private BigDecimal amount; 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/transaction/TransactionAccountProjection.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.transaction; 2 | 3 | import lombok.Data; 4 | import lombok.val; 5 | 6 | import java.math.BigDecimal; 7 | import java.time.LocalDateTime; 8 | 9 | import static com.cbank.domain.transaction.TransactionDirection.IN; 10 | import static com.cbank.domain.transaction.TransactionDirection.OUT; 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 19.12.2017. 15 | */ 16 | @Data 17 | public class TransactionAccountProjection { 18 | 19 | 20 | /** 21 | * Another participant of transaction 22 | */ 23 | private String accountNum; 24 | private TransactionDirection direction; 25 | private BigDecimal amount; 26 | private LocalDateTime createdAt; 27 | private String details; 28 | 29 | public static TransactionAccountProjection from(Transaction transaction, String accountNum){ 30 | val projection = new TransactionAccountProjection(); 31 | projection.setDetails(transaction.getDetails()); 32 | projection.setCreatedAt(transaction.getCreatedAt()); 33 | projection.setAmount(transaction.getAmount()); 34 | val isPayer = accountNum.equals(transaction.getPayer()); 35 | 36 | if(isPayer){ 37 | projection.setAccountNum(transaction.getRecipient()); 38 | projection.setDirection(OUT); 39 | }else { 40 | projection.setAccountNum(transaction.getPayer()); 41 | projection.setDirection(IN); 42 | } 43 | 44 | return projection; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/TransactionController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import com.cbank.domain.Account; 4 | import com.cbank.domain.transaction.Transaction; 5 | import com.cbank.services.TransactionService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.math.BigDecimal; 13 | 14 | @Slf4j 15 | @RestController 16 | @AllArgsConstructor 17 | @AggregationModel 18 | @RequestMapping("/transactions") 19 | public class TransactionController { 20 | private final TransactionService transactionService; 21 | 22 | @PostMapping 23 | public ResponseEntity create(@RequestBody TransactionCreationRequest request, 24 | @ModelAttribute Account account) { 25 | 26 | return ResponseEntity.ok( 27 | transactionService.create(request.toTransaction(account.getNum())) 28 | ); 29 | } 30 | 31 | @Data 32 | private static class TransactionCreationRequest { 33 | private String recipient; 34 | private BigDecimal amount; 35 | private String details; 36 | 37 | Transaction toTransaction(String account) { 38 | return Transaction.builder() 39 | .payer(account) 40 | .recipient(recipient) 41 | .amount(amount) 42 | .details(details) 43 | .build(); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/config/AuthSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.cbank.config; 2 | 3 | 4 | import com.cbank.services.AuthenticationService; 5 | import lombok.AllArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import lombok.val; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.web.RedirectStrategy; 10 | import org.springframework.security.web.WebAttributes; 11 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | 18 | @Component 19 | @Slf4j 20 | @AllArgsConstructor 21 | class AuthSuccessHandler implements AuthenticationSuccessHandler { 22 | 23 | private static final int SESSION_TIME = 50000; 24 | 25 | private final AuthenticationService authenticationService; 26 | private final RedirectStrategy redirectStrategy; 27 | 28 | @Override 29 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { 30 | authenticationService.success(authentication.getName()); 31 | val session = request.getSession(); 32 | session.setMaxInactiveInterval(SESSION_TIME); 33 | redirectStrategy.sendRedirect(request, response, "/"); 34 | session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); 35 | log.debug("Authentication was successful for " + authentication.getName()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/credit/CreditFactory.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.credit; 2 | 3 | import com.cbank.domain.Account; 4 | import com.cbank.domain.credit.Credit; 5 | import com.cbank.domain.credit.CreditState; 6 | import com.cbank.domain.credit.CreditType; 7 | import lombok.val; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.math.BigDecimal; 11 | import java.time.LocalDateTime; 12 | 13 | import static java.math.BigDecimal.ROUND_CEILING; 14 | 15 | /** 16 | * @author Podshivalov N.A. 17 | * @since 20.12.2017. 18 | */ 19 | @Component 20 | public class CreditFactory { 21 | 22 | public Credit create(Account account, CreditType type, BigDecimal initialAmount, Integer numOfMonths){ 23 | val credit = new Credit(); 24 | credit.setState(CreditState.OPENED); 25 | credit.setInitialAmount(initialAmount); 26 | credit.setAccountId(account.getId()); 27 | credit.setNumOfWithdraws(numOfMonths); 28 | credit.setType(type); 29 | credit.setMonthlySum(calculateMonthlyWithdrawAmount(type, initialAmount, numOfMonths)); 30 | credit.setClosedAt(LocalDateTime.now().plusMonths(numOfMonths)); 31 | return credit; 32 | } 33 | 34 | private BigDecimal calculateMonthlyWithdrawAmount(CreditType type, BigDecimal initialAmount, Integer numOfMonths){ 35 | val rate = type.getPercent(); 36 | val months = new BigDecimal(numOfMonths); 37 | val initialPerMonth = initialAmount.divide(months, 2, ROUND_CEILING); 38 | return initialPerMonth.multiply(rate); 39 | } 40 | 41 | 42 | 43 | 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/repositories/RememberMeTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.cbank.repositories; 2 | 3 | import com.cbank.domain.security.RememberMeToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Modifying; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; 8 | import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.Date; 12 | import java.util.Optional; 13 | 14 | public interface RememberMeTokenRepository extends JpaRepository, PersistentTokenRepository { 15 | 16 | 17 | @Override 18 | default void createNewToken(PersistentRememberMeToken token) { 19 | save(new RememberMeToken(token)); 20 | } 21 | 22 | 23 | @Override 24 | @Modifying 25 | @Transactional 26 | @Query("UPDATE RememberMeToken t SET t.tokenValue = ?2, t.date= ?3 WHERE t.series = ?1") 27 | void updateToken(String series, String tokenValue, Date lastUsed); 28 | 29 | 30 | @Override 31 | default PersistentRememberMeToken getTokenForSeries(String series) { 32 | return Optional.ofNullable(findOne(series)) 33 | .map(RememberMeToken::getPersistentToken) 34 | .orElse(null); 35 | } 36 | 37 | @Override 38 | @Modifying 39 | @Transactional 40 | @Query("DELETE FROM RememberMeToken t WHERE t.username = ?1") 41 | void removeUserTokens(String username); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/tariff/TariffHolder.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.tariff; 2 | 3 | import com.cbank.domain.transaction.Transaction; 4 | import com.cbank.services.TariffService; 5 | import lombok.val; 6 | 7 | import java.math.BigDecimal; 8 | 9 | import static java.math.BigDecimal.*; 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 21.11.2017. 14 | */ 15 | public enum TariffHolder implements TariffService { 16 | BUDGET_TRANSACTION { 17 | 18 | @Override 19 | public BigDecimal evaluate(Transaction transaction) { 20 | return TEN; 21 | } 22 | 23 | 24 | 25 | }, GENERAL_TRANSACTION { 26 | private final BigDecimal FIFTY = BigDecimal.valueOf(50); 27 | private final BigDecimal ONE_THOUSAND = BigDecimal.valueOf(1000); 28 | private final BigDecimal ONE_HUNDRED = new BigDecimal(100); 29 | 30 | private BigDecimal commission(BigDecimal amount, int rate){ 31 | return amount.multiply(BigDecimal.valueOf(rate).divide(ONE_HUNDRED, 2, ROUND_CEILING)); 32 | } 33 | 34 | @Override 35 | public BigDecimal evaluate(Transaction transaction) { 36 | val amount = transaction.getAmount(); 37 | return amount.compareTo(FIFTY) < 0 38 | ? commission(amount, 3) 39 | : amount.compareTo(ONE_THOUSAND) < 0 40 | ? commission(amount, 7) 41 | : commission(amount, 15); 42 | } 43 | }, 44 | 45 | NONE { 46 | @Override 47 | public BigDecimal evaluate(Transaction transaction) { 48 | return ZERO; 49 | } 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/NavigationController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import com.cbank.domain.Client; 4 | import com.cbank.services.AccountService; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | 11 | /** 12 | * Contains only get methods for navigation 13 | */ 14 | 15 | @Controller 16 | @AllArgsConstructor 17 | public class NavigationController { 18 | private final AccountService accountService; 19 | 20 | @GetMapping("/") 21 | public String init() { 22 | return "index"; 23 | } 24 | 25 | @GetMapping("/personal") 26 | public String personal() { 27 | return "personal"; 28 | } 29 | 30 | @GetMapping("/business") 31 | public String business() { 32 | return "business"; 33 | } 34 | 35 | @GetMapping("/contact") 36 | public String investors() { 37 | return "contact"; 38 | } 39 | 40 | @GetMapping("/sign") 41 | public String signIn() { 42 | return "sign"; 43 | } 44 | 45 | @GetMapping("/forgot") 46 | public String reset() { 47 | return "reset"; 48 | } 49 | 50 | @GetMapping("/confirm") 51 | public String confirm() { 52 | return "confirm"; 53 | } 54 | 55 | @PreAuthorize("isAuthenticated()") 56 | @GetMapping("/private") 57 | public String dbo(Client client, Model model) { 58 | model.addAttribute("accounts", accountService.byClient(client.getId())); 59 | return "private"; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/credit/CreditServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.credit; 2 | 3 | import com.cbank.domain.credit.Credit; 4 | import com.cbank.domain.credit.CreditState; 5 | import com.cbank.repositories.AccountRepository; 6 | import com.cbank.repositories.CreditRepository; 7 | import com.cbank.services.CreditService; 8 | import com.cbank.services.TransactionService; 9 | import lombok.AllArgsConstructor; 10 | import lombok.val; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import java.time.LocalDateTime; 15 | 16 | /** 17 | * @author Podshivalov N.A. 18 | * @since 20.12.2017. 19 | */ 20 | @Service 21 | @AllArgsConstructor 22 | public class CreditServiceImpl implements CreditService { 23 | private final CreditRepository creditRepository; 24 | private final TransactionService transactionService; 25 | private final AccountRepository accountRepository; 26 | 27 | @Override 28 | @Transactional 29 | public Credit create(Credit credit){ 30 | val account = accountRepository.findOne(credit.getAccountId()); 31 | transactionService.credit(account, credit); 32 | return creditRepository.save(credit); 33 | } 34 | 35 | @Override 36 | @Transactional 37 | public void withdraw(Credit credit){ 38 | val account = accountRepository.findOne(credit.getAccountId()); 39 | transactionService.creditWithdraw(account, credit); 40 | 41 | if(LocalDateTime.now().isAfter(credit.getClosedAt())){ 42 | credit.setState(CreditState.CLOSED); 43 | creditRepository.save(credit); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/user/AuthenticationServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user 2 | 3 | import com.cbank.domain.user.User 4 | import com.cbank.services.UserService 5 | import spock.lang.Specification 6 | 7 | import static com.cbank.services.AuthenticationService.UserFailStatus.BLOCK 8 | import static com.cbank.services.AuthenticationService.UserFailStatus.WRONG 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 22.12.2017. 13 | */ 14 | class AuthenticationServiceImplUnitTest extends Specification { 15 | 16 | def userService = Mock(UserService) 17 | def authenticationService = new AuthenticationServiceImpl(userService) 18 | def username = "smith56" 19 | def user = new User(username: username) 20 | 21 | def "failed: user was not found"() { 22 | given: userService.byUsername(username) >> Optional.empty() 23 | when: def fail = authenticationService.failed(username) 24 | then: fail == WRONG 25 | } 26 | 27 | 28 | def "failed: password is incorrect"() { 29 | given: userService.byUsername(username) >> Optional.of(user) 30 | when: def fail = authenticationService.failed(username) 31 | then: fail == WRONG 32 | } 33 | 34 | def "failed: attemps are exceed"() { 35 | given: 36 | userService.byUsername(username) >> Optional.of(user) 37 | authenticationService.attemptsRepository.put(username, 3) 38 | when: def fail = authenticationService.failed(username) 39 | then: fail == BLOCK 40 | } 41 | 42 | def cleanup(){ 43 | authenticationService.attemptsRepository.clear() 44 | } 45 | 46 | def "success"() { 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | language: java 5 | jdk: oraclejdk8 6 | env: 7 | global: 8 | - secure: "MC94nxxyqZF2z+BGMdLXieFtiDsZlrcbVTOwHszGdShAKDIemaOnzNPw7j45NnXUzkLklxnqUUJmjgN/qLvaG+tAlXGpzHqs6+IO/BWOS6yYIzVPe2UYnF3Uxvp3TEhAuc/fpbLE2JoPEtcgm6g09ccBFpus2x+6mqSZuJePd0wyX1HnDH/AxA+w5OpuPsrfpN90P1hSssPlqAEywOK1HSB9zKnMYHHcb+i38Qhk5wk/YJ+CXBuBWb3d7GNQHwy6leMeuKs9Wv8Z50EkvCvEJUmHvTVTXR5EgxoGNuo/Sr/jGs2S6lr9j1hUXp9l4VvCfIlebvnnNl5VOuJtL9r5CAXPCQbZX7AuEAai5uTKgX2LLHlPSJ2FaT+W9u3DOPoNoE7enrtQeDcIxtiWCr6v+qU/FPEtoNLx+/2yUIVsYFK2MSz0ty4P460aaiHPGyqSjyZoHXLoXHqejcajUFg0j6awuE3yHI2Z3msau7R35NIw9EYoIoUhPDBj85+PqLXUD7ixFeI2G0mkNp/6gCDFuTnN6zUNyUS3gCjsvc8rskxwpBierGJA+bIxaSIkruKkUC4PtCbehBFQyP75SWOetdIf8ZAj+iFnzXe7xBzkCIEbH21GSLqecFX4f/KiiOG2Ht56+/pnHxdOPIOUkZCJgc3PD/SW1mDBvqf2ulmguH8=" # DOCKER_USERNAME 9 | - secure: "JmidX0NV07KKpa9BOclukmJ7KTeEK/1wkmp9JtkjtKTqrFrRWXJqmfE+jlmSXyZfbg5zWvTf8xKBamlnXq5xOoeCFl987ZqLCA/2vCSG4jgCoa5rlq/2pK7jAagSYvGnW25n4DpBdzh/WC2EWKuB6uFGrESVckjrX3OQpfJ8PtVPClCBy01gQB4doJjvOlArQapoFuS0h6kA+Tch/Lz0QCpB3JHOXJAVd2eMj7CxpF8SGUiU9nSmSevuFwkftAa7ny6SxFdVwHQDA0X7+46QJlPqDCZmk3RNJzBIfrh2GcxvTr5+JTImpQOxBntlZPu9vH8ZVT9sQmBLt6X65tpye4iZR3R10LlkLfFyviQKtVjNWqvf96fnlMabfgqDpFF9tOKr89KOMg2OfxSUrwC7LIOY1DobxWm8P9uJj2XpY83aQnvIezY0uOOcZnD2fy5WlLEISg4Bv9enMP5UfVT9PSW/cH4Aue3QCOoYDDeASPNAvB6P0NzgkSj4kC0Kr3qbpxsLW3tBd3Ky+WLz3btnihOCkvNpB96fY8B2/0XDde699Z3K9AFtMeEZpq+wwP9UfVC3kesJNlJg4lVND4SShs3MTX/7mMnU1F9RHMf+r4va2xhc8Y79hKE4e8XrSEHRLdrn4YmnU5xdzQMsHdZyEWk+9GcZNSzKRcQ19rgTFpo=" # DOCKER_PASSWORD 10 | after_success: 11 | - bash <(curl -s https://codecov.io/bash) 12 | - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD 13 | - docker build -t nikitap/countrybank:latest . 14 | - docker push nikitap/countrybank -------------------------------------------------------------------------------- /src/main/java/com/cbank/validators/ValidationUtils.java: -------------------------------------------------------------------------------- 1 | package com.cbank.validators; 2 | 3 | 4 | import com.cbank.exceptions.ValidationException; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * @author Podshivalov N.A. 11 | * @since 21.11.2017. 12 | */ 13 | public class ValidationUtils { 14 | 15 | private static final String EMAIL_REGEX = "[_A-Za-z0-9-+]+(\\.[_A-Za-z0-9-]+)*@" + 16 | "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; 17 | private static final Pattern emailPattern = Pattern.compile(EMAIL_REGEX); 18 | 19 | private static final String NAME_REGEX = "^[a-zA-Z_ ]{5,}"; 20 | private static final Pattern namePattern = Pattern.compile(NAME_REGEX); 21 | 22 | private static final String ACCOUNT_REGEX = "^[0-9]{16}$"; 23 | private static final Pattern accountPattern = Pattern.compile(ACCOUNT_REGEX); 24 | 25 | public static void email(String email) { 26 | if (email == null || !emailPattern.matcher(email).matches()) throw new ValidationException("The email is incorrect"); 27 | } 28 | 29 | public static void name(String name) { 30 | if (name == null ||!namePattern.matcher(name).matches()) throw new ValidationException("The name is incorrect or too short"); 31 | } 32 | 33 | public static void account(String account) { 34 | if (account == null || !accountPattern.matcher(account).matches()) 35 | throw new ValidationException(String.format("The account %s is incorrect", account)); 36 | } 37 | 38 | public static void zeroAmount(BigDecimal amount) { 39 | if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) 40 | throw new ValidationException("Amount should be great than 0"); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/user/AuthenticationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user; 2 | 3 | 4 | import com.cbank.services.AuthenticationService; 5 | import com.cbank.services.UserService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | @Slf4j 15 | @Service 16 | @AllArgsConstructor 17 | public class AuthenticationServiceImpl implements AuthenticationService { 18 | private static final Map attemptsRepository = new ConcurrentHashMap<>(); 19 | private final UserService userService; 20 | 21 | @Override 22 | public UserFailStatus failed(String login) { 23 | log.debug("#failed({})", login); 24 | return userService.byUsername(login) 25 | .map(user -> { 26 | int attempt = attempts(user.getUsername()); 27 | if (++attempt > MAX_ATTEMPTS) { 28 | userService.lock(user); 29 | return UserFailStatus.BLOCK; 30 | } else { 31 | attemptsRepository.put(login, attempt); 32 | return UserFailStatus.WRONG; 33 | } 34 | }).orElse(UserFailStatus.WRONG); 35 | } 36 | 37 | 38 | private int attempts(String login){ 39 | return Optional.ofNullable(attemptsRepository.get(login)).orElse(0); 40 | } 41 | 42 | 43 | @Override 44 | public void success(String login) { 45 | log.debug("#success({})", login); 46 | attemptsRepository.remove(login); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/static/included/transactions.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Account's transactions

4 |
    5 |
  • 6 |
    7 |
    8 |
    9 | 11 | 13 |
    14 |

    16 |
    17 |

    18 |
    19 |
    20 |

    21 |
    22 |
    23 |

    24 |
    25 |
    26 |
    27 |
  • 28 |
29 |
30 |
31 |

Account has no transaction

32 |
33 |
-------------------------------------------------------------------------------- /src/main/resources/static/js/validate.js: -------------------------------------------------------------------------------- 1 | const EMAIL = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i; 2 | const ALPHA_NUMERIC = /^[A-Za-z]{4,}$/i; 3 | 4 | const confirmPasswordHint = $("#confirm-password-hint"); 5 | const confirmPassword = $("#confirm-password"); 6 | const emailHint = $("#email-hint"); 7 | const loginHint = $("#username-hint"); 8 | 9 | const validateText= (val, id) => { 10 | let hint = $('#' + id + '-hint'); 11 | let result = val.length >= 5 ? "fa-check" : "fa-close"; 12 | classesSwap(hint, "inv fa-close fa-check", result); 13 | }; 14 | 15 | const validateConfirmPass = () => { 16 | confirmPasswordHint.removeClass("inv"); 17 | let result = password.val() === confirmPassword.val() ? "fa-check" : "fa-close"; 18 | classesSwap(confirmPasswordHint, "fa-close fa-check", result) 19 | }; 20 | 21 | const validateLogin = val => { 22 | classesSwap(loginHint, "inv fa-close fa-check", "fa-spinner fa-spin"); 23 | if (val && ALPHA_NUMERIC.test(val)) checkToExist(loginHint, val); 24 | else classesSwap(loginHint, "fa-spinner fa-spin", "fa-close") 25 | }; 26 | 27 | const validateEmail = val => { 28 | classesSwap(emailHint, "inv fa-close fa-check", "fa-spinner fa-spin"); 29 | if (val && EMAIL.test(val)) checkToExist(emailHint, val); 30 | else classesSwap(emailHint, "fa-spinner fa-spin", "fa-close") 31 | }; 32 | 33 | const checkToExist = (hint, usernameOrEmail) => 34 | request({ 35 | url: '/users/registration?usernameOrEmail=' + usernameOrEmail, 36 | type: GET 37 | }, () => classesSwap(hint, "fa-spinner fa-spin", "fa-check"), 38 | () => classesSwap(hint, "fa-spinner fa-spin", "fa-close") 39 | ); 40 | 41 | const classesSwap = (target, from, to) => { 42 | target.removeClass(from); 43 | target.addClass(to); 44 | }; -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/tariff/TariffResolverUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.tariff 2 | 3 | import com.cbank.domain.transaction.Transaction 4 | import com.cbank.services.AccountService 5 | import spock.lang.Shared 6 | import spock.lang.Specification 7 | 8 | import java.math.RoundingMode 9 | 10 | import static java.math.BigDecimal.TEN 11 | import static java.math.BigDecimal.ZERO 12 | 13 | /** 14 | * @author Podshivalov N.A. 15 | * @since 25.12.2017. 16 | */ 17 | class TariffResolverUnitTest extends Specification { 18 | 19 | 20 | def tariffResolver = new TariffResolver() 21 | 22 | @Shared 23 | def accountNum = "123456789076821" 24 | 25 | def "evaluate"() { 26 | when: 27 | def evaluated = tariffResolver.evaluate(new Transaction(recipient: recipient, amount: amount, payer: "1234567890")) 28 | then: 29 | evaluated.compareTo(commission) == 0 30 | where: 31 | recipient | amount | commission 32 | AccountService.GOVERNMENT_ACCOUNT | new BigDecimal(123_456) | TEN 33 | AccountService.GOVERNMENT_ACCOUNT | new BigDecimal(500) | TEN 34 | AccountService.GOVERNMENT_ACCOUNT | new BigDecimal(9_999_999) | TEN 35 | AccountService.BANK_ACCOUNT | new BigDecimal(123_456) | ZERO 36 | AccountService.BANK_ACCOUNT | new BigDecimal(500) | ZERO 37 | AccountService.BANK_ACCOUNT | new BigDecimal(9_999_999) | ZERO 38 | accountNum | new BigDecimal(100) | new BigDecimal(7) 39 | accountNum | new BigDecimal(40) | new BigDecimal(1.2).setScale(2, RoundingMode.CEILING) 40 | accountNum | new BigDecimal(15000) | new BigDecimal(2250) 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/resources/static/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | // Only display content to screen readers. A la Bootstrap 4. 29 | // 30 | // See: http://a11yproject.com/posts/how-to-hide-content/ 31 | 32 | .sr-only() { 33 | position: absolute; 34 | width: 1px; 35 | height: 1px; 36 | padding: 0; 37 | margin: -1px; 38 | overflow: hidden; 39 | clip: rect(0, 0, 0, 0); 40 | border: 0; 41 | } 42 | 43 | // Use in conjunction with .sr-only to only display content when it's focused. 44 | // 45 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 46 | // 47 | // Credit: HTML5 Boilerplate 48 | 49 | .sr-only-focusable() { 50 | &:active, 51 | &:focus { 52 | position: static; 53 | width: auto; 54 | height: auto; 55 | margin: 0; 56 | overflow: visible; 57 | clip: auto; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/resources/static/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | // Only display content to screen readers. A la Bootstrap 4. 29 | // 30 | // See: http://a11yproject.com/posts/how-to-hide-content/ 31 | 32 | @mixin sr-only { 33 | position: absolute; 34 | width: 1px; 35 | height: 1px; 36 | padding: 0; 37 | margin: -1px; 38 | overflow: hidden; 39 | clip: rect(0, 0, 0, 0); 40 | border: 0; 41 | } 42 | 43 | // Use in conjunction with .sr-only to only display content when it's focused. 44 | // 45 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 46 | // 47 | // Credit: HTML5 Boilerplate 48 | 49 | @mixin sr-only-focusable { 50 | &:active, 51 | &:focus { 52 | position: static; 53 | width: auto; 54 | height: auto; 55 | margin: 0; 56 | overflow: visible; 57 | clip: auto; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/AccountController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | 4 | import com.cbank.domain.Account; 5 | import com.cbank.services.AccountService; 6 | import com.cbank.services.BalanceService; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.math.BigDecimal; 14 | 15 | import static org.springframework.http.ResponseEntity.ok; 16 | 17 | /** 18 | * Contains API for private room 19 | */ 20 | @Slf4j 21 | @RestController 22 | @AllArgsConstructor 23 | @AggregationModel 24 | @RequestMapping("/accounts") 25 | public class AccountController { 26 | private final AccountService accountService; 27 | private final BalanceService balanceService; 28 | 29 | @PostMapping 30 | public ResponseEntity add(@ModelAttribute("account") Account current) { 31 | return ok(accountService.create(current)); 32 | } 33 | 34 | @PatchMapping 35 | public ResponseEntity setCurrent(@RequestBody MarkAsCurrentRequest request, 36 | @ModelAttribute("account") Account current) { 37 | return ok(accountService.asCurrent(current, request.accountNum)); 38 | } 39 | 40 | @GetMapping("/{accountNum}/balance") 41 | public ResponseEntity balance(@PathVariable String accountNum){ 42 | //todo check whether the account number is belonged to user 43 | return ok(BalanceContainer.of(balanceService.balance(accountNum))); 44 | } 45 | 46 | @Data 47 | private static class MarkAsCurrentRequest{ 48 | private String accountNum; 49 | } 50 | 51 | @Data 52 | @AllArgsConstructor(staticName = "of") 53 | private static class BalanceContainer{ 54 | private BigDecimal balance; 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import com.cbank.exceptions.InsufficientFundsException; 4 | import com.cbank.exceptions.TokenExpiredException; 5 | import com.cbank.exceptions.ValidationException; 6 | import lombok.Data; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | 12 | import javax.persistence.EntityNotFoundException; 13 | import java.time.LocalDateTime; 14 | 15 | /** 16 | * @author Podshivalov N.A. 17 | * @since 21.11.2017. 18 | */ 19 | @ControllerAdvice 20 | public class ErrorController { 21 | 22 | @ExceptionHandler(ValidationException.class) 23 | public ResponseEntity handle(ValidationException ex){ 24 | return ResponseEntity.badRequest().body(ErrorMessage.create(ex.getMessage())); 25 | } 26 | 27 | @ExceptionHandler(EntityNotFoundException.class) 28 | public ResponseEntity handle(EntityNotFoundException ex){ 29 | return ResponseEntity.notFound().build(); 30 | } 31 | 32 | @ExceptionHandler(TokenExpiredException.class) 33 | public ResponseEntity handle(TokenExpiredException ex){ 34 | return ResponseEntity.badRequest().body(ErrorMessage.create("The token is expired. Get new token again, please")); 35 | } 36 | 37 | @ExceptionHandler(InsufficientFundsException.class) 38 | public ResponseEntity handle(InsufficientFundsException ex){ 39 | return ResponseEntity.badRequest().body(ErrorMessage.create("Insufficient funds. Please, fund money on your account")); 40 | } 41 | 42 | @Data 43 | @RequiredArgsConstructor(staticName = "create") 44 | private static class ErrorMessage{ 45 | private final String message; 46 | private LocalDateTime timestamps = LocalDateTime.now(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/message/MessageServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.message 2 | 3 | import com.cbank.config.MailProperties 4 | import com.cbank.domain.message.Feedback 5 | import com.cbank.domain.message.Message 6 | import com.cbank.domain.message.MessageTemplate 7 | import com.cbank.repositories.FeedbackRepository 8 | import com.cbank.repositories.MessageRepository 9 | import com.cbank.validators.FeedbackValidator 10 | import org.springframework.mail.javamail.JavaMailSender 11 | import spock.lang.Specification 12 | 13 | /** 14 | * @author Podshivalov N.A. 15 | * @since 25.12.2017. 16 | */ 17 | class MessageServiceImplUnitTest extends Specification { 18 | 19 | def mailSender = Mock(JavaMailSender) 20 | def messageTemplateFactory = Mock(MessageTemplateFactory) 21 | def feedbackValidator = Mock(FeedbackValidator) 22 | def feedbackRepository = Mock(FeedbackRepository) 23 | def messageRepository = Mock(MessageRepository) 24 | def mailProperties = new MailProperties() 25 | def messageService = new MessageServiceImpl(mailSender, messageTemplateFactory, feedbackValidator, 26 | feedbackRepository, messageRepository, mailProperties) 27 | 28 | def "send"() { 29 | given: 30 | messageTemplateFactory.create(*_) >> "template" 31 | mailProperties.enable = true 32 | when: messageService.send("recipient", MessageTemplate.ACCESS_RECOVERY, [:]) 33 | then: 34 | 1 * messageRepository.save(_) 35 | 1 * mailSender.send(*_) 36 | } 37 | 38 | def "persist"() { 39 | given: def feedback = new Feedback("name", "email", "body") 40 | when: messageService.persist(feedback) 41 | then: 1 * feedbackRepository.save(feedback) 42 | } 43 | 44 | def "save"() { 45 | given: def message = new Message("to", "title", "body") 46 | when: messageService.save(message) 47 | then: 1 * messageRepository.save(message) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/SubscriberController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | 4 | import com.cbank.domain.Client; 5 | import com.cbank.domain.Subscriber; 6 | import com.cbank.services.SubscribeService; 7 | import lombok.AllArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import lombok.val; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.access.prepost.PreAuthorize; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.util.Optional; 15 | 16 | @Slf4j 17 | @RestController 18 | @AllArgsConstructor 19 | @AggregationModel 20 | @RequestMapping("/subscribers") 21 | public class SubscriberController { 22 | private SubscribeService subscribeService; 23 | 24 | /** 25 | * Saving new anonymous subscribed or {@param account} 26 | */ 27 | @PostMapping 28 | public ResponseEntity subscribe(@RequestBody Subscriber subscriber, 29 | @ModelAttribute Client client) { 30 | 31 | val toSave = Optional.ofNullable(client) 32 | .map(Client::getEmail) 33 | .map(Subscriber::of) 34 | .orElse(subscriber); 35 | 36 | return ResponseEntity.ok(subscribeService.subscribe(toSave)); 37 | } 38 | 39 | @PreAuthorize("isAuthenticated()") 40 | @DeleteMapping 41 | public ResponseEntity deleteSubscriber(@ModelAttribute Client client) { 42 | log.debug("Deleting subscriber by client: " + client); 43 | subscribeService.unsubscribe(client.getEmail()); 44 | return ResponseEntity.accepted().build(); 45 | } 46 | 47 | @GetMapping 48 | public ResponseEntity exists(@ModelAttribute Client client) { 49 | return Optional.ofNullable(client) 50 | .flatMap((c) -> subscribeService.byEmail(c.getEmail())) 51 | .map((s) -> ResponseEntity.ok().build()) 52 | .orElse(ResponseEntity.notFound().build()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/config/Sha256AuthenticationProviderUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.config 2 | 3 | import com.cbank.domain.user.User 4 | import com.cbank.exceptions.AuthenticationProcessException 5 | import com.cbank.services.UserService 6 | import org.springframework.security.authentication.TestingAuthenticationToken 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken 8 | import spock.lang.Specification 9 | 10 | import static java.util.Optional.empty 11 | import static java.util.Optional.of 12 | 13 | class Sha256AuthenticationProviderUnitTest extends Specification { 14 | 15 | def userService = Mock(UserService) 16 | def authProvider = new Sha256AuthenticationProvider(userService) 17 | 18 | def "authenticate: throw exception"() { 19 | given: 20 | 21 | when: 22 | userService.byUsername("login") >> users 23 | userService.hashPassword(*_) >> "otherPassword" 24 | authProvider.authenticate(new TestingAuthenticationToken("login", "password")) 25 | then: 26 | thrown(AuthenticationProcessException) 27 | where: 28 | users << [empty(), of(new User(nonLocked: true, enabled: false)), 29 | of(new User(nonLocked: false, enabled: true)), of(new User(nonLocked: true, enabled: true, password: "P@ssW0rD"))] 30 | 31 | } 32 | 33 | def "authenticate: ok"() { 34 | given: 35 | def user = new User(nonLocked: true, enabled: true, password: "P@ssW0rD") 36 | userService.byUsername("login") >> of(user) 37 | userService.hashPassword(user, "password") >> "P@ssW0rD" 38 | when: 39 | authProvider.authenticate(new TestingAuthenticationToken("login", "password")) 40 | then: 41 | noExceptionThrown() 42 | 43 | } 44 | 45 | def "Supports"() { 46 | expect: 47 | !authProvider.supports(Object) 48 | and: 49 | authProvider.supports(UsernamePasswordAuthenticationToken) 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/resources/static/js/transaction.js: -------------------------------------------------------------------------------- 1 | let balanceHolder = $("#balance"); 2 | let statementSection = $('#statement'); 3 | let setterBtn = $("#current-setter-btn"); 4 | let footer = $("#footer"); 5 | let transactionElement = $("#mov"); 6 | let accountNumElement = $("#account-num"); 7 | 8 | const commit = (accountNum, amount, details) => { 9 | request( 10 | { 11 | url: '/transactions', 12 | type: POST, 13 | data: { 14 | amount: amount, 15 | recipient: accountNum, 16 | details: details 17 | } 18 | }, 19 | (data) => showResult("#transfer", "Transaction was successfully saved"), 20 | (err) => showResult("#transfer", err.responseJSON.message) 21 | ) 22 | }; 23 | 24 | $("#commit").bind("click", function () { 25 | let accountNum = $("#accountNum").val(); 26 | let amount = $("#amount").val(); 27 | let details = $("#details").val(); 28 | showConfirmation(() => commit(accountNum, amount, details)); 29 | }); 30 | 31 | $("#commitPayment").bind("click", function () { 32 | let amount = $("#paymentAmount").val(); 33 | showConfirmation(() => commit(governmentAccountNum, amount)); 34 | }); 35 | 36 | const statement = (accountNum, flag) => { 37 | 38 | footer.addClass("footer"); 39 | transactionElement.load("/accounts/" + accountNum + "/statement"); 40 | 41 | balance(accountNum); 42 | accountNumElement.text(accountNum); 43 | 44 | if (flag) setterBtn.hide(); 45 | else setterBtn.show(); 46 | 47 | statementSection.fadeIn(100); 48 | $("html, body").animate({scrollTop: statementSection.offset().top}, 'slow'); 49 | }; 50 | 51 | const balance = (accountNum) => { 52 | request( 53 | { 54 | url: '/accounts/' + accountNum + '/balance', 55 | type: GET 56 | }, 57 | (data) => balanceHolder.text(data.balance), (err) => {console.log(err.text)} 58 | ) 59 | }; 60 | 61 | const governmentAccountNum = "9999999999999999"; -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/ClientServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl; 2 | 3 | import com.cbank.domain.Client; 4 | import com.cbank.domain.message.MessageTemplate; 5 | import com.cbank.domain.security.BaseTokenType; 6 | import com.cbank.repositories.ClientRepository; 7 | import com.cbank.services.ClientService; 8 | import com.cbank.services.MessageService; 9 | import com.cbank.services.TokenService; 10 | import com.cbank.utils.MapUtils; 11 | import lombok.AllArgsConstructor; 12 | import lombok.val; 13 | import org.springframework.stereotype.Service; 14 | 15 | import javax.persistence.EntityNotFoundException; 16 | import java.util.Optional; 17 | 18 | import static com.google.common.collect.Maps.immutableEntry; 19 | 20 | /** 21 | * @author Podshivalov N.A. 22 | * @since 21.11.2017. 23 | */ 24 | @Service 25 | @AllArgsConstructor 26 | public class ClientServiceImpl implements ClientService { 27 | private final ClientRepository clientRepository; 28 | private final TokenService tokenService; 29 | private final MessageService messageService; 30 | 31 | @Override 32 | public Client save(Client client) { 33 | return clientRepository.save(client); 34 | } 35 | 36 | @Override 37 | public Optional byUserId(String userId) { 38 | return clientRepository.findByUserId(userId); 39 | } 40 | 41 | @Override 42 | public void accessRecovery(String loginOrEmail) { 43 | val client = clientRepository.findByUserId(loginOrEmail) 44 | .orElseGet(() -> clientRepository.findByEmail(loginOrEmail) 45 | .orElseThrow(EntityNotFoundException::new)); //java 9 Optional#or 46 | 47 | val token = tokenService.create(client.getUserId(), BaseTokenType.RESET_PASSWORD); 48 | messageService.send(client.getEmail(), MessageTemplate.ACCESS_RECOVERY, 49 | MapUtils.from( 50 | immutableEntry("token", token), 51 | immutableEntry("client", client) 52 | ) 53 | ); 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/static/included/signin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 32 |
33 |
-------------------------------------------------------------------------------- /src/main/java/com/cbank/config/Sha256AuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.cbank.config; 2 | 3 | import com.cbank.domain.user.User; 4 | import com.cbank.exceptions.AuthenticationProcessException; 5 | import com.cbank.services.UserService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.val; 8 | import org.springframework.security.authentication.AuthenticationProvider; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.AuthenticationException; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.Collections; 15 | 16 | /** 17 | * @author Podshivalov N.A. 18 | * @since 23.11.2017. 19 | */ 20 | @Component 21 | @AllArgsConstructor 22 | public class Sha256AuthenticationProvider implements AuthenticationProvider{ 23 | private final UserService userService; 24 | 25 | @Override 26 | public Authentication authenticate(Authentication auth) throws AuthenticationException { 27 | val user = userService.byUsername(auth.getPrincipal().toString()) 28 | .orElseThrow(() -> new AuthenticationProcessException(auth.getPrincipal() + " wasn't found")); 29 | 30 | validate(user, auth.getCredentials().toString()); 31 | return new UsernamePasswordAuthenticationToken(user, user.getPassword(), Collections.emptyList()); 32 | } 33 | 34 | private void validate(User user, String password){ 35 | if(!user.isEnabled()) throw new AuthenticationProcessException("Your account hasn't been confirmed"); 36 | if(!user.isNonLocked()) throw new AuthenticationProcessException("Your account was locked. Contact to us for more information"); 37 | val hashed = userService.hashPassword(user, password); 38 | if(!user.getPassword().equals(hashed)) throw new AuthenticationProcessException("Your account hasn't been confirmed"); 39 | } 40 | 41 | @Override 42 | public boolean supports(Class authentication) { 43 | return authentication == UsernamePasswordAuthenticationToken.class; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/domain/user/User.java: -------------------------------------------------------------------------------- 1 | package com.cbank.domain.user; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.crypto.keygen.KeyGenerators; 8 | import org.springframework.security.crypto.keygen.StringKeyGenerator; 9 | 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.Id; 13 | import javax.persistence.Table; 14 | import java.util.Collection; 15 | 16 | /** 17 | * Wrapper of UserDetails 18 | */ 19 | 20 | @Entity 21 | @Table(name = "users") 22 | @Data 23 | @ToString(exclude = {"password", "salt"}) 24 | public class User implements UserDetails { 25 | private static final StringKeyGenerator keyGenerator = KeyGenerators.string(); 26 | 27 | @Id 28 | private String username; 29 | 30 | @Column(nullable = false) 31 | private String password; 32 | 33 | private boolean enabled = false; 34 | private boolean nonLocked = true; 35 | 36 | @Column(name = "salt", nullable = false, updatable = false) 37 | private String salt = keyGenerator.generateKey(); 38 | 39 | public User() { 40 | } 41 | 42 | @Override 43 | public Collection getAuthorities() { 44 | return null; 45 | } 46 | 47 | 48 | @Override 49 | public boolean isAccountNonExpired() { 50 | return true; 51 | } 52 | 53 | @Override 54 | public boolean isAccountNonLocked() { 55 | return nonLocked; 56 | } 57 | 58 | @Override 59 | public boolean isCredentialsNonExpired() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public boolean isEnabled() { 65 | return enabled; 66 | } 67 | 68 | public void setEnabled(boolean enabled) { 69 | this.enabled = enabled; 70 | } 71 | 72 | public void setPassword(String password) { 73 | this.password = password; 74 | } 75 | 76 | public void setNonLocked(boolean nonLocked) { 77 | this.nonLocked = nonLocked; 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/message/MessageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.message; 2 | 3 | import com.cbank.config.MailProperties; 4 | import com.cbank.domain.message.Feedback; 5 | import com.cbank.domain.message.Message; 6 | import com.cbank.domain.message.MessageTemplate; 7 | import com.cbank.repositories.FeedbackRepository; 8 | import com.cbank.repositories.MessageRepository; 9 | import com.cbank.services.MessageService; 10 | import com.cbank.validators.FeedbackValidator; 11 | import lombok.RequiredArgsConstructor; 12 | import lombok.extern.slf4j.Slf4j; 13 | import lombok.val; 14 | import org.springframework.mail.javamail.JavaMailSender; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.util.Map; 18 | 19 | /** 20 | * @author Podshivalov N.A. 21 | * @since 21.11.2017. 22 | */ 23 | @Slf4j 24 | @Service 25 | @RequiredArgsConstructor 26 | public class MessageServiceImpl implements MessageService { 27 | private final JavaMailSender mailSender; 28 | private final MessageTemplateFactory templateFactory; 29 | private final FeedbackValidator feedbackValidator; 30 | private final FeedbackRepository feedbackRepository; 31 | private final MessageRepository messageRepository; 32 | private final MailProperties mailProperties; 33 | 34 | 35 | @Override 36 | public Message send(String recipient, MessageTemplate template, Map context) { 37 | log.debug("#send({},{},{})", recipient, template, context); 38 | val message = new Message(recipient, template.getTitle(), templateFactory.create(template, context)); 39 | save(message); 40 | 41 | if (mailProperties.getEnable()){ 42 | mailSender.send(message.toMailMessage(mailProperties.getUsername())); 43 | } 44 | return message; 45 | } 46 | 47 | @Override 48 | public Feedback persist(Feedback feedback) { 49 | feedbackValidator.validate(feedback); 50 | return feedbackRepository.save(feedback); 51 | } 52 | 53 | @Override 54 | public Message save(Message message) { 55 | return messageRepository.save(message); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/ClientServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl 2 | 3 | import com.cbank.domain.Client 4 | import com.cbank.domain.message.MessageTemplate 5 | import com.cbank.domain.security.BaseTokenType 6 | import com.cbank.repositories.ClientRepository 7 | import com.cbank.services.MessageService 8 | import com.cbank.services.TokenService 9 | import spock.lang.Specification 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 22.12.2017. 14 | */ 15 | class ClientServiceImplUnitTest extends Specification { 16 | 17 | def messageService = Mock(MessageService) 18 | def tokenService = Mock(TokenService) 19 | def clientRepository = Mock(ClientRepository) 20 | def clientService = new ClientServiceImpl(clientRepository, tokenService, messageService) 21 | 22 | def "save"() { 23 | given: def client = new Client() 24 | when: clientService.save(client) 25 | then: 1 * clientRepository.save(client) 26 | } 27 | 28 | def "get client by username"() { 29 | given: 30 | def client = new Client(userId: "smith65") 31 | clientRepository.findByUserId(client.userId) >> Optional.of(client) 32 | when: 33 | def target = clientService.byUserId(client.userId) 34 | then: 35 | target.get() == client 36 | } 37 | 38 | def "access recovery"() { 39 | given: 40 | def client = new Client(email: "smith65@mail.com", userId: "smith65") 41 | clientRepository.findByUserId(client.userId) >> Optional.of(client) 42 | clientRepository.findByEmail(client.email) >> Optional.of(client) 43 | 44 | when: "by email" 45 | clientService.accessRecovery("smith65") 46 | then: 47 | 1 * tokenService.create(client.userId, BaseTokenType.RESET_PASSWORD) 48 | 1 * messageService.send(client.email, MessageTemplate.ACCESS_RECOVERY, _) 49 | 50 | when: "by username" 51 | clientService.accessRecovery("smith65") 52 | then: 53 | 1 * tokenService.create(client.userId, BaseTokenType.RESET_PASSWORD) 54 | 1 * messageService.send(client.email, MessageTemplate.ACCESS_RECOVERY, _) 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/validators/CreditValidatorUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.validators 2 | 3 | import com.cbank.domain.credit.Credit 4 | import com.cbank.exceptions.InsufficientFundsException 5 | import com.cbank.exceptions.ValidationException 6 | import com.cbank.repositories.CreditRepository 7 | import com.cbank.services.BalanceService 8 | import spock.lang.Specification 9 | 10 | import static com.cbank.domain.credit.CreditType.* 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 22.12.2017. 15 | */ 16 | class CreditValidatorUnitTest extends Specification { 17 | 18 | def balanceService = Mock(BalanceService) 19 | def creditRepository = Mock(CreditRepository) 20 | def creditValidator = new CreditValidator(balanceService, creditRepository) 21 | 22 | 23 | def "validate: shouldn't throw any exception"() { 24 | given: 25 | def credit = new Credit(accountId: 10, initialAmount: 6_000, type: PERSONAL_CREDIT) 26 | creditRepository.findAllByAccountIdAndTypeIn(*_) >> [] 27 | when: 28 | creditValidator.validate(credit) 29 | then: 30 | noExceptionThrown() 31 | } 32 | 33 | 34 | def "validate: should throw validation exception"() { 35 | given: 36 | creditRepository.findAllByAccountIdAndTypeIn(*_) >> [] 37 | when: 38 | creditValidator.validate(new Credit(accountId: 10, initialAmount: amount, type: type)) 39 | then: 40 | thrown(ValidationException) 41 | where: 42 | type | amount 43 | PERSONAL_CREDIT | BigDecimal.TEN 44 | PERSONAL_CREDIT | new BigDecimal(100_000) 45 | BUSINESS_CREDIT | new BigDecimal(1_000) 46 | BUSINESS_CREDIT | new BigDecimal(10_000_000) 47 | } 48 | 49 | def "validate: should throw insufficient funds exception"() { 50 | given: 51 | def credit = new Credit(accountId: 10, initialAmount: BigDecimal.TEN, type: BUSINESS_DEPOSIT) 52 | creditRepository.findAllByAccountIdAndTypeIn(*_) >> [] 53 | balanceService.balance(credit.accountId) >> BigDecimal.ZERO 54 | when: 55 | creditValidator.validate(credit) 56 | then: 57 | thrown(InsufficientFundsException) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/credit/CreditServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.credit 2 | 3 | import com.cbank.domain.Account 4 | import com.cbank.domain.credit.Credit 5 | import com.cbank.domain.credit.CreditState 6 | import com.cbank.repositories.AccountRepository 7 | import com.cbank.repositories.CreditRepository 8 | import com.cbank.services.TransactionService 9 | import spock.lang.Specification 10 | 11 | import java.time.LocalDateTime 12 | 13 | /** 14 | * @author Podshivalov N.A. 15 | * @since 25.12.2017. 16 | */ 17 | class CreditServiceImplUnitTest extends Specification { 18 | 19 | def creditRepository = Mock(CreditRepository) 20 | def transactionService = Mock(TransactionService) 21 | def accountRepository = Mock(AccountRepository) 22 | def creditService = new CreditServiceImpl(creditRepository, transactionService, accountRepository) 23 | 24 | def "create withdraw"(){ 25 | given: 26 | def account = new Account() 27 | def credit = new Credit(accountId: 10L, closedAt: LocalDateTime.now().plusDays(30)) 28 | accountRepository.findOne(10L) >> account 29 | when: 30 | creditService.withdraw(credit) 31 | then: 32 | 1 * transactionService.creditWithdraw(account, credit) 33 | 34 | } 35 | 36 | def "create withdraw and close credit"(){ 37 | given: 38 | def account = new Account() 39 | def credit = new Credit(accountId: 10L, closedAt: LocalDateTime.now().minusHours(5)) 40 | accountRepository.findOne(10L) >> account 41 | when: 42 | creditService.withdraw(credit) 43 | then: 44 | 1 * transactionService.creditWithdraw(account, credit) 45 | 1 * creditRepository.save(credit) 46 | credit.state == CreditState.CLOSED 47 | 48 | } 49 | 50 | def "create credit"(){ 51 | def account = new Account() 52 | def credit = new Credit(accountId: 10L, closedAt: LocalDateTime.now().plusDays(30)) 53 | accountRepository.findOne(10L) >> account 54 | when: 55 | creditService.create(credit) 56 | then: 57 | 1 * transactionService.credit(account, credit) 58 | 1 * creditRepository.save(credit) 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/TokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl; 2 | 3 | 4 | import com.cbank.domain.security.BaseToken; 5 | import com.cbank.domain.security.BaseTokenType; 6 | import com.cbank.exceptions.TokenExpiredException; 7 | import com.cbank.repositories.BaseTokenRepository; 8 | import com.cbank.services.TokenService; 9 | import lombok.AllArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | import lombok.val; 12 | import org.springframework.scheduling.annotation.Scheduled; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import javax.persistence.EntityNotFoundException; 17 | import java.time.LocalDateTime; 18 | import java.util.Optional; 19 | 20 | /** 21 | * @author Podshivalov N.A. 22 | * @since 21.11.2017. 23 | */ 24 | @Slf4j 25 | @Service 26 | @AllArgsConstructor 27 | public class TokenServiceImpl implements TokenService { 28 | private final BaseTokenRepository baseTokenRepository; 29 | 30 | @Override 31 | public BaseToken create(String username, BaseTokenType tokenType) { 32 | log.debug("#create({}, {})", username, tokenType); 33 | return baseTokenRepository.save(BaseToken.of(username, tokenType)); 34 | } 35 | 36 | private void invalidate(BaseToken token) { 37 | token.setValid(false); 38 | baseTokenRepository.save(token); 39 | } 40 | 41 | @Override 42 | @Scheduled(cron = "${schedule.cron}") 43 | @Transactional 44 | public int checkForExpired() { 45 | log.debug("Schedule check for tokens"); 46 | LocalDateTime time = LocalDateTime.now().minusDays(1); 47 | int numOfExpired = baseTokenRepository.expire(time); 48 | log.debug(numOfExpired + " tokens has been updated"); 49 | return numOfExpired; 50 | } 51 | 52 | @Override 53 | public BaseToken get(String uuid) { 54 | val token = Optional.ofNullable(baseTokenRepository.findOne(uuid)) 55 | .orElseThrow(EntityNotFoundException::new); 56 | 57 | if(token.getCreatedAt().isBefore(LocalDateTime.now().minusMinutes(10))){ 58 | invalidate(token); 59 | throw new TokenExpiredException(); 60 | } 61 | 62 | invalidate(token); 63 | return token; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/validators/RegistrationValidatorUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.validators 2 | 3 | import com.cbank.domain.RegistrationForm 4 | import com.cbank.exceptions.ValidationException 5 | import com.cbank.services.UserService 6 | import spock.lang.Specification 7 | 8 | /** 9 | * @author Podshivalov N.A. 10 | * @since 22.12.2017. 11 | */ 12 | class RegistrationValidatorUnitTest extends Specification { 13 | 14 | def userService = Mock(UserService) 15 | def registrationValidator = new RegistrationValidator(userService) 16 | 17 | def "validate: shouldn't throw any exception"() { 18 | given: 19 | def form = new RegistrationForm(username: "smith65", password: "qwerty", email: "smith1865@mail.com", address: "Ocean Avenue, 5", name: "James Smith") 20 | userService.byUsername(form.username) >> Optional.empty() 21 | when: 22 | registrationValidator.validate(form) 23 | then: 24 | noExceptionThrown() 25 | } 26 | 27 | def "validate: should throw validation exception"() { 28 | given: 29 | def form = new RegistrationForm(username: username, password: password, email: email, address: address, name: name) 30 | userService.byUsername(form.username) >> Optional.empty() 31 | when: 32 | registrationValidator.validate(form) 33 | then: 34 | thrown(ValidationException) 35 | where: 36 | username | password | email | address | name 37 | null | "qwerty" | "smith1865@mail.com" | "Ocean Avenue, 5" | "James Smith" 38 | "sm" | "qwerty" | "smith1865@mail.com" | "Ocean Avenue, 5" | "James Smith" 39 | "smitty:)" | "qwerty" | "smith1865@mail.com" | "Ocean Avenue, 5" | "James Smith" 40 | "smith65" | "q" | "smith1865@mail.com" | "Ocean Avenue, 5" | "James Smith" 41 | "smith65" | null | "smith1865@mail.com" | "Ocean Avenue, 5" | "James Smith" 42 | "smith65" | "qwerty" | "smith1865@mail" | "Ocean Avenue, 5" | "James Smith" 43 | "smith65" | "qwerty" | "smith1865@mail.com" | "5" | "James Smith" 44 | "smith65" | "qwerty" | "smith1865@mail.com" | "Ocean Avenue,5" | "s" 45 | "smith65" | "qwerty" | "smith1865@mail.com" | "Ocean Avenue,5" | null 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/resources/static/reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/TokenServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl 2 | 3 | import com.cbank.domain.security.BaseToken 4 | import com.cbank.domain.security.BaseTokenType 5 | import com.cbank.exceptions.TokenExpiredException 6 | import com.cbank.repositories.BaseTokenRepository 7 | import spock.lang.Specification 8 | 9 | import javax.persistence.EntityNotFoundException 10 | import java.time.LocalDateTime 11 | 12 | /** 13 | * @author Podshivalov N.A. 14 | * @since 22.12.2017. 15 | */ 16 | class TokenServiceImplUnitTest extends Specification { 17 | 18 | def baseTokenRepository = Mock(BaseTokenRepository) 19 | def tokenService = new TokenServiceImpl(baseTokenRepository) 20 | 21 | def "create token"() { 22 | when: 23 | tokenService.create("username", BaseTokenType.REGISTRATION) 24 | then: 25 | 1 * baseTokenRepository.save(_) 26 | } 27 | 28 | def "check that job searching expired token works correctly"() { 29 | when: 30 | tokenService.checkForExpired() 31 | then: 32 | 1 * baseTokenRepository.expire(_) 33 | } 34 | 35 | def "get token and invalidate it"() { 36 | given: 37 | def uuid = UUID.randomUUID().toString() 38 | def token = new BaseToken(token: uuid, createdAt: LocalDateTime.now(), valid: true) 39 | baseTokenRepository.findOne(uuid) >> token 40 | when: 41 | tokenService.get(uuid) 42 | then: 43 | noExceptionThrown() 44 | !token.valid 45 | 1 * baseTokenRepository.save(token) 46 | } 47 | 48 | def "getToken should not found token and throw exception"() { 49 | given: 50 | def uuid = UUID.randomUUID().toString() 51 | baseTokenRepository.findOne(uuid) >> null 52 | when: 53 | tokenService.get(uuid) 54 | then: 55 | thrown(EntityNotFoundException) 56 | } 57 | 58 | def "getToken get expired token and throw exception"() { 59 | given: 60 | def uuid = UUID.randomUUID().toString() 61 | def token = new BaseToken(token: uuid, createdAt: LocalDateTime.now().minusHours(1), valid: true) 62 | baseTokenRepository.findOne(uuid) >> token 63 | when: 64 | tokenService.get(uuid) 65 | then: 66 | thrown(TokenExpiredException) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/config/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.cbank.config; 2 | 3 | import com.cbank.repositories.RememberMeTokenRepository; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 7 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | 12 | /** 13 | * Security configuration 14 | */ 15 | @Configuration 16 | @EnableWebSecurity 17 | @EnableGlobalMethodSecurity(prePostEnabled = true) 18 | @AllArgsConstructor 19 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 20 | 21 | private static final int TOKEN_VALIDITY_SEC = 1000000; 22 | 23 | private final RememberMeTokenRepository tokenRepository; 24 | private final AuthFailureHandler authFailureHandler; 25 | private final Sha256AuthenticationProvider authenticationProvider; 26 | 27 | 28 | protected void configure(HttpSecurity http) throws Exception { 29 | http 30 | .authorizeRequests() 31 | .antMatchers("/", "/sign", "/images/**", "/js/**", "/users/**", "/feedback/**", 32 | "/confirm/**", "/forgot/**", "/personal/**", "/business/**", 33 | "/css/**", "/messages/**", "/subscribers/**", "/fonts/**", "/contact/**").permitAll() 34 | .anyRequest().authenticated() 35 | .and() 36 | .formLogin() 37 | .loginPage("/sign") 38 | .usernameParameter("login") 39 | .passwordParameter("pass") 40 | .defaultSuccessUrl("/") 41 | .failureHandler(authFailureHandler) 42 | .and() 43 | .logout() 44 | .logoutSuccessUrl("/") 45 | .and() 46 | .rememberMe() 47 | .tokenRepository(tokenRepository) 48 | .tokenValiditySeconds(TOKEN_VALIDITY_SEC) 49 | .and() 50 | .csrf().disable(); 51 | } 52 | 53 | 54 | @Override 55 | protected void configure(AuthenticationManagerBuilder auth) { 56 | auth.authenticationProvider(authenticationProvider); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/validators/RegistrationValidator.java: -------------------------------------------------------------------------------- 1 | package com.cbank.validators; 2 | 3 | import com.cbank.domain.RegistrationForm; 4 | import com.cbank.exceptions.ValidationException; 5 | import com.cbank.services.UserService; 6 | import lombok.AllArgsConstructor; 7 | import lombok.val; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | import static org.springframework.util.StringUtils.hasLength; 13 | 14 | 15 | @Component 16 | @AllArgsConstructor 17 | public class RegistrationValidator implements Validator { 18 | 19 | /** 20 | * Pattern and regex for username 21 | */ 22 | private static final String USERNAME_REGEX = "^(?=.*[a-z])[a-z0-9]{3,}"; 23 | private static final Pattern pattern = Pattern.compile(USERNAME_REGEX); 24 | 25 | private static final String NULL = "You have not entered username or email"; 26 | private static final String SMALL_PASSWORD = "Your password is too short"; 27 | private static final String INCORRECT_USERNAME = "Incorrect username"; 28 | private static final String USERNAME_ALREADY_EXIST = "Username is already taken"; 29 | private static final String SMALL_ADDRESS_OR_NAME = "Address or Name is too short"; 30 | 31 | private final UserService userService; 32 | 33 | 34 | public void validate(RegistrationForm form) { 35 | val error = check(form); 36 | if(error != null) throw new ValidationException(error); 37 | } 38 | 39 | private String check(RegistrationForm form){ 40 | val username = form.getUsername(); 41 | val email = form.getEmail(); 42 | ValidationUtils.email(email); 43 | if (username == null) return NULL; 44 | if (isAlreadyExist(username)) return USERNAME_ALREADY_EXIST; 45 | if (!pattern.matcher(username).matches()) return INCORRECT_USERNAME; 46 | if (!validatePassword(form.getPassword())) return SMALL_PASSWORD; 47 | if (!hasLength(form.getAddress()) || form.getAddress().length() < 5) return SMALL_ADDRESS_OR_NAME; 48 | if (!hasLength(form.getName()) || form.getName().length() < 3) return SMALL_ADDRESS_OR_NAME; 49 | return null; 50 | } 51 | 52 | private boolean validatePassword(String password) { 53 | return hasLength(password) && password.length() >= 5; 54 | } 55 | 56 | private boolean isAlreadyExist(String username) { 57 | return userService.byUsername(username).isPresent(); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/user/RegistrationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user; 2 | 3 | import com.cbank.domain.Account; 4 | import com.cbank.domain.RegistrationForm; 5 | import com.cbank.domain.message.MessageTemplate; 6 | import com.cbank.domain.security.BaseTokenType; 7 | import com.cbank.services.*; 8 | import com.cbank.utils.MapUtils; 9 | import com.cbank.validators.RegistrationValidator; 10 | import lombok.AllArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import lombok.val; 13 | import org.springframework.stereotype.Service; 14 | 15 | import static com.google.common.collect.Maps.immutableEntry; 16 | 17 | /** 18 | * @author Podshivalov N.A. 19 | * @since 21.11.2017. 20 | */ 21 | @Slf4j 22 | @Service 23 | @AllArgsConstructor 24 | public class RegistrationServiceImpl implements RegistrationService { 25 | private final UserService userService; 26 | private final TokenService tokenService; 27 | private final ClientService clientService; 28 | private final AccountService accountService; 29 | private final MessageService messageService; 30 | private final RegistrationValidator registrationValidator; 31 | 32 | 33 | @Override 34 | public Account register(RegistrationForm form) { 35 | log.debug("#register({})", form); 36 | registrationValidator.validate(form); 37 | 38 | 39 | val user = userService.save(form.getUsername(), form.getPassword()); 40 | val token = tokenService.create(user.getUsername(), BaseTokenType.REGISTRATION); 41 | 42 | val client = form.toClient(); 43 | clientService.save(client); 44 | 45 | val account = new Account(client.getId()); 46 | accountService.save(account); 47 | 48 | messageService.send(client.getEmail(), MessageTemplate.REGISTRATION_CONFIRMATION, 49 | MapUtils.from(immutableEntry("user", user), 50 | immutableEntry("client", client), 51 | immutableEntry("account", account), 52 | immutableEntry( "token", token))); 53 | 54 | log.debug("Account has been saved successfully: " + account); 55 | return account; 56 | } 57 | 58 | @Override 59 | public void confirm(String token) { 60 | userService.enable(token); 61 | } 62 | 63 | @Override 64 | public boolean check(String usernameOrEmail) { 65 | return userService.byUsername(usernameOrEmail) 66 | .flatMap(user -> clientService.byUserId(user.getUsername())) 67 | .isPresent(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/user/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user; 2 | 3 | import com.cbank.domain.user.User; 4 | import com.cbank.repositories.UserRepository; 5 | import com.cbank.services.TokenService; 6 | import com.cbank.services.UserService; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import lombok.val; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import javax.persistence.EntityExistsException; 16 | import java.util.Optional; 17 | 18 | /** 19 | * @author Podshivalov N.A. 20 | * @since 21.11.2017. 21 | */ 22 | @Slf4j 23 | @Service 24 | @RequiredArgsConstructor 25 | public class UserServiceImpl implements UserService { 26 | private final UserRepository userRepository; 27 | private final TokenService tokenService; 28 | 29 | @Override 30 | public User save(User user) { 31 | return userRepository.save(user); 32 | } 33 | 34 | @Override 35 | public Optional byUsername(String username) { 36 | return userRepository.findByUsername(username); 37 | } 38 | 39 | @Override 40 | @Transactional 41 | public void resetPassword(String tokenId, String password) { 42 | val token = tokenService.get(tokenId); 43 | 44 | byUsername(token.getUsername()) 45 | .map(user -> userRepository.save(setPassword(user, password))) 46 | .orElseThrow(EntityExistsException::new); 47 | } 48 | 49 | @Override 50 | @Transactional 51 | public void enable(String tokenId) { 52 | log.debug("#enable({})", tokenId); 53 | val token = tokenService.get(tokenId); 54 | 55 | byUsername(token.getUsername()) 56 | .ifPresent(user -> { 57 | user.setEnabled(true); 58 | userRepository.save(user); 59 | }); 60 | 61 | } 62 | 63 | @Override 64 | public void lock(User user) { 65 | log.debug("#lock({})", user); 66 | user.setNonLocked(false); 67 | save(user); 68 | } 69 | 70 | 71 | @Override 72 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 73 | log.debug("#loadUserByUsername({})", username); 74 | return byUsername(username) 75 | .map(user -> (UserDetails) user) 76 | .orElseThrow( () -> new UsernameNotFoundException("User : " + username + " was not found ")); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/services/impl/AccountServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl; 2 | 3 | import com.cbank.domain.Account; 4 | import com.cbank.domain.transaction.Transaction; 5 | import com.cbank.exceptions.InsufficientFundsException; 6 | import com.cbank.repositories.AccountRepository; 7 | import com.cbank.services.AccountService; 8 | import com.cbank.services.BalanceService; 9 | import com.cbank.services.TransactionService; 10 | import lombok.AllArgsConstructor; 11 | import lombok.val; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import javax.persistence.EntityNotFoundException; 16 | import java.util.Collection; 17 | import java.util.Optional; 18 | 19 | /** 20 | * @author Podshivalov N.A. 21 | * @since 21.11.2017. 22 | */ 23 | @Service 24 | @AllArgsConstructor 25 | public class AccountServiceImpl implements AccountService { 26 | private final AccountRepository accountRepository; 27 | private final TransactionService transactionService; 28 | private final BalanceService balanceService; 29 | 30 | 31 | @Override 32 | public Account save(Account account) { 33 | return accountRepository.save(account); 34 | } 35 | 36 | @Override 37 | public Optional current(Long clientId) { 38 | return accountRepository.findByClientIdAndCurrentIsTrue(clientId); 39 | } 40 | 41 | @Override 42 | @Transactional 43 | public Account asCurrent(Account current, String accountNum) { 44 | val account = accountRepository.findByNum(accountNum).orElseThrow(EntityNotFoundException::new); 45 | current.setCurrent(Boolean.FALSE); 46 | account.setCurrent(Boolean.TRUE); 47 | save(account); save(current); 48 | return account; 49 | } 50 | 51 | @Override 52 | @Transactional 53 | public Account create(Account current) { 54 | val balance = balanceService.balance(current); 55 | if (NEW_ACCOUNT_PRICE.compareTo( balance) > 0 ) 56 | throw new InsufficientFundsException(); 57 | 58 | val transaction = Transaction.builder() 59 | .payer(current.getNum()) 60 | .amount(NEW_ACCOUNT_PRICE) 61 | .recipient(BANK_ACCOUNT) 62 | .details("Account opening fee") 63 | .build(); 64 | 65 | transactionService.create(transaction); 66 | val persited = accountRepository.save(new Account(current.getClientId())); 67 | 68 | current.setCurrent(Boolean.FALSE); 69 | accountRepository.save(current); 70 | return persited; 71 | } 72 | 73 | @Override 74 | public Collection byClient(Long clientId) { 75 | return accountRepository.findAllByClientId(clientId); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/AccountServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl 2 | 3 | import com.cbank.domain.Account 4 | import com.cbank.exceptions.InsufficientFundsException 5 | import com.cbank.repositories.AccountRepository 6 | import com.cbank.services.BalanceService 7 | import com.cbank.services.TransactionService 8 | import spock.lang.Specification 9 | 10 | /** 11 | * @author Podshivalov N.A. 12 | * @since 22.12.2017. 13 | */ 14 | class AccountServiceImplUnitTest extends Specification { 15 | 16 | def balanceService = Mock(BalanceService) 17 | def transactionService = Mock(TransactionService) 18 | def accountRepository = Mock(AccountRepository) 19 | def accountService = new AccountServiceImpl(accountRepository, transactionService, balanceService) 20 | 21 | def "find current account"() { 22 | given: 23 | def account = new Account(clientId: 1) 24 | accountRepository.findByClientIdAndCurrentIsTrue(account.clientId) >> Optional.of(account) 25 | when: 26 | def target = accountService.current(account.clientId) 27 | then: 28 | target.get() == account 29 | } 30 | 31 | def "mark as current"() { 32 | given: 33 | def current = new Account(clientId: 1, current: true) 34 | def target = new Account(num: "000000000000000", current: false) 35 | accountRepository.findByNum(target.num) >> Optional.of(target) 36 | when: 37 | accountService.asCurrent(current, target.num) 38 | then: 39 | 1 * accountRepository.save(current) 40 | 1 * accountRepository.save(target) 41 | target.current 42 | !current.current 43 | } 44 | 45 | def "create: should not throw any exception"() { 46 | given: 47 | def current = new Account(clientId: 1, current: true) 48 | balanceService.balance(current) >> new BigDecimal(10_000) 49 | when: 50 | accountService.create(current) 51 | then: 52 | 1 * transactionService.create(_) 53 | 2 * accountRepository.save(_) 54 | !current.current 55 | } 56 | 57 | 58 | def "create: should throw the exception"() { 59 | given: 60 | def current = new Account(clientId: 1, current: true) 61 | balanceService.balance(current) >> BigDecimal.ZERO 62 | when: 63 | accountService.create(current) 64 | then: 65 | thrown(InsufficientFundsException) 66 | } 67 | 68 | def "by clientId"() { 69 | given: 70 | def account = new Account(clientId: 1) 71 | accountRepository.findAllByClientId(account.clientId) >> [account] 72 | when: 73 | def target = accountService.byClient(account.clientId) 74 | then: 75 | !target.isEmpty() 76 | target.size() == 1 77 | target.iterator().next() == account 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/controllers/UserController.java: -------------------------------------------------------------------------------- 1 | package com.cbank.controllers; 2 | 3 | import com.cbank.domain.RegistrationForm; 4 | import com.cbank.services.ClientService; 5 | import com.cbank.services.RegistrationService; 6 | import com.cbank.services.UserService; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.ToString; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.security.access.prepost.PreAuthorize; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | /** 16 | * @author Poshivalov Nikita 17 | * @since 16.11.2016. 18 | */ 19 | 20 | @Slf4j 21 | @RestController 22 | @PreAuthorize("isAnonymous()") 23 | @AllArgsConstructor 24 | @RequestMapping("/users") 25 | public class UserController { 26 | private final UserService userService; 27 | private final RegistrationService registrationService; 28 | private final ClientService clientService; 29 | 30 | 31 | @PostMapping(value = "/registration") 32 | public ResponseEntity addNewUser(@RequestBody RegistrationForm form) { 33 | log.debug("#addNewUser({})", form); 34 | return ResponseEntity.ok().body(registrationService.register(form)); 35 | } 36 | 37 | @GetMapping(value = "/registration") 38 | public ResponseEntity check(@RequestParam String usernameOrEmail) { 39 | return registrationService.check(usernameOrEmail) 40 | ? ResponseEntity.badRequest().build() 41 | : ResponseEntity.ok().build(); 42 | } 43 | 44 | @PostMapping("/password") 45 | public ResponseEntity resetPassword(@RequestBody ResetPasswordRequest request) { 46 | if (request.password.length() < 4) { 47 | return ResponseEntity.badRequest().body(ErrorMessage.create("Password is too short, Please, type more strong password")); 48 | } 49 | userService.resetPassword(request.token, request.password); 50 | return ResponseEntity.ok().build(); 51 | } 52 | 53 | @GetMapping(value = "/password") 54 | public ResponseEntity forget(@RequestParam String loginOrEmail) { 55 | log.debug("#forget({})", loginOrEmail); 56 | clientService.accessRecovery(loginOrEmail); 57 | return ResponseEntity.accepted().build(); 58 | } 59 | 60 | @PostMapping(value = "/confirmation") 61 | public ResponseEntity confirm(@RequestBody ConfirmationRequest request) { 62 | registrationService.confirm(request.token); 63 | return ResponseEntity.ok().build(); 64 | } 65 | 66 | 67 | @ToString(exclude = "password") 68 | private static class ResetPasswordRequest { 69 | private String password; 70 | private String token; 71 | } 72 | 73 | @Data 74 | private static class ConfirmationRequest{ 75 | private String token; 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/cbank/validators/CreditValidator.java: -------------------------------------------------------------------------------- 1 | package com.cbank.validators; 2 | 3 | import com.cbank.domain.credit.Credit; 4 | import com.cbank.exceptions.InsufficientFundsException; 5 | import com.cbank.exceptions.ValidationException; 6 | import com.cbank.repositories.CreditRepository; 7 | import com.cbank.services.BalanceService; 8 | import lombok.AllArgsConstructor; 9 | import lombok.val; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.math.BigDecimal; 13 | 14 | import static com.cbank.domain.credit.CreditType.CREDITS; 15 | 16 | @Component 17 | @AllArgsConstructor 18 | public class CreditValidator implements Validator { 19 | 20 | /** 21 | * Constants of personal and business crediting. 22 | */ 23 | private static final BigDecimal PERSONAL_MAX = BigDecimal.valueOf(50_000); 24 | private static final BigDecimal PERSONAL_MIN = BigDecimal.valueOf(1_000); 25 | private static final BigDecimal BUSINESS_MAX = BigDecimal.valueOf(1_000_000); 26 | private static final BigDecimal BUSINESS_MIN = BigDecimal.valueOf(10_000); 27 | 28 | private static final String LESS_THEN_PERSONAL_MIN = "Please enter more money than " + PERSONAL_MIN; 29 | private static final String LESS_THEN_BUSINESS_MIN = "Too small sum for business credit. You should take personal credit"; 30 | private static final String MORE_THEN_PERSONAL_MAX = String.format("Maximum credit money is %1$,.2f .\n May be you already have many credits", PERSONAL_MAX); 31 | private static final String MORE_THEN_BUSINESS_MAX = String.format("Maximum credit money is %1$,.2f .\n May be you already have many credits", BUSINESS_MAX); 32 | 33 | private final BalanceService balanceService; 34 | private final CreditRepository creditRepository; 35 | 36 | @Override 37 | public void validate(Credit credit) { 38 | val amount = credit.getInitialAmount(); 39 | 40 | val maxLimit = creditRepository.findAllByAccountIdAndTypeIn(credit.getAccountId(), CREDITS).stream() 41 | .map(Credit::getInitialAmount) 42 | .reduce(credit.getInitialAmount(), BigDecimal::add); 43 | 44 | 45 | switch (credit.getType()) { 46 | case PERSONAL_CREDIT: 47 | if (amount.compareTo(PERSONAL_MIN) < 0) throw new ValidationException(LESS_THEN_PERSONAL_MIN); 48 | if (maxLimit.compareTo(PERSONAL_MAX) > 0) throw new ValidationException(MORE_THEN_PERSONAL_MAX); 49 | break; 50 | case BUSINESS_CREDIT: 51 | if (amount.compareTo(BUSINESS_MIN) < 0) throw new ValidationException(LESS_THEN_BUSINESS_MIN); 52 | if (maxLimit.compareTo(BUSINESS_MAX) > 0) throw new ValidationException(MORE_THEN_BUSINESS_MAX); 53 | break; 54 | case BUSINESS_DEPOSIT: 55 | if (amount.compareTo(balanceService.balance(credit.getAccountId())) > 0 ) throw new InsufficientFundsException(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/resources/static/included/signup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 61 |
62 |
-------------------------------------------------------------------------------- /src/main/resources/static/contact.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |
14 |

Contact information

15 |

If you want to invest in country bank or you need support, please contact us

16 |
17 |
18 |
19 |

CONTACT INFO

20 |

You can contact with us by phone or email

21 |
22 |

CountryBank@company.com

23 |
24 |
25 |

001-122-2221 | 001-001-2000

26 |
27 |
28 |
29 |
30 |
31 | 33 |
34 |
35 | 37 |
38 |
39 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |

50 |
51 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/resources/static/private.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 20 |
21 | 22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 |
    31 |
  • 32 | 40 |
  • 41 |
42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 |
50 | 51 |
52 |
53 |

Account

54 |
55 |

Account , balance

56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 | 64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/user/UserServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.user 2 | 3 | import com.cbank.domain.security.BaseToken 4 | import com.cbank.domain.security.BaseTokenType 5 | import com.cbank.domain.user.User 6 | import com.cbank.repositories.UserRepository 7 | import com.cbank.services.TokenService 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException 9 | import spock.lang.Specification 10 | 11 | /** 12 | * @author Podshivalov N.A. 13 | * @since 22.12.2017. 14 | */ 15 | class UserServiceImplUnitTest extends Specification { 16 | 17 | def tokenService = Mock(TokenService) 18 | def userRepository = Mock(UserRepository) 19 | def userService = new UserServiceImpl(userRepository, tokenService) 20 | 21 | 22 | def "save"() { 23 | given : def user = new User() 24 | when: userService.save(user) 25 | then: 1 * userRepository.save(user) 26 | } 27 | 28 | def "find by username"() { 29 | given: 30 | def user = new User(username: "username") 31 | userRepository.findByUsername(user.username) >> Optional.of(user) 32 | expect: 33 | userService.byUsername(user.username).get() == user 34 | } 35 | 36 | def "reset password"() { 37 | given: 38 | def token = UUID.randomUUID().toString() 39 | def oldPassword = "password" 40 | def user = new User(username: "username", password: oldPassword) 41 | tokenService.get(token) >> BaseToken.of(user.username, BaseTokenType.RESET_PASSWORD) 42 | userRepository.findByUsername(user.username) >> Optional.of(user) 43 | userRepository.save(user) >> user 44 | when: 45 | userService.resetPassword(token, "newPassword") 46 | then: 47 | user.password != oldPassword 48 | } 49 | 50 | def "enable"() { 51 | given: 52 | def token = UUID.randomUUID().toString() 53 | def user = new User(enabled: false, username: "username") 54 | tokenService.get(token) >> BaseToken.of(user.username, BaseTokenType.REGISTRATION) 55 | userRepository.findByUsername(user.username) >> Optional.of(user) 56 | when: 57 | userService.enable(token) 58 | then: 59 | 1 * userRepository.save(user) 60 | user.enabled 61 | } 62 | 63 | def "lock"() { 64 | given: 65 | def user = new User(nonLocked: true) 66 | when: 67 | userService.lock(user) 68 | then: 69 | 1 * userRepository.save(user) 70 | !user.nonLocked 71 | 72 | } 73 | 74 | def "loadUserByUsername: should return user"() { 75 | given: 76 | def username = "username" 77 | userRepository.findByUsername(username) >> Optional.of(new User()) 78 | when: 79 | def user = userService.loadUserByUsername(username) 80 | then: 81 | noExceptionThrown() 82 | user != null 83 | 84 | } 85 | 86 | def "loadUserByUsername: should throw exception"() { 87 | given: 88 | def username = "username" 89 | userRepository.findByUsername(username) >> Optional.empty() 90 | when: 91 | userService.loadUserByUsername(username) 92 | then: 93 | thrown(UsernameNotFoundException) 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/nikitap492/CountryBank.svg?branch=master)](https://travis-ci.org/nikitap492/CountryBank) 2 | [![codecov.io](https://codecov.io/github/nikitap492/CountryBank/coverage.svg?branch=master)](https://travis-ci.org/nikitap492/CountryBank?branch=master) 3 | [![CodeFactor](https://www.codefactor.io/repository/github/nikitap492/countrybank/badge)](https://www.codefactor.io/repository/github/nikitap492/countrybank) 4 | # Country Bank 5 | **Simple bank application with Spring Boot and Spring Data** 6 | 7 | ### Country bank is standalone server by spring boot with flexible interface. 8 | Imagine yourself town that exists on the Wild West and has population about 1000 people. Country Bank is located in the town. Simple application was developed for bank which offers e-services (Are you sure there is on the Wild West?) 9 | 10 | ### Clients might: 11 | - Subscribe. Anonymous user might subscribe by email. Authenticated user just needs click a button for subscribe/ unsubscribe (email will taken from account) 12 | - Create account. After sign up in system, registration token will have sent for confirmatioin of registration 13 | - Send message to the bank. Messages are saving in database. Authenticated user just needs wrote a message text (email and name will taken from account) 14 | - Reset password if user forget it. Reset password token will have sent on email 15 | 16 | All tokens valid 24 hours. 17 | 18 | 19 | ![anon2](https://cloud.githubusercontent.com/assets/18111582/22162520/13a3254c-df60-11e6-936a-4ac52a765e7f.gif) 20 | 21 | 22 | After authentication you can use other features, as 23 | - Transfer money to bill 24 | - Make payments for fines, debts, or public services (For simplicity this all was combines in one action – make payment. Then let the government chooses for which payment has been  ) 25 | - Take credits or put their money in deposit 26 | - Check bills or open new bill. 27 | - Check transactions by bill 28 | - Choose active bill 29 | 30 | #### For simplicity also all bills have UUID structure in spite of XXXX-XXXX-XXXX-XXXX. Application is used to active bill for payments. 31 | 32 | ![auth3](https://cloud.githubusercontent.com/assets/18111582/22162518/13a12e22-df60-11e6-8852-b1199ce0dd3e.gif) 33 | 34 | ### Application structure is using MVC pattern: 35 | ![layers](https://cloud.githubusercontent.com/assets/18111582/22162519/13a2dc90-df60-11e6-8bc3-b5effc15f4c0.gif) 36 | Controllers are receiving all requests, after checking by validators. If request are valid then services process this interaction with database. 37 | ## How To Install? 38 | ### By gradle 39 | - You must have jdk 8 and gradle 40 | - Download sources from github 41 | - You need export environment variables for mail provider: `CONFIG_MAIL_HOST`, `CONFIG_MAIL_PORT`, `CONFIG_MAIL_SMTP_AUTH`, `CONFIG_MAIL_STARTTLS`, `CONFIG_MAIL_USERNAME`, `CONFIG_MAIL_PASSWORD` or override these variable in `Application.yml` in classpath. 42 | - Run `gradle bootRun`, then server starts on port `8000` 43 | 44 | ### By docker 45 | - Install docker and docker-compose 46 | - You need export environment variables for mail provider: `CONFIG_MAIL_HOST`, `CONFIG_MAIL_PORT`, `CONFIG_MAIL_SMTP_AUTH`, `CONFIG_MAIL_STARTTLS`, `CONFIG_MAIL_USERNAME`, `CONFIG_MAIL_PASSWORD`. 47 | - In your terminal run `docker-compose up`, then server starts on port `8000` 48 | 49 | -------------------------------------------------------------------------------- /src/test/groovy/com/cbank/services/impl/transaction/TransactionServiceImplUnitTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cbank.services.impl.transaction 2 | 3 | import com.cbank.domain.Account 4 | import com.cbank.domain.credit.Credit 5 | import com.cbank.domain.credit.CreditType 6 | import com.cbank.domain.transaction.Transaction 7 | import com.cbank.exceptions.InsufficientFundsException 8 | import com.cbank.repositories.TransactionRepository 9 | import com.cbank.services.BalanceService 10 | import com.cbank.services.TariffService 11 | import spock.lang.Specification 12 | 13 | import static com.cbank.services.AccountService.BANK_ACCOUNT 14 | import static java.math.BigDecimal.TEN 15 | import static java.math.BigDecimal.ZERO 16 | 17 | /** 18 | * @author Podshivalov N.A. 19 | * @since 22.12.2017. 20 | */ 21 | class TransactionServiceImplUnitTest extends Specification { 22 | 23 | def balanceService = Mock(BalanceService) 24 | def tariffService = Mock(TariffService) 25 | def transactionRepository = Mock(TransactionRepository) 26 | def transactionService = new TransactionServiceImpl(balanceService, tariffService, transactionRepository) 27 | 28 | def accountNum = "1111111111111111" 29 | def first = new Transaction(payer: BANK_ACCOUNT, recipient: accountNum, amount: new BigDecimal(145)) 30 | def second = new Transaction(payer: accountNum, recipient: BANK_ACCOUNT, amount: new BigDecimal(80)) 31 | def third = new Transaction(payer: accountNum, recipient: BANK_ACCOUNT, amount: new BigDecimal(55)) 32 | 33 | def "create general transaction"() { 34 | given: 35 | balanceService.balance(second.payer) >> new BigDecimal(200) 36 | tariffService.evaluate(_) >> TEN 37 | when: transactionService.create(second) 38 | then: 2 * transactionRepository.save(_) 39 | } 40 | 41 | def "create general transaction: throw InsufficientFundsException"() { 42 | given: 43 | balanceService.balance(second.payer) >> ZERO 44 | tariffService.evaluate(_) >> TEN 45 | when: transactionService.create(second) 46 | then: 47 | 0 * transactionRepository.save(_) 48 | thrown(InsufficientFundsException) 49 | } 50 | 51 | def "credit withdraw"() { 52 | when: transactionService.creditWithdraw(new Account(num: accountNum), new Credit(initialAmount: TEN, type: CreditType.BUSINESS_CREDIT)) 53 | then: 1 * transactionRepository.save(_) 54 | 55 | } 56 | 57 | def "create credit"() { 58 | when: transactionService.credit(new Account(num: accountNum), new Credit(initialAmount: TEN, type: CreditType.BUSINESS_CREDIT)) 59 | then: 1 * transactionRepository.save(_) 60 | } 61 | 62 | def "create deposite"() { 63 | given: 64 | def account = new Account(num: accountNum) 65 | balanceService.balance(*_) >> new BigDecimal(10000) 66 | when: transactionService.credit(account, new Credit(initialAmount: new BigDecimal(1000), type: CreditType.BUSINESS_DEPOSIT)) 67 | then: 1 * transactionRepository.save(_) 68 | } 69 | 70 | def "create deposite: throw InsufficientFundsException"() { 71 | given: 72 | def account = new Account(num: accountNum) 73 | balanceService.balance(*_) >> ZERO 74 | when: transactionService.credit(account, new Credit(initialAmount: new BigDecimal(1000), type: CreditType.BUSINESS_DEPOSIT)) 75 | then: 76 | 0 * transactionRepository.save(_) 77 | thrown(InsufficientFundsException) 78 | } 79 | 80 | def "load statement"() { 81 | given: 82 | transactionRepository.findAllByAccountNum(accountNum) >> [first, second, third] 83 | expect: transactionService.byAccount(accountNum).size() == 3 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/resources/static/js/user.js: -------------------------------------------------------------------------------- 1 | const s_up = $("#sign_up"); 2 | const s_in = $("#sign_in"); 3 | const err = $("#error"); 4 | const con = $("#congratulation"); 5 | const forget = $("#forget"); 6 | const username = $("#username"); 7 | const password = $("#password"); 8 | const email = $("#email"); 9 | const name = $("#name"); 10 | const address = $("#address"); 11 | 12 | var s_f = true; 13 | 14 | const loadPage = () => { 15 | if (s_f) s_in.show(); 16 | }; 17 | 18 | const showSignIn = (time) => { 19 | s_f = true; 20 | s_in.delay(time).fadeIn(sec, positionFooter); 21 | }; 22 | 23 | $("#registration_btn").bind("click", function (e) { 24 | e.preventDefault(); 25 | request({ 26 | url: '/users/registration', 27 | type: POST, 28 | data: { 29 | username: username.val(), 30 | password: password.val(), 31 | email: email.val(), 32 | name: name.val(), 33 | address: address.val() 34 | } 35 | }, 36 | () => { 37 | s_up.fadeOut(sec); 38 | con.delay(sec).fadeIn(sec, positionFooter); 39 | }, 40 | (err) => { 41 | s_up.fadeOut(sec); 42 | showError(err.responseJSON.message) 43 | }) 44 | }); 45 | 46 | $("#confirm_btn").bind("click", () => 47 | request({ 48 | url: "/users/confirmation", 49 | type: POST, 50 | data: {token : getQueryVariable("token")} 51 | }, 52 | () => showResult("#confirm", "Your account was confirmed successfully"), 53 | err => showResult("#confirm", err.responseJSON.message) 54 | ) 55 | ); 56 | 57 | const showResult = (id, text) => { 58 | let result = $("#result"); 59 | $(id).fadeOut(sec); 60 | result.delay(sec).fadeIn(sec, positionFooter); 61 | $("#result_ans").html(text); 62 | result.click(function () { 63 | window.location.replace('/sign'); 64 | }); 65 | }; 66 | 67 | const showError = text => { 68 | err.delay(sec).fadeIn(sec, positionFooter); 69 | $("#error-text").text(text); 70 | }; 71 | 72 | const showForget = () => { 73 | hideSignIn(); 74 | forget.delay(sec).fadeIn(sec, positionFooter) 75 | }; 76 | 77 | const showRegistration = () => { 78 | hideSignIn(); 79 | s_up.delay(sec).fadeIn(sec, positionFooter) 80 | }; 81 | 82 | const hideSignIn = () => { 83 | s_in.fadeOut(sec); 84 | s_f = false; 85 | }; 86 | 87 | const showLogInForm = () => { 88 | s_up.fadeOut(sec); 89 | forget.fadeOut(sec); 90 | showSignIn(sec, sec); 91 | }; 92 | 93 | $("#error-btn-yes").bind("click", function () { 94 | err.fadeOut(sec); 95 | s_up.delay(sec).fadeIn(sec, positionFooter); 96 | }); 97 | 98 | /** 99 | * For wrong authentication 100 | */ 101 | 102 | $(document).ready(function () { 103 | let str = window.location.search.substring(1); 104 | let pair = str.split("="); 105 | if (pair[0] === 'error' && pair[1].toLowerCase() === 'wrong') { 106 | $("#error-login-msg").show(); 107 | } 108 | if (pair[0] === 'error' && pair[1].toLowerCase() === 'block') { 109 | $("#error-block-msg").show(); 110 | } 111 | }); 112 | 113 | $(".btn").bind("click", function () { 114 | openSignForm(); 115 | }); 116 | 117 | $("#credit_btn").bind("click", function () { 118 | openSignForm(); 119 | }); 120 | 121 | $("#deposit_btn").bind("click", function () { 122 | openSignForm(); 123 | }); 124 | 125 | const openSignForm = () => { 126 | $("#offer").fadeOut(sec); 127 | if (s_f) s_in.delay(sec).fadeIn(sec, positionFooter); 128 | }; 129 | 130 | 131 | const hideCongratulation = () => { 132 | con.fadeOut(sec / 2); 133 | s_in.delay(sec).fadeIn(sec, positionFooter) 134 | }; 135 | --------------------------------------------------------------------------------