├── .github ├── ISSUE_TEMPLATE │ └── шаблон-баг-репорта.md └── workflows │ ├── e2e.yml │ ├── niffler-auth-prod.yaml │ ├── niffler-currency-prod.yaml │ ├── niffler-gateway-prod.yaml │ ├── niffler-ng-client-prod.yaml │ ├── niffler-spend-prod.yaml │ ├── niffler-userdata-prod.yaml │ └── versions.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docker-compose-dev.sh ├── docker-compose-e2e.sh ├── docker-compose.ci.yml ├── docker-compose.mock.yml ├── docker-compose.yml ├── docker.properties ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── localenv.sh ├── niffler-auth ├── build.gradle └── src │ ├── main │ ├── java │ │ └── guru │ │ │ └── qa │ │ │ └── niffler │ │ │ ├── NifflerAuthApplication.java │ │ │ ├── config │ │ │ ├── Callbacks.java │ │ │ ├── NifflerAuthProducerConfiguration.java │ │ │ ├── NifflerAuthServiceConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── keys │ │ │ │ └── KeyManager.java │ │ │ ├── controller │ │ │ ├── ErrorAuthController.java │ │ │ ├── LoginController.java │ │ │ └── RegisterController.java │ │ │ ├── data │ │ │ ├── Authority.java │ │ │ ├── AuthorityEntity.java │ │ │ ├── UserEntity.java │ │ │ └── repository │ │ │ │ └── UserRepository.java │ │ │ ├── model │ │ │ ├── EqualPasswords.java │ │ │ ├── RegistrationModel.java │ │ │ └── UserJson.java │ │ │ ├── service │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── NifflerUserDetailsService.java │ │ │ ├── OauthSessionValidator.java │ │ │ ├── OidcCookiesLogoutAuthenticationSuccessHandler.java │ │ │ ├── PropertiesLogger.java │ │ │ ├── SpecificRequestDumperFilter.java │ │ │ ├── UserService.java │ │ │ └── cors │ │ │ │ ├── CookieCsrfFilter.java │ │ │ │ └── CorsCustomizer.java │ │ │ └── validation │ │ │ ├── EqualPasswordsValidator.java │ │ │ ├── NoWhitespace.java │ │ │ └── NoWhitespaceValidator.java │ └── resources │ │ ├── application.yaml │ │ ├── db │ │ └── migration │ │ │ └── niffler-auth │ │ │ ├── V1__schema_init.sql │ │ │ ├── V2__rename_tables.sql │ │ │ └── V3__persistent_sessions.sql │ │ ├── static │ │ ├── fonts │ │ │ ├── Inter-Regular.woff2 │ │ │ ├── YoungSerif-Regular.ttf │ │ │ ├── YoungSerif-Regular.woff │ │ │ └── YoungSerif-Regular.woff2 │ │ ├── images │ │ │ ├── coin.svg │ │ │ ├── eye-active.svg │ │ │ ├── eye.svg │ │ │ ├── favicon.ico │ │ │ ├── forest-low.svg │ │ │ └── forest.svg │ │ └── styles │ │ │ └── styles.css │ │ └── templates │ │ ├── error.html │ │ ├── login.html │ │ └── register.html │ └── test │ └── java │ └── guru │ └── qa │ └── niffler │ ├── service │ ├── EqualPasswordsValidatorTest.java │ ├── NifflerUserDetailsServiceTest.java │ └── cors │ │ ├── CookieCsrfFilterTest.java │ │ └── CorsCustomizerTest.java │ └── validation │ └── NoWhitespaceValidatorTest.java ├── niffler-currency ├── build.gradle └── src │ ├── main │ ├── java │ │ └── guru │ │ │ └── qa │ │ │ └── niffler │ │ │ ├── NifflerCurrencyApplication.java │ │ │ ├── data │ │ │ ├── CurrencyEntity.java │ │ │ ├── CurrencyValues.java │ │ │ └── repository │ │ │ │ └── CurrencyRepository.java │ │ │ └── service │ │ │ ├── GrpcCurrencyService.java │ │ │ ├── MigrationService.java │ │ │ └── PropertiesLogger.java │ └── resources │ │ ├── Dollar-cropped.svg │ │ ├── Euro-cropped.svg │ │ ├── Rub-cropped.svg │ │ ├── Tenge-cropped.svg │ │ ├── application.yaml │ │ └── db │ │ └── migration │ │ └── niffler-currency │ │ ├── V1__schema_init.sql │ │ └── V2__currency_symbols.sql │ └── test │ └── java │ └── guru │ └── qa │ └── niffler │ └── service │ └── GrpcCurrencyServiceTest.java ├── niffler-diagram.png ├── niffler-e-2-e-tests ├── .dockerignore ├── Dockerfile ├── build.gradle └── src │ └── test │ ├── graphql │ ├── AllPeople.graphql │ ├── Currencies.graphql │ ├── CurrentUser.graphql │ ├── Friends.graphql │ ├── Friends2SubQueries.graphql │ ├── FriendsWithCategories.graphql │ ├── Stat.graphql │ └── UpdateUser.graphql │ ├── java │ └── guru │ │ └── qa │ │ └── niffler │ │ ├── api │ │ ├── AllureDockerApi.java │ │ ├── AllureDockerApiClient.java │ │ ├── AuthApi.java │ │ ├── AuthApiClient.java │ │ ├── GatewayApi.java │ │ ├── GatewayApiClient.java │ │ ├── GatewayV2Api.java │ │ ├── GatewayV2ApiClient.java │ │ ├── GatewayV3Api.java │ │ ├── GatewayV3ApiClient.java │ │ ├── GhApi.java │ │ ├── GhApiClient.java │ │ ├── SpendApi.java │ │ ├── SpendApiClient.java │ │ ├── UserdataApi.java │ │ ├── UserdataApiClient.java │ │ ├── UserdataWsApi.java │ │ ├── UserdataWsClient.java │ │ ├── converter │ │ │ ├── JaxbConverterFactory.java │ │ │ ├── JaxbRequestConverter.java │ │ │ └── JaxbResponseConverter.java │ │ ├── interceptor │ │ │ └── CodeInterceptor.java │ │ └── service │ │ │ ├── RestClient.java │ │ │ └── ThreadLocalCookieStore.java │ │ ├── condition │ │ ├── Color.java │ │ ├── StatConditions.java │ │ ├── TableConditions.java │ │ ├── spend │ │ │ └── Spends.java │ │ └── users │ │ │ └── Users.java │ │ ├── config │ │ ├── Config.java │ │ ├── DockerConfig.java │ │ └── LocalConfig.java │ │ ├── data │ │ ├── DataBase.java │ │ ├── entity │ │ │ ├── CurrencyValues.java │ │ │ ├── auth │ │ │ │ ├── AuthUserEntity.java │ │ │ │ ├── Authority.java │ │ │ │ └── AuthorityEntity.java │ │ │ ├── spend │ │ │ │ ├── CategoryEntity.java │ │ │ │ └── SpendEntity.java │ │ │ └── userdata │ │ │ │ ├── FriendShipId.java │ │ │ │ ├── FriendshipEntity.java │ │ │ │ ├── FriendshipStatus.java │ │ │ │ └── UserEntity.java │ │ ├── jdbc │ │ │ └── DataSourceContext.java │ │ ├── jpa │ │ │ ├── EmfContext.java │ │ │ ├── ThreadSafeEntityManager.java │ │ │ └── TransactionalEntityManager.java │ │ ├── logging │ │ │ ├── AllureAppender.java │ │ │ └── SqlRequestAttachment.java │ │ ├── repository │ │ │ ├── SpendRepository.java │ │ │ ├── SpendRepositoryHibernate.java │ │ │ ├── SpendRepositoryJdbc.java │ │ │ ├── SpendRepositorySpringJdbc.java │ │ │ ├── UserRepository.java │ │ │ ├── UserRepositoryHibernate.java │ │ │ ├── UserRepositoryJdbc.java │ │ │ └── UserRepositorySpringJdbc.java │ │ └── sjdbc │ │ │ ├── AuthUserEntityRowMapper.java │ │ │ ├── AuthorityEntityRowMapper.java │ │ │ ├── CategoryEntityRowMapper.java │ │ │ └── UserEntityRowMapper.java │ │ ├── grpc │ │ └── HeaderClientInterceptor.java │ │ ├── jupiter │ │ ├── annotation │ │ │ ├── AllureIdParam.java │ │ │ ├── ApiLogin.java │ │ │ ├── DisabledByIssue.java │ │ │ ├── Friends.java │ │ │ ├── GenerateCategory.java │ │ │ ├── GenerateSpend.java │ │ │ ├── GenerateUser.java │ │ │ ├── GenerateUsers.java │ │ │ ├── IncomeInvitations.java │ │ │ ├── OutcomeInvitations.java │ │ │ ├── Repository.java │ │ │ ├── ScreenShotTest.java │ │ │ ├── StaticUser.java │ │ │ ├── Token.java │ │ │ ├── User.java │ │ │ └── meta │ │ │ │ ├── DBTest.java │ │ │ │ ├── GqlTest.java │ │ │ │ ├── GrpcTest.java │ │ │ │ ├── KafkaTest.java │ │ │ │ ├── RestTest.java │ │ │ │ ├── SoapTest.java │ │ │ │ └── WebTest.java │ │ ├── converter │ │ │ ├── AllureIdConverter.java │ │ │ └── SpendConverter.java │ │ └── extension │ │ │ ├── AbstractCreateUserExtension.java │ │ │ ├── AllureDockerExtension.java │ │ │ ├── AllureLogsExtension.java │ │ │ ├── AllurePassedAttachmentsExtension.java │ │ │ ├── ApiLoginExtension.java │ │ │ ├── BrowserExtension.java │ │ │ ├── CookiesExtension.java │ │ │ ├── DatabaseCreateUserExtension.java │ │ │ ├── IssueExtension.java │ │ │ ├── JpaExtension.java │ │ │ ├── KafkaExtension.java │ │ │ ├── RestCreateUserExtension.java │ │ │ ├── ScreenShotTestExtension.java │ │ │ ├── SuiteExtension.java │ │ │ ├── TestMethodContextExtension.java │ │ │ ├── UserRepositoryResolver.java │ │ │ └── UsersQueueExtension.java │ │ ├── kafka │ │ └── KafkaConsumer.java │ │ ├── model │ │ ├── allure │ │ │ ├── AllureProject.java │ │ │ ├── AllureResults.java │ │ │ ├── DecodedAllureFile.java │ │ │ └── ScreenDif.java │ │ ├── page │ │ │ ├── PagedModelJson.java │ │ │ └── RestPage.java │ │ ├── queue │ │ │ └── UserModel.java │ │ └── rest │ │ │ ├── CategoryJson.java │ │ │ ├── CurrencyJson.java │ │ │ ├── CurrencyValues.java │ │ │ ├── DataFilterValues.java │ │ │ ├── FriendJson.java │ │ │ ├── FriendshipStatus.java │ │ │ ├── SessionJson.java │ │ │ ├── SpendJson.java │ │ │ ├── StatisticByCategoryJson.java │ │ │ ├── StatisticJson.java │ │ │ ├── StatisticV2Json.java │ │ │ ├── SumByCategory.java │ │ │ ├── TestData.java │ │ │ └── UserJson.java │ │ ├── page │ │ ├── BasePage.java │ │ ├── EditSpendingPage.java │ │ ├── FriendsPage.java │ │ ├── LoginPage.java │ │ ├── MainPage.java │ │ ├── PeoplePage.java │ │ ├── ProfilePage.java │ │ ├── RegisterPage.java │ │ └── component │ │ │ ├── BaseComponent.java │ │ │ ├── Calendar.java │ │ │ ├── Header.java │ │ │ ├── SearchField.java │ │ │ ├── SelectField.java │ │ │ ├── SpendingTable.java │ │ │ └── StatComponent.java │ │ ├── test │ │ ├── gql │ │ │ ├── BaseGraphQlTest.java │ │ │ ├── GraphQlFriendsTest.java │ │ │ ├── GraphQlStatTest.java │ │ │ └── GraphQlUsersTest.java │ │ ├── grpc │ │ │ ├── BaseGrpcTest.java │ │ │ └── NifflerCurrencyTest.java │ │ ├── kafka │ │ │ ├── AuthRegistrationKafkaTest.java │ │ │ └── BaseKafkaTest.java │ │ ├── rest │ │ │ ├── BaseRestTest.java │ │ │ ├── GatewayFriendsRestTest.java │ │ │ ├── GatewayInvitationsRestTest.java │ │ │ ├── GatewaySpendRestTest.java │ │ │ ├── GatewayStatRestTest.java │ │ │ ├── GatewayUsersRestTest.java │ │ │ ├── GatewayV2SpendRestTest.java │ │ │ ├── GatewayV2UsersRestTest.java │ │ │ ├── GatewayV3SpendRestTest.java │ │ │ └── GatewayV3UsersRestTest.java │ │ ├── soap │ │ │ ├── BaseSoapTest.java │ │ │ ├── UserDataFriendsSoapTest.java │ │ │ ├── UserDataInvitationsSoapTest.java │ │ │ └── UserDataUsersSoapTest.java │ │ └── web │ │ │ ├── BaseWebTest.java │ │ │ ├── FriendsTest.java │ │ │ ├── LoginTest.java │ │ │ ├── ProfileTest.java │ │ │ ├── RegistrationTest.java │ │ │ ├── SpendingTest.java │ │ │ └── StatisticTest.java │ │ └── utils │ │ ├── DataUtils.java │ │ ├── DateUtils.java │ │ ├── ErrorMessage.java │ │ ├── GrpcConsoleInterceptor.java │ │ ├── OauthUtils.java │ │ ├── ScreenDiffResult.java │ │ ├── SuccessMessage.java │ │ ├── UrlUtils.java │ │ └── WaitForOne.java │ ├── resources │ ├── META-INF │ │ ├── persistence.xml │ │ └── services │ │ │ └── org.junit.jupiter.api.extension.Extension │ ├── img │ │ └── cat.jpeg │ ├── junit-platform.properties │ ├── logback.xml │ ├── rest │ │ ├── spend0.json │ │ └── spend1.json │ ├── screenshots │ │ ├── local │ │ │ ├── cat.png │ │ │ ├── expected-stat-archived.png │ │ │ └── expected-stat.png │ │ └── selenoid │ │ │ ├── cat.png │ │ │ ├── expected-stat-archived.png │ │ │ └── expected-stat.png │ ├── spy.properties │ ├── tpl │ │ └── sql-query.ftl │ └── xml │ │ └── currentUserRequest.xml │ └── schemas │ └── xjc │ └── userdata.wsdl ├── niffler-gateway ├── build.gradle ├── src │ ├── main │ │ ├── java │ │ │ └── guru │ │ │ │ └── qa │ │ │ │ └── niffler │ │ │ │ ├── NifflerGatewayApplication.java │ │ │ │ ├── config │ │ │ │ ├── NifflerGatewayServiceConfig.java │ │ │ │ ├── SecurityConfigLocal.java │ │ │ │ └── SecurityConfigMain.java │ │ │ │ ├── controller │ │ │ │ ├── CategoriesController.java │ │ │ │ ├── CurrencyController.java │ │ │ │ ├── FriendsController.java │ │ │ │ ├── InvitationsController.java │ │ │ │ ├── SessionController.java │ │ │ │ ├── SpendController.java │ │ │ │ ├── StatController.java │ │ │ │ ├── UserController.java │ │ │ │ ├── graphql │ │ │ │ │ ├── SessionQueryController.java │ │ │ │ │ ├── SpendMutationController.java │ │ │ │ │ ├── SpendQueryController.java │ │ │ │ │ ├── StatQueryController.java │ │ │ │ │ ├── UserMutationController.java │ │ │ │ │ └── UserQueryController.java │ │ │ │ ├── v2 │ │ │ │ │ ├── FriendsV2Controller.java │ │ │ │ │ ├── SpendV2Controller.java │ │ │ │ │ ├── StatV2Controller.java │ │ │ │ │ └── UserV2Controller.java │ │ │ │ └── v3 │ │ │ │ │ ├── FriendsV3Controller.java │ │ │ │ │ ├── SpendV3Controller.java │ │ │ │ │ └── UserV3Controller.java │ │ │ │ ├── ex │ │ │ │ ├── IllegalGqlFieldAccessException.java │ │ │ │ ├── InvalidUserJsonException.java │ │ │ │ ├── NoRestResponseException.java │ │ │ │ ├── NoSoapResponseException.java │ │ │ │ └── TooManySubQueriesException.java │ │ │ │ ├── model │ │ │ │ ├── CategoryJson.java │ │ │ │ ├── CurrencyJson.java │ │ │ │ ├── CurrencyValues.java │ │ │ │ ├── DataFilterValues.java │ │ │ │ ├── ErrorJson.java │ │ │ │ ├── FriendJson.java │ │ │ │ ├── FriendshipStatus.java │ │ │ │ ├── SessionJson.java │ │ │ │ ├── SpendJson.java │ │ │ │ ├── StatisticByCategoryJson.java │ │ │ │ ├── StatisticJson.java │ │ │ │ ├── StatisticV2Json.java │ │ │ │ ├── SumByCategory.java │ │ │ │ ├── UserJson.java │ │ │ │ ├── gql │ │ │ │ │ ├── CategoryGqlInput.java │ │ │ │ │ ├── FriendshipAction.java │ │ │ │ │ ├── FriendshipGqlInput.java │ │ │ │ │ ├── SpendGqlInput.java │ │ │ │ │ ├── UserGql.java │ │ │ │ │ └── UserGqlInput.java │ │ │ │ └── page │ │ │ │ │ ├── PagedModelJson.java │ │ │ │ │ └── RestPage.java │ │ │ │ ├── service │ │ │ │ ├── GraphQlExceptionResolver.java │ │ │ │ ├── PropertiesLogger.java │ │ │ │ ├── RestExceptionHandler.java │ │ │ │ ├── SpendClient.java │ │ │ │ ├── StatisticAggregator.java │ │ │ │ ├── UserDataClient.java │ │ │ │ ├── api │ │ │ │ │ ├── GrpcCurrencyClient.java │ │ │ │ │ ├── RestSpendClient.java │ │ │ │ │ └── RestUserDataClient.java │ │ │ │ ├── cors │ │ │ │ │ └── CorsCustomizer.java │ │ │ │ ├── soap │ │ │ │ │ ├── SoapPageable.java │ │ │ │ │ └── SoapUserDataClient.java │ │ │ │ └── utils │ │ │ │ │ ├── DateUtils.java │ │ │ │ │ ├── GqlQueryPaginationAndSort.java │ │ │ │ │ ├── HttpQueryPaginationAndSort.java │ │ │ │ │ └── ValidationExceptionMessageResolver.java │ │ │ │ └── validation │ │ │ │ ├── IsPhotoString.java │ │ │ │ ├── IsUuidString.java │ │ │ │ ├── PhotoStringValidator.java │ │ │ │ ├── UnixEpochOrLater.java │ │ │ │ └── UnixEpochOrLaterValidator.java │ │ ├── resources │ │ │ ├── application-test.yaml │ │ │ ├── application.yaml │ │ │ ├── graphql │ │ │ │ └── query.graphqls │ │ │ └── static │ │ │ │ └── favicon.ico │ │ └── schemas │ │ │ └── xjc │ │ │ └── userdata.wsdl │ └── test │ │ └── java │ │ └── guru │ │ └── qa │ │ └── niffler │ │ ├── controller │ │ ├── CategoriesControllerTest.java │ │ ├── FriendsControllerTest.java │ │ ├── InvitationsControllerTest.java │ │ ├── SpendControllerTest.java │ │ └── UserControllerTest.java │ │ └── validation │ │ ├── PhotoStringValidatorTest.java │ │ └── UnixEpochOrLaterValidatorTest.java └── userdata.wsdl ├── niffler-grpc-common ├── build.gradle └── src │ └── main │ └── proto │ └── niffler-currency.proto ├── niffler-ng-client ├── .dockerignore ├── .env ├── .env.docker ├── .env.prod ├── .env.staging ├── .eslintrc.cjs ├── .gitignore ├── Dockerfile ├── README.md ├── index.html ├── nginx.conf ├── package-lock.json ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.css │ ├── App.tsx │ ├── api │ │ ├── apiClient.ts │ │ ├── authClient.ts │ │ ├── authUtils.ts │ │ └── url │ │ │ └── auth.ts │ ├── assets │ │ ├── fonts │ │ │ ├── Inter-Regular.woff2 │ │ │ ├── YoungSerif-Regular.ttf │ │ │ ├── YoungSerif-Regular.woff │ │ │ └── YoungSerif-Regular.woff2 │ │ ├── icons │ │ │ ├── coin.svg │ │ │ ├── forest.svg │ │ │ ├── ic_add_friend.svg │ │ │ ├── ic_all.svg │ │ │ ├── ic_archive.svg │ │ │ ├── ic_cal.svg │ │ │ ├── ic_check.svg │ │ │ ├── ic_cross.svg │ │ │ ├── ic_delete.svg │ │ │ ├── ic_edit.svg │ │ │ ├── ic_friends.svg │ │ │ ├── ic_menu.svg │ │ │ ├── ic_plus.svg │ │ │ ├── ic_signout.svg │ │ │ ├── ic_upload.svg │ │ │ └── ic_user.svg │ │ └── images │ │ │ ├── niffler-with-a-coin.png │ │ │ └── niffler-with-a-coin2.png │ ├── components │ │ ├── AppContent │ │ │ └── index.tsx │ │ ├── Button │ │ │ └── index.tsx │ │ ├── CategoryItem │ │ │ └── index.tsx │ │ ├── CategorySection │ │ │ └── index.tsx │ │ ├── CategorySelect │ │ │ └── index.tsx │ │ ├── CurrencySelect │ │ │ └── index.tsx │ │ ├── Diagram │ │ │ └── index.tsx │ │ ├── EmptyUsersState │ │ │ └── index.tsx │ │ ├── Icon │ │ │ ├── CalendarIcon.tsx │ │ │ └── index.tsx │ │ ├── ImageUpload │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Input │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Loader │ │ │ └── index.tsx │ │ ├── MenuAppBar │ │ │ ├── HeaderMenu │ │ │ │ └── index.tsx │ │ │ ├── MenuButton │ │ │ │ └── index.tsx │ │ │ ├── MobileHeaderMenu │ │ │ │ └── index.tsx │ │ │ ├── NewSpendingButton │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── NewCategoryFrom │ │ │ └── index.tsx │ │ ├── PeopleTable │ │ │ ├── AcceptButton │ │ │ │ └── index.tsx │ │ │ ├── ActionButtons │ │ │ │ └── index.tsx │ │ │ ├── AddFriendButton │ │ │ │ └── index.tsx │ │ │ ├── AllTable │ │ │ │ └── index.tsx │ │ │ ├── DeclineButton │ │ │ │ └── index.tsx │ │ │ ├── FriendsTable │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── PrivateRoute │ │ │ └── index.tsx │ │ ├── ProfileForm │ │ │ ├── formValidate.ts │ │ │ └── index.tsx │ │ ├── SearchInput │ │ │ └── index.tsx │ │ ├── SpendingForm │ │ │ ├── formValidate.ts │ │ │ └── index.tsx │ │ ├── SpendingsTable │ │ │ ├── Toolbar │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── TabPanel │ │ │ └── index.tsx │ │ ├── Table │ │ │ ├── HeadCell │ │ │ │ └── index.tsx │ │ │ ├── Pagination │ │ │ │ └── index.tsx │ │ │ ├── TableHead │ │ │ │ └── index.tsx │ │ │ └── TableToolbar │ │ │ │ └── index.tsx │ │ └── Toggle │ │ │ └── index.tsx │ ├── const │ │ └── constants.ts │ ├── context │ │ ├── DialogContext.tsx │ │ ├── SessionContext.tsx │ │ └── SnackBarContext.tsx │ ├── hooks │ │ └── usePrevious.ts │ ├── index.css │ ├── main.tsx │ ├── pages │ │ ├── Authorized │ │ │ └── index.tsx │ │ ├── Logout │ │ │ └── index.tsx │ │ ├── Main │ │ │ └── index.tsx │ │ ├── NotFoundPage │ │ │ └── index.tsx │ │ ├── PeoplePage │ │ │ └── index.tsx │ │ ├── ProfilePage │ │ │ └── index.tsx │ │ └── SpendingPage │ │ │ └── index.tsx │ ├── theme.tsx │ ├── types │ │ ├── Category.ts │ │ ├── CategoryStatistic.ts │ │ ├── Country.ts │ │ ├── Currency.ts │ │ ├── DoughnutOptions.ts │ │ ├── Error.ts │ │ ├── FilterPeriod.ts │ │ ├── FriendshipStatus.ts │ │ ├── IStringIndex.ts │ │ ├── JsonTokens.ts │ │ ├── Likes.ts │ │ ├── Order.ts │ │ ├── Pageable.ts │ │ ├── Photo.ts │ │ ├── RequestHandler.ts │ │ ├── Session.ts │ │ ├── Spending.ts │ │ ├── Statistic.ts │ │ ├── User.ts │ │ └── Void.ts │ ├── utils │ │ ├── arrays.ts │ │ ├── chart.ts │ │ ├── comparator.ts │ │ ├── dataConverter.ts │ │ ├── date.ts │ │ └── form.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── niffler-ng-gql-client ├── .env ├── .env.docker ├── .env.prod ├── .env.staging ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── codegen.yml ├── index.html ├── nginx.conf ├── package-lock.json ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.css │ ├── App.tsx │ ├── api │ │ ├── authClient.ts │ │ ├── authUtils.ts │ │ ├── graphqlClient.ts │ │ ├── mutations │ │ │ ├── category.graphql │ │ │ ├── spend.graphql │ │ │ └── user.graphql │ │ ├── queries │ │ │ ├── currency.graphql │ │ │ ├── session.graphql │ │ │ ├── spend.graphql │ │ │ ├── stat.graphql │ │ │ └── user.graphql │ │ └── url │ │ │ └── auth.ts │ ├── assets │ │ ├── fonts │ │ │ ├── Inter-Regular.woff2 │ │ │ ├── YoungSerif-Regular.ttf │ │ │ ├── YoungSerif-Regular.woff │ │ │ └── YoungSerif-Regular.woff2 │ │ ├── icons │ │ │ ├── coin.svg │ │ │ ├── forest.svg │ │ │ ├── ic_add_friend.svg │ │ │ ├── ic_all.svg │ │ │ ├── ic_archive.svg │ │ │ ├── ic_cal.svg │ │ │ ├── ic_check.svg │ │ │ ├── ic_cross.svg │ │ │ ├── ic_delete.svg │ │ │ ├── ic_edit.svg │ │ │ ├── ic_friends.svg │ │ │ ├── ic_menu.svg │ │ │ ├── ic_plus.svg │ │ │ ├── ic_signout.svg │ │ │ ├── ic_upload.svg │ │ │ └── ic_user.svg │ │ └── images │ │ │ ├── niffler-with-a-coin.png │ │ │ └── niffler-with-a-coin2.png │ ├── components │ │ ├── AppContent │ │ │ └── index.tsx │ │ ├── Button │ │ │ └── index.tsx │ │ ├── CategoryItem │ │ │ └── index.tsx │ │ ├── CategorySection │ │ │ └── index.tsx │ │ ├── CategorySelect │ │ │ └── index.tsx │ │ ├── CommonError │ │ │ └── index.tsx │ │ ├── CurrencySelect │ │ │ └── index.tsx │ │ ├── Diagram │ │ │ └── index.tsx │ │ ├── EmptyUsersState │ │ │ └── index.tsx │ │ ├── Icon │ │ │ ├── CalendarIcon.tsx │ │ │ └── index.tsx │ │ ├── ImageUpload │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Input │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Loader │ │ │ └── index.tsx │ │ ├── MenuAppBar │ │ │ ├── HeaderMenu │ │ │ │ └── index.tsx │ │ │ ├── MenuButton │ │ │ │ └── index.tsx │ │ │ ├── MobileHeaderMenu │ │ │ │ └── index.tsx │ │ │ ├── NewSpendingButton │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── NewCategoryFrom │ │ │ └── index.tsx │ │ ├── PeopleTable │ │ │ ├── AcceptButton │ │ │ │ └── index.tsx │ │ │ ├── ActionButtons │ │ │ │ └── index.tsx │ │ │ ├── AddFriendButton │ │ │ │ └── index.tsx │ │ │ ├── AllTable │ │ │ │ └── index.tsx │ │ │ ├── DeclineButton │ │ │ │ └── index.tsx │ │ │ ├── FriendsTable │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── PrivateRoute │ │ │ └── index.tsx │ │ ├── ProfileForm │ │ │ ├── formValidate.ts │ │ │ └── index.tsx │ │ ├── SearchInput │ │ │ └── index.tsx │ │ ├── SpendingForm │ │ │ ├── AddFormComponent │ │ │ │ └── index.tsx │ │ │ ├── EditFormComponent │ │ │ │ └── index.tsx │ │ │ ├── FormComponent │ │ │ │ └── index.tsx │ │ │ ├── formValidate.ts │ │ │ └── index.tsx │ │ ├── SpendingsTable │ │ │ ├── Toolbar │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── TabPanel │ │ │ └── index.tsx │ │ ├── Table │ │ │ ├── HeadCell │ │ │ │ └── index.tsx │ │ │ ├── Pagination │ │ │ │ └── index.tsx │ │ │ ├── TableHead │ │ │ │ └── index.tsx │ │ │ └── TableToolbar │ │ │ │ └── index.tsx │ │ └── Toggle │ │ │ └── index.tsx │ ├── const │ │ └── constants.ts │ ├── context │ │ ├── DialogContext.tsx │ │ └── SnackBarContext.tsx │ ├── generated │ │ └── graphql.tsx │ ├── hooks │ │ └── usePrevious.ts │ ├── index.css │ ├── main.tsx │ ├── pages │ │ ├── Authorized │ │ │ └── index.tsx │ │ ├── Logout │ │ │ └── index.tsx │ │ ├── Main │ │ │ └── index.tsx │ │ ├── NotFoundPage │ │ │ └── index.tsx │ │ ├── PeoplePage │ │ │ └── index.tsx │ │ ├── ProfilePage │ │ │ └── index.tsx │ │ └── SpendingPage │ │ │ └── index.tsx │ ├── theme.tsx │ ├── types │ │ ├── Category.ts │ │ ├── CategoryStatistic.ts │ │ ├── Currency.ts │ │ ├── DoughnutOptions.ts │ │ ├── Error.ts │ │ ├── FilterPeriod.ts │ │ ├── FriendshipStatus.ts │ │ ├── IStringIndex.ts │ │ ├── JsonTokens.ts │ │ ├── Order.ts │ │ ├── RequestHandler.ts │ │ ├── Session.ts │ │ ├── Spending.ts │ │ ├── Statistic.ts │ │ ├── User.ts │ │ └── Void.ts │ ├── utils │ │ ├── arrays.ts │ │ ├── chart.ts │ │ ├── comparator.ts │ │ ├── dataConverter.ts │ │ ├── date.ts │ │ └── form.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── niffler-spend ├── build.gradle └── src │ ├── main │ ├── java │ │ └── guru │ │ │ └── qa │ │ │ └── niffler │ │ │ ├── NifflerSpendApplication.java │ │ │ ├── controller │ │ │ ├── CategoriesController.java │ │ │ ├── SpendController.java │ │ │ ├── StatController.java │ │ │ ├── v2 │ │ │ │ ├── SpendV2Controller.java │ │ │ │ └── StatV2Controller.java │ │ │ └── v3 │ │ │ │ └── SpendV3Controller.java │ │ │ ├── data │ │ │ ├── CategoryEntity.java │ │ │ ├── SpendEntity.java │ │ │ ├── projection │ │ │ │ ├── SumByCategory.java │ │ │ │ ├── SumByCategoryAggregate.java │ │ │ │ ├── SumByCategoryInUserCurrency.java │ │ │ │ └── SumByCategoryInfo.java │ │ │ └── repository │ │ │ │ ├── CategoryRepository.java │ │ │ │ └── SpendRepository.java │ │ │ ├── ex │ │ │ ├── CategoryNotFoundException.java │ │ │ ├── InvalidCategoryNameException.java │ │ │ ├── SpendExportException.java │ │ │ ├── SpendNotFoundException.java │ │ │ └── TooManyCategoriesException.java │ │ │ ├── model │ │ │ ├── CategoryJson.java │ │ │ ├── CurrencyJson.java │ │ │ ├── CurrencyValues.java │ │ │ ├── DataFilterValues.java │ │ │ ├── ErrorJson.java │ │ │ ├── SpendJson.java │ │ │ ├── StatisticByCategoryJson.java │ │ │ ├── StatisticJson.java │ │ │ └── StatisticV2Json.java │ │ │ └── service │ │ │ ├── CategoryService.java │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── GrpcCurrencyClient.java │ │ │ ├── PropertiesLogger.java │ │ │ ├── SpendService.java │ │ │ └── StatService.java │ └── resources │ │ ├── application-test.yaml │ │ ├── application.yaml │ │ └── db │ │ └── migration │ │ └── niffler-spend │ │ ├── V1__schema_init.sql │ │ ├── V2__rename_tables.sql │ │ ├── V3__add_archived_field_to_category.sql │ │ └── V4__rename_category_field.sql │ └── test │ ├── java │ └── guru │ │ └── qa │ │ └── niffler │ │ ├── controller │ │ ├── CategoriesControllerTest.java │ │ ├── v2 │ │ │ ├── SpendV2ControllerTest.java │ │ │ └── StatV2ControllerTest.java │ │ └── v3 │ │ │ └── SpendV3ControllerTest.java │ │ └── service │ │ ├── CategoryServiceTest.java │ │ └── StatServiceTest.java │ └── resources │ └── guru │ └── qa │ └── niffler │ └── controller │ ├── CategoriesControllerTest.sql │ ├── v2 │ ├── SpendV2ControllerTest.sql │ └── StatV2ControllerTest.sql │ └── v3 │ └── SpendV3ControllerTest.sql ├── niffler-userdata ├── build.gradle └── src │ ├── main │ ├── java │ │ └── guru │ │ │ └── qa │ │ │ └── niffler │ │ │ ├── NifflerUserdataApplication.java │ │ │ ├── config │ │ │ ├── NifflerUserdataConsumerConfiguration.java │ │ │ └── NifflerUserdataServiceConfig.java │ │ │ ├── controller │ │ │ ├── FriendsController.java │ │ │ ├── InvitationsController.java │ │ │ ├── UserController.java │ │ │ ├── v2 │ │ │ │ ├── FriendsV2Controller.java │ │ │ │ └── UserV2Controller.java │ │ │ └── v3 │ │ │ │ ├── FriendsV3Controller.java │ │ │ │ └── UserV3Controller.java │ │ │ ├── data │ │ │ ├── CurrencyValues.java │ │ │ ├── FriendShipId.java │ │ │ ├── FriendshipEntity.java │ │ │ ├── FriendshipStatus.java │ │ │ ├── UserEntity.java │ │ │ ├── projection │ │ │ │ └── UserWithStatus.java │ │ │ └── repository │ │ │ │ └── UserRepository.java │ │ │ ├── ex │ │ │ ├── NotFoundException.java │ │ │ └── SameUsernameException.java │ │ │ ├── model │ │ │ ├── ErrorJson.java │ │ │ ├── FriendshipStatus.java │ │ │ ├── IUserJson.java │ │ │ ├── UserJson.java │ │ │ └── UserJsonBulk.java │ │ │ ├── service │ │ │ ├── GlobalExceptionHandler.java │ │ │ ├── MigrationService.java │ │ │ ├── PropertiesLogger.java │ │ │ ├── SmallPhoto.java │ │ │ └── UserService.java │ │ │ └── soap │ │ │ ├── BaseEndpoint.java │ │ │ ├── FriendsEndpoint.java │ │ │ ├── InvitationsEndpoint.java │ │ │ ├── SpringPageable.java │ │ │ └── UserEndpoint.java │ └── resources │ │ ├── application-test.yaml │ │ ├── application.yaml │ │ ├── db │ │ └── migration │ │ │ └── niffler-userdata │ │ │ ├── V1__schema_init.sql │ │ │ ├── V2__rename_tables.sql │ │ │ ├── V3__friendship.sql │ │ │ ├── V4__small_avatar.sql │ │ │ └── V5__full_name.sql │ │ └── userdata.xsd │ └── test │ ├── java │ └── guru │ │ └── qa │ │ └── niffler │ │ ├── controller │ │ ├── UserControllerTest.java │ │ └── v3 │ │ │ ├── FriendsV3ControllerTest.java │ │ │ └── UserV3ControllerTest.java │ │ └── service │ │ └── UserServiceTest.java │ └── resources │ └── guru │ └── qa │ └── niffler │ └── controller │ ├── UserControllerTest.sql │ └── v3 │ ├── FriendsV3ControllerTest.sql │ └── UserV3ControllerTest.sql ├── postgres └── script │ └── init-database.sh ├── selenoid └── browsers.json ├── settings.gradle └── wiremock ├── grpc └── mappings │ ├── calculateRate.json │ └── getAllCurrencies.json └── rest └── mappings └── currentUser.json /.github/ISSUE_TEMPLATE/шаблон-баг-репорта.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Баг-репорт 3 | about: Описание 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Предусловие** 11 | Пример - "юзер авторизован"/"открыта главная страница сайта"/ "у юзера есть 3 спендинга в рублевой валюте за текущий 12 | месяц" 13 | 14 | **Шаги для воспроизведения бага** 15 | 16 | 1. 17 | 2. 18 | 3. 19 | 20 | ... 21 | 22 | **Ожидаемое поведение** 23 | Как система должна себя вести в соответствии с требованиями 24 | 25 | **Фактический результат** 26 | Как система себя ведет в действительности 27 | 28 | **Окружение** 29 | Если баг воспроизводится на определенных версиях операционных систем/браузеров/девайсов 30 | 31 | **Скриншоты/ Видео** 32 | Если есть возможность приложить визуальное подтверждение бага 33 | 34 | **Дополнительно** 35 | Место для дополнительной полезной информации о баге 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build 4 | **/build 5 | !gradle/wrapper/gradle-wrapper.jar 6 | !gradle/wrapper/gradle-wrapper.properties 7 | !**/src/main/**/build/ 8 | !**/src/test/**/build/ 9 | 10 | ### STS ### 11 | .apt_generated 12 | .classpath 13 | .factorypath 14 | .project 15 | .settings 16 | .springBeans 17 | .sts4-cache 18 | bin/ 19 | !**/src/main/**/bin/ 20 | !**/src/test/**/bin/ 21 | 22 | ### IntelliJ IDEA ### 23 | .idea 24 | *.iws 25 | *.iml 26 | *.ipr 27 | out/ 28 | !**/src/main/**/out/ 29 | !**/src/test/**/out/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### gRPC generated files ### 42 | /niffler-grpc-common/src/generated/** 43 | /niffler-e-2-e-tests/.screen-output/** 44 | -------------------------------------------------------------------------------- /docker-compose-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ./docker.properties 3 | export PROFILE=docker 4 | export PREFIX="${IMAGE_PREFIX}" 5 | 6 | docker compose down 7 | docker_containers=$(docker ps -a -q) 8 | docker_images=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep 'niffler') 9 | 10 | if [ ! -z "$docker_containers" ]; then 11 | echo "### Stop containers: $docker_containers ###" 12 | docker stop $docker_containers 13 | docker rm $docker_containers 14 | fi 15 | 16 | if [ ! -z "$docker_images" ]; then 17 | echo "### Remove images: $docker_images ###" 18 | docker rmi $docker_images 19 | fi 20 | 21 | echo '### Java version ###' 22 | java --version 23 | bash ./gradlew clean 24 | if [ "$1" = "push" ]; then 25 | echo "### Build & push images ###" 26 | bash ./gradlew jib -x :niffler-e-2-e-tests:test -Duser.timezone=UTC 27 | docker compose push frontend.niffler.dc 28 | else 29 | echo "### Build images ###" 30 | bash ./gradlew jibDockerBuild -x :niffler-e-2-e-tests:test -Duser.timezone=UTC 31 | fi 32 | 33 | docker compose up -d 34 | docker ps -a 35 | -------------------------------------------------------------------------------- /docker-compose-e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ./docker.properties 3 | export COMPOSE_PROFILES=test 4 | export PROFILE=docker 5 | export PREFIX="${IMAGE_PREFIX}" 6 | 7 | export ALLURE_DOCKER_API=http://allure:5050/ 8 | export HEAD_COMMIT_MESSAGE="local build" 9 | export ARCH=$(uname -m) 10 | 11 | docker compose down 12 | docker_containers=$(docker ps -a -q) 13 | docker_images=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep 'niffler') 14 | 15 | if [ ! -z "$docker_containers" ]; then 16 | echo "### Stop containers: $docker_containers ###" 17 | docker stop $docker_containers 18 | docker rm $docker_containers 19 | fi 20 | 21 | if [ ! -z "$docker_images" ]; then 22 | echo "### Remove images: $docker_images ###" 23 | docker rmi $docker_images 24 | fi 25 | 26 | echo '### Java version ###' 27 | java --version 28 | bash ./gradlew clean 29 | bash ./gradlew jibDockerBuild -x :niffler-e-2-e-tests:test -Duser.timezone=UTC 30 | 31 | docker pull twilio/selenoid:chrome_stable_135 32 | docker compose up -d 33 | docker ps -a 34 | -------------------------------------------------------------------------------- /docker-compose.mock.yml: -------------------------------------------------------------------------------- 1 | services: 2 | currency.niffler.dc: 3 | container_name: currency.niffler.dc 4 | image: adven27/grpc-wiremock:latest 5 | volumes: 6 | - ./wiremock/grpc:/wiremock #stubs 7 | - ./niffler-grpc-common/src/main/proto:/proto #proto 8 | ports: 9 | - 8888:8888 # wiremock port 10 | - 8092:8092 # gRPC port 11 | environment: 12 | - GRPC_SERVER_PORT=8092 13 | networks: 14 | - niffler-network 15 | 16 | userdata.niffler.dc: 17 | container_name: userdata.niffler.dc 18 | image: wiremock/wiremock:2.35.0 19 | ports: 20 | - 8089:8089 21 | restart: always 22 | command: [ "--port", "8089", "--global-response-templating", "--enable-stub-cors" ] 23 | volumes: 24 | - ./wiremock/rest:/home/wiremock 25 | networks: 26 | - niffler-network 27 | 28 | networks: 29 | niffler-network: 30 | driver: bridge 31 | -------------------------------------------------------------------------------- /docker.properties: -------------------------------------------------------------------------------- 1 | IMAGE_PREFIX=qaguru 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=false 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /localenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker stop $(docker ps -a -q) 4 | docker rm $(docker ps -a -q) 5 | 6 | docker run --name niffler-all -p 5432:5432 -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data -v ./postgres/script:/docker-entrypoint-initdb.d -e CREATE_DATABASES=niffler-auth,niffler-currency,niffler-spend,niffler-userdata -e TZ=GMT+3 -e PGTZ=GMT+3 -d postgres:15.1 --max_prepared_transactions=100 7 | docker run --name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 -p 2181:2181 -d confluentinc/cp-zookeeper:7.3.2 8 | docker run --name=kafka -e KAFKA_BROKER_ID=1 \ 9 | -e KAFKA_ZOOKEEPER_CONNECT=$(docker inspect zookeeper --format='{{ .NetworkSettings.IPAddress }}'):2181 \ 10 | -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \ 11 | -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \ 12 | -e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 \ 13 | -e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 \ 14 | -p 9092:9092 -d confluentinc/cp-kafka:7.3.2 15 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/NifflerAuthApplication.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler; 2 | 3 | import guru.qa.niffler.service.PropertiesLogger; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | public class NifflerAuthApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication springApplication = new SpringApplication(NifflerAuthApplication.class); 12 | springApplication.addListeners(new PropertiesLogger()); 13 | springApplication.run(args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/config/Callbacks.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.config; 2 | 3 | public interface Callbacks { 4 | 5 | interface Android { 6 | String login = "/callback"; 7 | String logout = "/logout_callback"; 8 | String init = "/start"; 9 | } 10 | 11 | interface Web { 12 | String login = "/authorized"; 13 | String logout = "/logout"; 14 | String init = "/main"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/data/Authority.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data; 2 | 3 | public enum Authority { 4 | read, write 5 | } 6 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/data/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.repository; 2 | 3 | import guru.qa.niffler.data.UserEntity; 4 | import jakarta.annotation.Nonnull; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | public interface UserRepository extends JpaRepository { 11 | 12 | @Nonnull 13 | Optional findByUsername(@Nonnull String username); 14 | } 15 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/model/EqualPasswords.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import guru.qa.niffler.validation.EqualPasswordsValidator; 4 | import jakarta.validation.Constraint; 5 | import jakarta.validation.Payload; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Target(ElementType.TYPE) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Constraint(validatedBy = {EqualPasswordsValidator.class}) 15 | public @interface EqualPasswords { 16 | String message() default "Passwords should be equal"; 17 | 18 | Class[] groups() default {}; 19 | 20 | Class[] payload() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/model/RegistrationModel.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import guru.qa.niffler.validation.NoWhitespace; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | 7 | @EqualPasswords 8 | public record RegistrationModel( 9 | @NotBlank(message = "Username can not be blank") 10 | @NoWhitespace(message = "Username must not contain whitespaces") 11 | @Size(min = 3, max = 50, message = "Allowed username length should be from 3 to 50 characters") 12 | String username, 13 | @NotBlank(message = "Password can not be blank") 14 | @NoWhitespace(message = "Password must not contain whitespaces") 15 | @Size(min = 3, max = 12, message = "Allowed password length should be from 3 to 12 characters") 16 | String password, 17 | String passwordSubmit) { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/model/UserJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public record UserJson( 6 | @JsonProperty("username") 7 | String username) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/validation/EqualPasswordsValidator.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import guru.qa.niffler.model.EqualPasswords; 4 | import guru.qa.niffler.model.RegistrationModel; 5 | import jakarta.validation.ConstraintValidator; 6 | import jakarta.validation.ConstraintValidatorContext; 7 | 8 | public class EqualPasswordsValidator implements ConstraintValidator { 9 | @Override 10 | public boolean isValid(RegistrationModel form, ConstraintValidatorContext context) { 11 | boolean isValid = form.password().equals(form.passwordSubmit()); 12 | if (!isValid) { 13 | context.disableDefaultConstraintViolation(); 14 | context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) 15 | .addPropertyNode("password") 16 | .addConstraintViolation(); 17 | } 18 | return isValid; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/validation/NoWhitespace.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Constraint(validatedBy = NoWhitespaceValidator.class) 12 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface NoWhitespace { 15 | String message() default "String must not contain whitespace"; 16 | 17 | Class[] groups() default {}; 18 | 19 | Class[] payload() default {}; 20 | } 21 | -------------------------------------------------------------------------------- /niffler-auth/src/main/java/guru/qa/niffler/validation/NoWhitespaceValidator.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.ConstraintValidator; 4 | import jakarta.validation.ConstraintValidatorContext; 5 | 6 | public class NoWhitespaceValidator implements ConstraintValidator { 7 | 8 | @Override 9 | public boolean isValid(String value, ConstraintValidatorContext context) { 10 | return !value.contains(" "); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/db/migration/niffler-auth/V2__rename_tables.sql: -------------------------------------------------------------------------------- 1 | alter table "users" 2 | rename to "user"; 3 | alter table "authorities" 4 | rename to "authority"; -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/fonts/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-auth/src/main/resources/static/fonts/Inter-Regular.woff2 -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/fonts/YoungSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-auth/src/main/resources/static/fonts/YoungSerif-Regular.ttf -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/fonts/YoungSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-auth/src/main/resources/static/fonts/YoungSerif-Regular.woff -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/fonts/YoungSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-auth/src/main/resources/static/fonts/YoungSerif-Regular.woff2 -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/images/eye-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/images/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-auth/src/main/resources/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-auth/src/main/resources/static/images/favicon.ico -------------------------------------------------------------------------------- /niffler-auth/src/test/java/guru/qa/niffler/validation/NoWhitespaceValidatorTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.ConstraintValidatorContext; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.ValueSource; 6 | import org.mockito.Mockito; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertFalse; 9 | 10 | class NoWhitespaceValidatorTest { 11 | 12 | private final NoWhitespaceValidator validator = new NoWhitespaceValidator(); 13 | private final ConstraintValidatorContext context = Mockito.mock(ConstraintValidatorContext.class); 14 | 15 | @ValueSource(strings = { 16 | "foo ", "foo bar" 17 | }) 18 | @ParameterizedTest 19 | void shouldReturnFalseForStringsWithSpaces(String input) { 20 | assertFalse( 21 | validator.isValid(input, context) 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /niffler-currency/src/main/java/guru/qa/niffler/NifflerCurrencyApplication.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler; 2 | 3 | import guru.qa.niffler.service.PropertiesLogger; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | public class NifflerCurrencyApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication springApplication = new SpringApplication(NifflerCurrencyApplication.class); 12 | springApplication.addListeners(new PropertiesLogger()); 13 | springApplication.run(args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /niffler-currency/src/main/java/guru/qa/niffler/data/CurrencyValues.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | 5 | @RequiredArgsConstructor 6 | public enum CurrencyValues { 7 | RUB("Rub-cropped.svg"), USD("Dollar-cropped.svg"), EUR("Euro-cropped.svg"), KZT("Tenge-cropped.svg"); 8 | 9 | public final String symbolResource; 10 | } 11 | -------------------------------------------------------------------------------- /niffler-currency/src/main/java/guru/qa/niffler/data/repository/CurrencyRepository.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.repository; 2 | 3 | import guru.qa.niffler.data.CurrencyEntity; 4 | import guru.qa.niffler.data.CurrencyValues; 5 | import jakarta.annotation.Nonnull; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.Optional; 9 | import java.util.UUID; 10 | 11 | public interface CurrencyRepository extends JpaRepository { 12 | 13 | @Nonnull 14 | Optional findByCurrency(@Nonnull CurrencyValues currency); 15 | } 16 | -------------------------------------------------------------------------------- /niffler-currency/src/main/resources/Dollar-cropped.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-currency/src/main/resources/Euro-cropped.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /niffler-currency/src/main/resources/Tenge-cropped.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /niffler-currency/src/main/resources/db/migration/niffler-currency/V1__schema_init.sql: -------------------------------------------------------------------------------- 1 | create extension if not exists "uuid-ossp"; 2 | 3 | create table if not exists "currency" 4 | ( 5 | id UUID unique not null default uuid_generate_v1() primary key, 6 | currency varchar(50) unique not null, 7 | currency_rate float not null 8 | ); 9 | 10 | alter table "currency" 11 | owner to postgres; 12 | 13 | delete 14 | from "currency"; 15 | insert into "currency"(currency, currency_rate) 16 | values ('RUB', 0.015); 17 | insert into "currency"(currency, currency_rate) 18 | values ('KZT', 0.0021); 19 | insert into "currency"(currency, currency_rate) 20 | values ('EUR', 1.08); 21 | insert into "currency"(currency, currency_rate) 22 | values ('USD', 1.0); 23 | -------------------------------------------------------------------------------- /niffler-currency/src/main/resources/db/migration/niffler-currency/V2__currency_symbols.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "currency" 2 | ADD symbol bytea; -------------------------------------------------------------------------------- /niffler-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-diagram.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/.dockerignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /niffler-e-2-e-tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:21-jdk 2 | 3 | WORKDIR /niffler 4 | ENV TZ=Europe/Moscow 5 | COPY ./gradle ./gradle 6 | COPY ./niffler-e-2-e-tests ./niffler-e-2-e-tests 7 | COPY ./niffler-grpc-common ./niffler-grpc-common 8 | COPY ./gradlew ./ 9 | COPY ./build.gradle ./ 10 | COPY ./settings.gradle ./ 11 | COPY ./gradle.properties ./ 12 | 13 | CMD ./gradlew test -Dtest.env=docker -Drepository=jpa -Duser.timezone=Europe/Moscow 14 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/AllPeople.graphql: -------------------------------------------------------------------------------- 1 | query AllPeople { 2 | allPeople(page: 0, size: 10) { 3 | edges { 4 | node { 5 | id 6 | username 7 | friendshipStatus 8 | } 9 | } 10 | pageInfo { 11 | hasPreviousPage 12 | hasNextPage 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/Currencies.graphql: -------------------------------------------------------------------------------- 1 | query Currencies { 2 | currencies { 3 | currency 4 | currencyRate 5 | } 6 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/CurrentUser.graphql: -------------------------------------------------------------------------------- 1 | query CurrentUser { 2 | user { 3 | id 4 | username 5 | fullname 6 | photo 7 | categories { 8 | name 9 | archived 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/Friends.graphql: -------------------------------------------------------------------------------- 1 | query Friends { 2 | user { 3 | username 4 | friends(page: 0, size: 10) { 5 | edges { 6 | node { 7 | id 8 | username 9 | friendshipStatus 10 | } 11 | } 12 | pageInfo { 13 | hasPreviousPage 14 | hasNextPage 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/FriendsWithCategories.graphql: -------------------------------------------------------------------------------- 1 | query FriendsWithCategories { 2 | user { 3 | username 4 | categories { 5 | name 6 | } 7 | friends(page: 0, size: 10) { 8 | edges { 9 | node { 10 | username 11 | categories { 12 | name 13 | } 14 | } 15 | } 16 | pageInfo { 17 | hasPreviousPage 18 | hasNextPage 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/Stat.graphql: -------------------------------------------------------------------------------- 1 | query Stat($filterPeriod: FilterPeriod $filterCurrency: CurrencyValues $statCurrency: CurrencyValues) { 2 | stat(filterPeriod: $filterPeriod filterCurrency: $filterCurrency statCurrency: $statCurrency) { 3 | total, 4 | currency, 5 | statByCategories { 6 | categoryName 7 | currency 8 | sum 9 | firstSpendDate 10 | lastSpendDate 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/graphql/UpdateUser.graphql: -------------------------------------------------------------------------------- 1 | mutation UpdateUser($input: UserInput!) { 2 | user(input: $input) { 3 | id 4 | username 5 | fullname 6 | photo 7 | } 8 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/GhApi.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.api; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import retrofit2.Call; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Header; 7 | import retrofit2.http.Headers; 8 | import retrofit2.http.Path; 9 | 10 | public interface GhApi { 11 | 12 | @GET("repos/qa-guru/niffler/issues/{ISSUE_NUMBER}") 13 | @Headers({ 14 | "Accept: application/vnd.github+json", 15 | "X-GitHub-Api-Version: 2022-11-28" 16 | }) 17 | Call issue(@Header("Authorization") String bearerToken, 18 | @Path("ISSUE_NUMBER") String issueNumber); 19 | } 20 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/GhApiClient.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.api; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import guru.qa.niffler.api.service.RestClient; 5 | import io.qameta.allure.Step; 6 | 7 | import javax.annotation.ParametersAreNonnullByDefault; 8 | import java.io.IOException; 9 | 10 | @ParametersAreNonnullByDefault 11 | public class GhApiClient extends RestClient { 12 | 13 | private final GhApi ghApi; 14 | 15 | public GhApiClient() { 16 | super("https://api.github.com/"); 17 | this.ghApi = retrofit.create(GhApi.class); 18 | } 19 | 20 | @Step("Get issue state by number: {issueNumber}") 21 | public String getIssueState(String issueNumber) throws IOException { 22 | JsonNode responseBody = ghApi.issue( 23 | "Bearer " + System.getenv("GITHUB_TOKEN"), 24 | issueNumber 25 | ).execute().body(); 26 | return responseBody.get("state").asText(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/condition/Color.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.condition; 2 | 3 | import lombok.AllArgsConstructor; 4 | 5 | @AllArgsConstructor 6 | public enum Color { 7 | yellow("rgba(255, 183, 3, 1)"), 8 | green("rgba(53, 173, 123, 1)"), 9 | blue100("rgba(41, 65, 204, 1)"), 10 | orange("rgba(251, 133, 0, 1)"), 11 | azure("rgba(33, 158, 188, 1)"), 12 | blue200("rgba(22, 41, 149, 1)"), 13 | red("rgba(247, 89, 67, 1)"), 14 | skyBlue("rgba(99, 181, 226, 1)"), 15 | purple("rgba(148, 85, 198, 1)"); 16 | 17 | public final String rgb; 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/condition/TableConditions.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.condition; 2 | 3 | import com.codeborne.selenide.WebElementsCondition; 4 | import guru.qa.niffler.condition.spend.Spends; 5 | import guru.qa.niffler.condition.users.Users; 6 | import guru.qa.niffler.model.rest.SpendJson; 7 | import guru.qa.niffler.model.rest.UserJson; 8 | 9 | import javax.annotation.Nonnull; 10 | import javax.annotation.ParametersAreNonnullByDefault; 11 | 12 | @ParametersAreNonnullByDefault 13 | public class TableConditions { 14 | 15 | @Nonnull 16 | public static WebElementsCondition spends(SpendJson... expectedSpends) { 17 | return new Spends(expectedSpends); 18 | } 19 | 20 | @Nonnull 21 | public static WebElementsCondition users(UserJson... expectedUsers) { 22 | return new Users(expectedUsers); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/DataBase.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data; 2 | 3 | import guru.qa.niffler.config.Config; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | public enum DataBase { 7 | USERDATA("jdbc:postgresql://%s/niffler-userdata"), 8 | AUTH("jdbc:postgresql://%s/niffler-auth"), 9 | SPEND("jdbc:postgresql://%s/niffler-spend"), 10 | CURRENCY("jdbc:postgresql://%s/niffler-currency"); 11 | private final String url; 12 | 13 | DataBase(String url) { 14 | this.url = url; 15 | } 16 | 17 | private static final Config CFG = Config.getInstance(); 18 | 19 | public String getUrl() { 20 | return String.format(url, CFG.databaseAddress()); 21 | } 22 | 23 | public String getUrlForP6Spy() { 24 | return "jdbc:p6spy:" + StringUtils.substringAfter(getUrl(), "jdbc:"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/entity/CurrencyValues.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.entity; 2 | 3 | public enum CurrencyValues { 4 | RUB, USD, EUR, KZT 5 | } 6 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/entity/auth/Authority.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.entity.auth; 2 | 3 | public enum Authority { 4 | read, write 5 | } 6 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/entity/userdata/FriendShipId.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.entity.userdata; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.io.Serializable; 7 | import java.util.Objects; 8 | import java.util.UUID; 9 | 10 | @Getter 11 | @Setter 12 | public class FriendShipId implements Serializable { 13 | 14 | private UUID requester; 15 | private UUID addressee; 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | FriendShipId friendsId = (FriendShipId) o; 22 | return Objects.equals(requester, friendsId.requester) && Objects.equals(addressee, friendsId.addressee); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(requester, addressee); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/entity/userdata/FriendshipStatus.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.entity.userdata; 2 | 3 | public enum FriendshipStatus { 4 | PENDING, 5 | ACCEPTED 6 | } 7 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/logging/SqlRequestAttachment.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.logging; 2 | 3 | import io.qameta.allure.attachment.AttachmentData; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public class SqlRequestAttachment implements AttachmentData { 8 | private final String name; 9 | private final String sql; 10 | 11 | public SqlRequestAttachment(String name, String sql) { 12 | this.name = name; 13 | this.sql = sql; 14 | } 15 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/repository/SpendRepository.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.repository; 2 | 3 | import guru.qa.niffler.data.entity.spend.CategoryEntity; 4 | import guru.qa.niffler.data.entity.spend.SpendEntity; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public interface SpendRepository { 10 | 11 | static SpendRepository getInstance() { 12 | if ("spring".equals(System.getProperty("repository", null))) { 13 | return new SpendRepositorySpringJdbc(); 14 | } else if ("jdbc".equals(System.getProperty("repository", null))) { 15 | return new SpendRepositoryJdbc(); 16 | } else { 17 | return new SpendRepositoryHibernate(); 18 | } 19 | } 20 | 21 | SpendEntity createSpend(SpendEntity spend); 22 | 23 | Optional findSpendById(UUID id); 24 | 25 | CategoryEntity createCategory(CategoryEntity category); 26 | 27 | Optional findCategoryById(UUID id); 28 | 29 | Optional findUserCategoryByName(String username, String category); 30 | } 31 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/sjdbc/AuthorityEntityRowMapper.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.sjdbc; 2 | 3 | import guru.qa.niffler.data.entity.auth.Authority; 4 | import guru.qa.niffler.data.entity.auth.AuthorityEntity; 5 | import org.springframework.jdbc.core.RowMapper; 6 | 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.util.UUID; 10 | 11 | public class AuthorityEntityRowMapper implements RowMapper { 12 | 13 | public static final AuthorityEntityRowMapper instance = new AuthorityEntityRowMapper(); 14 | 15 | @Override 16 | public AuthorityEntity mapRow(ResultSet rs, int rowNum) throws SQLException { 17 | AuthorityEntity user = new AuthorityEntity(); 18 | user.setId(rs.getObject("id", UUID.class)); 19 | user.setAuthority(Authority.valueOf(rs.getString("authority"))); 20 | return user; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/data/sjdbc/CategoryEntityRowMapper.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.data.sjdbc; 2 | 3 | import guru.qa.niffler.data.entity.spend.CategoryEntity; 4 | import org.springframework.jdbc.core.RowMapper; 5 | 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.util.UUID; 9 | 10 | public class CategoryEntityRowMapper implements RowMapper { 11 | 12 | public static final CategoryEntityRowMapper instance = new CategoryEntityRowMapper(); 13 | 14 | @Override 15 | public CategoryEntity mapRow(ResultSet rs, int rowNum) throws SQLException { 16 | CategoryEntity category = new CategoryEntity(); 17 | category.setId(rs.getObject("id", UUID.class)); 18 | category.setUsername(rs.getString("username")); 19 | category.setName(rs.getString("name")); 20 | category.setArchived(rs.getBoolean("archived")); 21 | return category; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/AllureIdParam.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | import guru.qa.niffler.jupiter.converter.AllureIdConverter; 4 | import org.junit.jupiter.params.converter.ConvertWith; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target(ElementType.PARAMETER) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @ConvertWith(AllureIdConverter.class) 14 | public @interface AllureIdParam { 15 | } 16 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/ApiLogin.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 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 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.METHOD, ElementType.TYPE}) 10 | public @interface ApiLogin { 11 | 12 | String username() default ""; 13 | 14 | String password() default ""; 15 | 16 | GenerateUser user() default @GenerateUser(handleAnnotation = false); 17 | } 18 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/DisabledByIssue.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | import guru.qa.niffler.jupiter.extension.IssueExtension; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target({ElementType.TYPE, ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @ExtendWith(IssueExtension.class) 14 | public @interface DisabledByIssue { 15 | String value(); 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Friends.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.FIELD, ElementType.METHOD}) 11 | public @interface Friends { 12 | 13 | boolean handleAnnotation() default true; 14 | 15 | int count() default 0; 16 | 17 | GenerateCategory[] categories() default {}; 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/GenerateCategory.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.FIELD}) 11 | public @interface GenerateCategory { 12 | 13 | boolean handleAnnotation() default true; 14 | 15 | String name(); 16 | 17 | boolean archived() default false; 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/GenerateSpend.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import guru.qa.niffler.model.rest.CurrencyValues; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.FIELD}) 13 | public @interface GenerateSpend { 14 | 15 | boolean handleAnnotation() default true; 16 | 17 | String name(); 18 | 19 | String category(); 20 | 21 | int addDaysToSpendDate() default 0; 22 | 23 | double amount(); 24 | 25 | CurrencyValues currency() default CurrencyValues.RUB; 26 | } 27 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/GenerateUser.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.FIELD, ElementType.METHOD}) 11 | public @interface GenerateUser { 12 | 13 | boolean handleAnnotation() default true; 14 | 15 | String username() default ""; 16 | 17 | String password() default ""; 18 | 19 | GenerateCategory[] categories() default {}; 20 | 21 | GenerateSpend[] spends() default {}; 22 | 23 | Friends friends() default @Friends(handleAnnotation = false); 24 | 25 | IncomeInvitations incomeInvitations() default @IncomeInvitations(handleAnnotation = false); 26 | 27 | OutcomeInvitations outcomeInvitations() default @OutcomeInvitations(handleAnnotation = false); 28 | } 29 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/GenerateUsers.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.FIELD, ElementType.METHOD}) 11 | public @interface GenerateUsers { 12 | 13 | GenerateUser[] value(); 14 | } 15 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/IncomeInvitations.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.FIELD, ElementType.METHOD}) 11 | public @interface IncomeInvitations { 12 | 13 | boolean handleAnnotation() default true; 14 | 15 | int count() default 0; 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/OutcomeInvitations.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.FIELD, ElementType.METHOD}) 11 | public @interface OutcomeInvitations { 12 | 13 | boolean handleAnnotation() default true; 14 | 15 | int count() default 0; 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Repository.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 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 | @Target({ElementType.FIELD, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Repository { 11 | } 12 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/ScreenShotTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 2 | 3 | import guru.qa.niffler.jupiter.extension.ScreenShotTestExtension; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.METHOD) 14 | @Test 15 | @ExtendWith(ScreenShotTestExtension.class) 16 | public @interface ScreenShotTest { 17 | String expected(); 18 | 19 | boolean rewriteExpected() default false; 20 | } 21 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/StaticUser.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 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 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.PARAMETER) 11 | public @interface StaticUser { 12 | 13 | Type value() default Type.COMMON; 14 | 15 | enum Type { 16 | ADMIN, COMMON 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Token.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 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 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.PARAMETER) 10 | public @interface Token { 11 | boolean bearer() default true; 12 | } 13 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/User.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation; 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 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.PARAMETER) 11 | public @interface User { 12 | 13 | Selector selector() default Selector.NESTED; 14 | 15 | enum Selector { 16 | METHOD, NESTED 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/DBTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import guru.qa.niffler.jupiter.extension.UserRepositoryResolver; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | @ExtendWith(UserRepositoryResolver.class) 14 | public @interface DBTest { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/GqlTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import guru.qa.niffler.jupiter.extension.DatabaseCreateUserExtension; 4 | import io.qameta.allure.junit5.AllureJunit5; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.TYPE) 14 | @ExtendWith({ 15 | DatabaseCreateUserExtension.class, 16 | AllureJunit5.class 17 | }) 18 | public @interface GqlTest { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/GrpcTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import io.qameta.allure.junit5.AllureJunit5; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | @ExtendWith({ 14 | AllureJunit5.class 15 | }) 16 | public @interface GrpcTest { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/KafkaTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import guru.qa.niffler.jupiter.extension.DatabaseCreateUserExtension; 4 | import guru.qa.niffler.jupiter.extension.KafkaExtension; 5 | import io.qameta.allure.junit5.AllureJunit5; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | @ExtendWith({ 16 | KafkaExtension.class, 17 | DatabaseCreateUserExtension.class, 18 | AllureJunit5.class 19 | }) 20 | public @interface KafkaTest { 21 | } 22 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/RestTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import guru.qa.niffler.jupiter.extension.DatabaseCreateUserExtension; 4 | import io.qameta.allure.junit5.AllureJunit5; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.TYPE) 14 | @ExtendWith({ 15 | DatabaseCreateUserExtension.class, 16 | AllureJunit5.class 17 | }) 18 | public @interface RestTest { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/SoapTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import guru.qa.niffler.jupiter.extension.DatabaseCreateUserExtension; 4 | import io.qameta.allure.junit5.AllureJunit5; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.TYPE) 14 | @ExtendWith({ 15 | DatabaseCreateUserExtension.class, 16 | AllureJunit5.class 17 | }) 18 | public @interface SoapTest { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/meta/WebTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.annotation.meta; 2 | 3 | import guru.qa.niffler.jupiter.extension.ApiLoginExtension; 4 | import guru.qa.niffler.jupiter.extension.BrowserExtension; 5 | import guru.qa.niffler.jupiter.extension.DatabaseCreateUserExtension; 6 | import io.qameta.allure.junit5.AllureJunit5; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | 9 | import java.lang.annotation.ElementType; 10 | import java.lang.annotation.Retention; 11 | import java.lang.annotation.RetentionPolicy; 12 | import java.lang.annotation.Target; 13 | 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.TYPE) 16 | @ExtendWith({ 17 | BrowserExtension.class, 18 | DatabaseCreateUserExtension.class, 19 | ApiLoginExtension.class, 20 | AllureJunit5.class 21 | }) 22 | public @interface WebTest { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/converter/AllureIdConverter.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.converter; 2 | 3 | import io.qameta.allure.Allure; 4 | import org.junit.jupiter.api.extension.ParameterContext; 5 | import org.junit.jupiter.params.converter.ArgumentConversionException; 6 | import org.junit.jupiter.params.converter.ArgumentConverter; 7 | 8 | public class AllureIdConverter implements ArgumentConverter { 9 | @Override 10 | public Object convert(Object o, ParameterContext parameterContext) throws ArgumentConversionException { 11 | if (o instanceof String allureId) { 12 | Allure.label("AS_ID", allureId); 13 | } 14 | return o; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/CookiesExtension.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.extension; 2 | 3 | import guru.qa.niffler.api.service.ThreadLocalCookieStore; 4 | import org.junit.jupiter.api.extension.AfterEachCallback; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | import javax.annotation.ParametersAreNonnullByDefault; 8 | 9 | @ParametersAreNonnullByDefault 10 | public class CookiesExtension implements AfterEachCallback { 11 | @Override 12 | public void afterEach(ExtensionContext context) throws Exception { 13 | ThreadLocalCookieStore.INSTANCE.removeAll(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/JpaExtension.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.extension; 2 | 3 | import guru.qa.niffler.data.jpa.EmfContext; 4 | import jakarta.persistence.EntityManagerFactory; 5 | 6 | public class JpaExtension implements SuiteExtension { 7 | 8 | @Override 9 | public void afterSuite() { 10 | EmfContext.storedEmf().forEach(EntityManagerFactory::close); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/KafkaExtension.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.extension; 2 | 3 | import guru.qa.niffler.kafka.KafkaConsumer; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | 10 | @ParametersAreNonnullByDefault 11 | public class KafkaExtension implements SuiteExtension { 12 | 13 | private static final KafkaConsumer KAFKA_CONSUMER = new KafkaConsumer(); 14 | private static final ExecutorService es = Executors.newSingleThreadExecutor(); 15 | 16 | @Override 17 | public void beforeSuite(ExtensionContext context) { 18 | es.execute(KAFKA_CONSUMER); 19 | es.shutdown(); 20 | } 21 | 22 | @Override 23 | public void afterSuite() { 24 | KAFKA_CONSUMER.shutdown(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SuiteExtension.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.jupiter.extension; 2 | 3 | import org.junit.jupiter.api.extension.BeforeAllCallback; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | 8 | @ParametersAreNonnullByDefault 9 | public interface SuiteExtension extends BeforeAllCallback { 10 | 11 | default void beforeSuite(ExtensionContext context) { 12 | } 13 | 14 | default void afterSuite() { 15 | } 16 | 17 | @Override 18 | default void beforeAll(ExtensionContext context) { 19 | context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL). 20 | getOrComputeIfAbsent(this.getClass(), 21 | k -> { 22 | beforeSuite(context); 23 | return (ExtensionContext.Store.CloseableResource) this::afterSuite; 24 | } 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/allure/AllureProject.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.allure; 2 | 3 | public record AllureProject(String id) { 4 | } 5 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/allure/AllureResults.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.allure; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public record AllureResults(@JsonProperty("results") List results) { 8 | } 9 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/allure/DecodedAllureFile.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.allure; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public record DecodedAllureFile(@JsonProperty("file_name") String fileName, 6 | @JsonProperty("content_base64") String contentBase64) { 7 | } 8 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/allure/ScreenDif.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.allure; 2 | 3 | public record ScreenDif(String expected, String actual, String diff) { 4 | } 5 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/queue/UserModel.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.queue; 2 | 3 | public record UserModel(String username, String password) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/CategoryJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.UUID; 6 | 7 | public record CategoryJson( 8 | @JsonProperty("id") 9 | UUID id, 10 | @JsonProperty("name") 11 | String name, 12 | @JsonProperty("username") 13 | String username, 14 | @JsonProperty("archived") 15 | boolean archived) { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/CurrencyJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public record CurrencyJson( 6 | @JsonProperty("currency") 7 | CurrencyValues currency, 8 | @JsonProperty("currencyRate") 9 | Double currencyRate) { 10 | } 11 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/CurrencyValues.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | 5 | import javax.annotation.Nonnull; 6 | 7 | @RequiredArgsConstructor 8 | public enum CurrencyValues { 9 | RUB("₽"), USD("$"), EUR("€"), KZT("₸"); 10 | public final String symbol; 11 | 12 | public static @Nonnull CurrencyValues fromSymbol(@Nonnull String symbol) { 13 | for (CurrencyValues value : CurrencyValues.values()) { 14 | if (value.symbol.equals(symbol)) { 15 | return value; 16 | } 17 | } 18 | throw new IllegalArgumentException("Can`t find CurrencyValues by given symbol: " + symbol); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/DataFilterValues.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | 5 | @RequiredArgsConstructor 6 | public enum DataFilterValues { 7 | TODAY("Today"), WEEK("last week"), MONTH("Last month"); 8 | public final String text; 9 | } 10 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/FriendJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public record FriendJson( 6 | @JsonProperty("username") 7 | String username) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/FriendshipStatus.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | public enum FriendshipStatus { 4 | INVITE_SENT, INVITE_RECEIVED, FRIEND 5 | } 6 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/SessionJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Date; 6 | 7 | public record SessionJson(@JsonProperty("username") 8 | String username, 9 | @JsonProperty("issuedAt") 10 | Date issuedAt, 11 | @JsonProperty("expiresAt") 12 | Date expiresAt) { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/StatisticByCategoryJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public record StatisticByCategoryJson( 8 | @JsonProperty("category") 9 | String category, 10 | @JsonProperty("total") 11 | Double total, 12 | @JsonProperty("totalInUserDefaultCurrency") 13 | Double totalInUserDefaultCurrency, 14 | @JsonProperty("spends") 15 | List spends) { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/StatisticJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | public record StatisticJson( 9 | @JsonProperty("dateFrom") 10 | Date dateFrom, 11 | @JsonProperty("dateTo") 12 | Date dateTo, 13 | @JsonProperty("currency") 14 | CurrencyValues currency, 15 | @JsonProperty("total") 16 | Double total, 17 | @JsonProperty("userDefaultCurrency") 18 | CurrencyValues userDefaultCurrency, 19 | @JsonProperty("totalInUserDefaultCurrency") 20 | Double totalInUserDefaultCurrency, 21 | @JsonProperty("categoryStatistics") 22 | List categoryStatistics) { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/StatisticV2Json.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public record StatisticV2Json( 8 | @JsonProperty("total") 9 | Double total, 10 | @JsonProperty("currency") 11 | CurrencyValues currency, 12 | @JsonProperty("statByCategories") 13 | List statByCategories) { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/model/rest/SumByCategory.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Date; 6 | 7 | public record SumByCategory(@JsonProperty("categoryName") 8 | String categoryName, 9 | @JsonProperty("currency") 10 | CurrencyValues currency, 11 | @JsonProperty("sum") 12 | double sum, 13 | @JsonProperty("firstSpendDate") 14 | Date firstSpendDate, 15 | @JsonProperty("lastSpendDate") 16 | Date lastSpendDate) { 17 | } 18 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/component/BaseComponent.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.page.component; 2 | 3 | import com.codeborne.selenide.SelenideElement; 4 | 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.ParametersAreNonnullByDefault; 7 | 8 | @ParametersAreNonnullByDefault 9 | public abstract class BaseComponent> { 10 | 11 | protected final SelenideElement self; 12 | 13 | public BaseComponent(SelenideElement self) { 14 | this.self = self; 15 | } 16 | 17 | @Nonnull 18 | public SelenideElement getSelf() { 19 | return self; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/component/SelectField.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.page.component; 2 | 3 | import com.codeborne.selenide.SelenideElement; 4 | 5 | import javax.annotation.ParametersAreNonnullByDefault; 6 | 7 | import static com.codeborne.selenide.Condition.text; 8 | import static com.codeborne.selenide.Selenide.$$; 9 | 10 | @ParametersAreNonnullByDefault 11 | public class SelectField extends BaseComponent { 12 | 13 | public SelectField(SelenideElement self) { 14 | super(self); 15 | } 16 | 17 | private final SelenideElement input = self.$("input"); 18 | 19 | public void setValue(String value) { 20 | self.click(); 21 | $$("li[role='option']").find(text(value)).click(); 22 | } 23 | 24 | public void checkSelectValueIsEqualTo(String value) { 25 | self.shouldHave(text(value)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/kafka/BaseKafkaTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.test.kafka; 2 | 3 | import guru.qa.niffler.jupiter.annotation.meta.KafkaTest; 4 | 5 | @KafkaTest 6 | public abstract class BaseKafkaTest { 7 | } 8 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/rest/BaseRestTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.test.rest; 2 | 3 | import guru.qa.niffler.api.GatewayApiClient; 4 | import guru.qa.niffler.jupiter.annotation.meta.RestTest; 5 | import guru.qa.niffler.jupiter.extension.ApiLoginExtension; 6 | import org.junit.jupiter.api.extension.RegisterExtension; 7 | 8 | @RestTest 9 | public abstract class BaseRestTest { 10 | 11 | protected static final String ID_REGEXP = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; 12 | protected static final GatewayApiClient gatewayApiClient = new GatewayApiClient(); 13 | 14 | @RegisterExtension 15 | protected static final ApiLoginExtension apiLoginExtension = ApiLoginExtension.rest(); 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/soap/BaseSoapTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.test.soap; 2 | 3 | import guru.qa.niffler.api.UserdataWsClient; 4 | import guru.qa.niffler.jupiter.annotation.meta.SoapTest; 5 | 6 | @SoapTest 7 | public abstract class BaseSoapTest { 8 | 9 | protected static final String ID_REGEXP = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; 10 | protected static final UserdataWsClient wsClient = new UserdataWsClient(); 11 | } 12 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/BaseWebTest.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.test.web; 2 | 3 | import com.codeborne.selenide.logevents.SelenideLogger; 4 | import guru.qa.niffler.config.Config; 5 | import guru.qa.niffler.jupiter.annotation.meta.DBTest; 6 | import guru.qa.niffler.jupiter.annotation.meta.WebTest; 7 | import io.qameta.allure.selenide.AllureSelenide; 8 | import org.junit.jupiter.api.BeforeEach; 9 | 10 | @WebTest 11 | @DBTest 12 | public abstract class BaseWebTest { 13 | 14 | protected static final Config CFG = Config.getInstance(); 15 | 16 | @BeforeEach 17 | void setup() { 18 | SelenideLogger.addListener("AllureSelenide", new AllureSelenide() 19 | .screenshots(false) 20 | .savePageSource(false) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/utils/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.utils; 2 | 3 | public enum ErrorMessage { 4 | BAD_CREDENTIALS("Bad credentials"), 5 | CAN_NOT_ADD_CATEGORY("Can not add new category"), 6 | PASSWORDS_SHOULD_BE_EQUAL("Passwords should be equal"), 7 | USERNAME_SHOULD_NOT_CONTAINS_WHITESPACES("Username must not contain whitespace"), 8 | PASSWORD_SHOULD_NOT_CONTAINS_WHITESPACES("Password must not contain whitespace"), 9 | CAN_NOT_PICK_FUTURE_DATE_FOR_SPENDING("You can not pick future date"), 10 | CAN_NOT_CREATE_SPENDING_WITHOUT_CATEGORY("Please choose category"), 11 | CAN_NOT_CREATE_SPENDING_WITHOUT_AMOUNT("Amount has to be not less then 0.01"), 12 | CAN_NOT_CREATE_SPENDING_WITH_INCORRECT_DATE("Date must be between 01.01.1970 and today"); 13 | 14 | public final String content; 15 | 16 | ErrorMessage(String content) { 17 | this.content = content; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/utils/SuccessMessage.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.utils; 2 | 3 | public enum SuccessMessage { 4 | FRIEND_DELETED("Friend is deleted"), 5 | INVITATION_ACCEPTED("Invitation is accepted"), 6 | INVITATION_DECLINED("Invitation is declined"), 7 | SPENDING_ADDED("New spending is successfully created"), 8 | PROFILE_UPDATED("Profile successfully updated"), 9 | CATEGORY_ADDED("You've added new category:"); 10 | 11 | public final String content; 12 | 13 | SuccessMessage(String content) { 14 | this.content = content; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/java/guru/qa/niffler/utils/UrlUtils.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.utils; 2 | 3 | import javax.annotation.Nullable; 4 | import java.net.URI; 5 | import java.net.URL; 6 | 7 | public class UrlUtils { 8 | 9 | public static boolean isValidURL(@Nullable String urlString) { 10 | try { 11 | URL url = new URI(urlString).toURL(); 12 | url.toURI(); 13 | return true; 14 | } catch (Exception e) { 15 | return false; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | guru.qa.niffler.data.entity.auth.AuthUserEntity 7 | guru.qa.niffler.data.entity.auth.AuthorityEntity 8 | guru.qa.niffler.data.entity.userdata.UserEntity 9 | guru.qa.niffler.data.entity.userdata.FriendshipEntity 10 | guru.qa.niffler.data.entity.userdata.FriendShipId 11 | guru.qa.niffler.data.entity.spend.SpendEntity 12 | guru.qa.niffler.data.entity.spend.CategoryEntity 13 | 14 | 15 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension: -------------------------------------------------------------------------------- 1 | guru.qa.niffler.jupiter.extension.TestMethodContextExtension 2 | guru.qa.niffler.jupiter.extension.CookiesExtension 3 | guru.qa.niffler.jupiter.extension.JpaExtension 4 | guru.qa.niffler.jupiter.extension.AllurePassedAttachmentsExtension 5 | guru.qa.niffler.jupiter.extension.AllureDockerExtension 6 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/img/cat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/img/cat.jpeg -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.execution.parallel.enabled=true 2 | junit.jupiter.execution.parallel.mode.default=concurrent 3 | junit.jupiter.execution.parallel.mode.classes.default=concurrent 4 | junit.jupiter.execution.parallel.config.strategy=fixed 5 | junit.jupiter.execution.parallel.config.fixed.parallelism=3 6 | junit.jupiter.execution.parallel.config.fixed.max-pool-size=3 7 | -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/rest/spend0.json: -------------------------------------------------------------------------------- 1 | { 2 | "spendDate": "2023-01-26", 3 | "category": { 4 | "name": "Рестораны" 5 | }, 6 | "currency": "RUB", 7 | "amount": 300.0, 8 | "description": "Описание", 9 | "username": "" 10 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/rest/spend1.json: -------------------------------------------------------------------------------- 1 | { 2 | "spendDate": "2023-01-26", 3 | "category": { 4 | "name": "Бары" 5 | }, 6 | "currency": "RUB", 7 | "amount": 25.0, 8 | "description": "Описание", 9 | "username": "" 10 | } -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/screenshots/local/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/screenshots/local/cat.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/screenshots/local/expected-stat-archived.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/screenshots/local/expected-stat-archived.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/screenshots/local/expected-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/screenshots/local/expected-stat.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/screenshots/selenoid/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/screenshots/selenoid/cat.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/screenshots/selenoid/expected-stat-archived.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/screenshots/selenoid/expected-stat-archived.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/screenshots/selenoid/expected-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-e-2-e-tests/src/test/resources/screenshots/selenoid/expected-stat.png -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/spy.properties: -------------------------------------------------------------------------------- 1 | driverlist=org.postgresql.Driver 2 | appender=guru.qa.niffler.data.logging.AllureAppender 3 | excludecategories=info,debug,result,resultset -------------------------------------------------------------------------------- /niffler-e-2-e-tests/src/test/resources/xml/currentUserRequest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | pizzly 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/NifflerGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler; 2 | 3 | import guru.qa.niffler.service.PropertiesLogger; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | public class NifflerGatewayApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication springApplication = new SpringApplication(NifflerGatewayApplication.class); 12 | springApplication.addListeners(new PropertiesLogger()); 13 | springApplication.run(args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/controller/SessionController.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.controller; 2 | 3 | 4 | import guru.qa.niffler.model.SessionJson; 5 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 6 | import org.springframework.security.oauth2.jwt.Jwt; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.Date; 12 | 13 | 14 | @RestController 15 | @RequestMapping("/api/session") 16 | public class SessionController { 17 | 18 | @GetMapping("/current") 19 | public SessionJson session(@AuthenticationPrincipal Jwt principal) { 20 | if (principal != null) { 21 | return new SessionJson( 22 | principal.getClaim("sub"), 23 | Date.from(principal.getIssuedAt()), 24 | Date.from(principal.getExpiresAt()) 25 | ); 26 | } else { 27 | return SessionJson.empty(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/controller/graphql/SessionQueryController.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.controller.graphql; 2 | 3 | 4 | import guru.qa.niffler.model.SessionJson; 5 | import org.springframework.graphql.data.method.annotation.QueryMapping; 6 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 7 | import org.springframework.security.oauth2.jwt.Jwt; 8 | import org.springframework.stereotype.Controller; 9 | 10 | import java.util.Date; 11 | 12 | 13 | @Controller 14 | public class SessionQueryController { 15 | 16 | @QueryMapping 17 | public SessionJson session(@AuthenticationPrincipal Jwt principal) { 18 | if (principal != null) { 19 | return new SessionJson( 20 | principal.getClaim("sub"), 21 | Date.from(principal.getIssuedAt()), 22 | Date.from(principal.getExpiresAt()) 23 | ); 24 | } else { 25 | return SessionJson.empty(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/ex/IllegalGqlFieldAccessException.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.ex; 2 | 3 | public class IllegalGqlFieldAccessException extends RuntimeException { 4 | public IllegalGqlFieldAccessException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/ex/InvalidUserJsonException.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.ex; 2 | 3 | public class InvalidUserJsonException extends RuntimeException { 4 | 5 | public InvalidUserJsonException() { 6 | } 7 | 8 | public InvalidUserJsonException(String message) { 9 | super(message); 10 | } 11 | 12 | public InvalidUserJsonException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public InvalidUserJsonException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | public InvalidUserJsonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/ex/NoRestResponseException.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.ex; 2 | 3 | public class NoRestResponseException extends RuntimeException { 4 | public NoRestResponseException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/ex/NoSoapResponseException.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.ex; 2 | 3 | public class NoSoapResponseException extends RuntimeException { 4 | public NoSoapResponseException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/ex/TooManySubQueriesException.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.ex; 2 | 3 | public class TooManySubQueriesException extends RuntimeException { 4 | public TooManySubQueriesException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/CurrencyJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import guru.qa.niffler.grpc.Currency; 5 | import jakarta.annotation.Nonnull; 6 | 7 | public record CurrencyJson( 8 | @JsonProperty("currency") 9 | CurrencyValues currency, 10 | @JsonProperty("currencyRate") 11 | Double currencyRate) { 12 | 13 | 14 | public static @Nonnull CurrencyJson fromGrpcMessage(@Nonnull Currency currencyMessage) { 15 | return new CurrencyJson( 16 | CurrencyValues.valueOf(currencyMessage.getCurrency().name()), 17 | currencyMessage.getCurrencyRate() 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/CurrencyValues.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | public enum CurrencyValues { 4 | RUB, USD, EUR, KZT 5 | } 6 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/DataFilterValues.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | public enum DataFilterValues { 4 | TODAY, WEEK, MONTH 5 | } 6 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/ErrorJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import jakarta.annotation.Nonnull; 4 | 5 | public record ErrorJson(@Nonnull String type, 6 | @Nonnull String title, 7 | int status, 8 | @Nonnull String detail, 9 | @Nonnull String instance) { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/FriendJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | 7 | public record FriendJson( 8 | @NotBlank(message = "Username can not be blank") 9 | @Size(min = 3, max = 50, message = "Allowed username length should be from 3 to 50 characters") 10 | @JsonProperty("username") 11 | String username) { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/FriendshipStatus.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | public enum FriendshipStatus { 4 | INVITE_SENT, INVITE_RECEIVED, FRIEND 5 | } 6 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/SessionJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import jakarta.annotation.Nonnull; 5 | 6 | import java.util.Date; 7 | 8 | public record SessionJson(@JsonProperty("username") 9 | String username, 10 | @JsonProperty("issuedAt") 11 | Date issuedAt, 12 | @JsonProperty("expiresAt") 13 | Date expiresAt) { 14 | public static @Nonnull SessionJson empty() { 15 | return new SessionJson(null, null, null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/StatisticByCategoryJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public record StatisticByCategoryJson( 8 | @JsonProperty("category") 9 | String category, 10 | @JsonProperty("total") 11 | Double total, 12 | @JsonProperty("totalInUserDefaultCurrency") 13 | Double totalInUserDefaultCurrency, 14 | @JsonProperty("spends") 15 | List spends) { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/StatisticJson.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | public record StatisticJson( 9 | @JsonProperty("dateFrom") 10 | Date dateFrom, 11 | @JsonProperty("dateTo") 12 | Date dateTo, 13 | @JsonProperty("currency") 14 | CurrencyValues currency, 15 | @JsonProperty("total") 16 | Double total, 17 | @JsonProperty("userDefaultCurrency") 18 | CurrencyValues userDefaultCurrency, 19 | @JsonProperty("totalInUserDefaultCurrency") 20 | Double totalInUserDefaultCurrency, 21 | @JsonProperty("categoryStatistics") 22 | List categoryStatistics) { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/StatisticV2Json.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | public record StatisticV2Json( 8 | @JsonProperty("total") 9 | Double total, 10 | @JsonProperty("currency") 11 | CurrencyValues currency, 12 | @JsonProperty("statByCategories") 13 | List statByCategories) { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/SumByCategory.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Date; 6 | 7 | public record SumByCategory(@JsonProperty("categoryName") 8 | String categoryName, 9 | @JsonProperty("currency") 10 | CurrencyValues currency, 11 | @JsonProperty("sum") 12 | double sum, 13 | @JsonProperty("firstSpendDate") 14 | Date firstSpendDate, 15 | @JsonProperty("lastSpendDate") 16 | Date lastSpendDate) { 17 | } 18 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/gql/CategoryGqlInput.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.gql; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import jakarta.validation.constraints.Size; 5 | 6 | import java.util.UUID; 7 | 8 | public record CategoryGqlInput( 9 | UUID id, 10 | @NotBlank(message = "Category can not be blank") 11 | @Size(min = 2, max = 50, message = "Allowed category length should be from 2 to 50 characters") 12 | String name, 13 | boolean archived 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/gql/FriendshipAction.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.gql; 2 | 3 | public enum FriendshipAction { 4 | ADD, ACCEPT, REJECT, DELETE 5 | } 6 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/gql/FriendshipGqlInput.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.gql; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import jakarta.validation.constraints.Size; 5 | 6 | public record FriendshipGqlInput( 7 | @NotBlank(message = "Username can not be blank") 8 | @Size(min = 3, max = 50, message = "Allowed username length should be from 3 to 50 characters") 9 | String username, 10 | FriendshipAction action) { 11 | } 12 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/gql/SpendGqlInput.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.gql; 2 | 3 | import guru.qa.niffler.model.CurrencyValues; 4 | import guru.qa.niffler.validation.UnixEpochOrLater; 5 | import jakarta.validation.Valid; 6 | import jakarta.validation.constraints.DecimalMin; 7 | import jakarta.validation.constraints.NotNull; 8 | 9 | import java.util.Date; 10 | import java.util.UUID; 11 | 12 | public record SpendGqlInput( 13 | UUID id, 14 | @NotNull(message = "Spend date can not be null") 15 | @UnixEpochOrLater(message = "Spend date must not be future or less than 01.01.1970") 16 | Date spendDate, 17 | @NotNull(message = "Category can not be null") 18 | @Valid CategoryGqlInput category, 19 | @NotNull(message = "Currency can not be null") 20 | CurrencyValues currency, 21 | @NotNull(message = "Amount can not be null") 22 | @DecimalMin(value = "0.01", message = "Amount should be greater than 0.01") 23 | Double amount, 24 | String description) { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/model/gql/UserGqlInput.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.model.gql; 2 | 3 | import guru.qa.niffler.config.NifflerGatewayServiceConfig; 4 | import guru.qa.niffler.validation.IsPhotoString; 5 | import jakarta.validation.constraints.Size; 6 | 7 | public record UserGqlInput( 8 | @Size(max = 100, message = "Fullname can`t be longer than 100 characters") 9 | String fullname, 10 | @Size(max = NifflerGatewayServiceConfig.ONE_MB) 11 | @IsPhotoString 12 | String photo 13 | ) { 14 | } 15 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/validation/IsPhotoString.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Constraint(validatedBy = PhotoStringValidator.class) 12 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface IsPhotoString { 15 | String message() default "Photo must be a base64 string in allowed formats: 'jpeg', 'jpg', 'png', 'bmp', 'wbmp', 'gif'"; 16 | 17 | Class[] groups() default {}; 18 | 19 | Class[] payload() default {}; 20 | } 21 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/validation/IsUuidString.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import jakarta.validation.constraints.Pattern; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 13 | @Constraint(validatedBy = {}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Pattern( 16 | regexp = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", 17 | flags = {Pattern.Flag.CASE_INSENSITIVE} 18 | ) 19 | public @interface IsUuidString { 20 | String message() default "ID must be in UUID format"; 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | } 26 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/validation/UnixEpochOrLater.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Constraint(validatedBy = UnixEpochOrLaterValidator.class) 12 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface UnixEpochOrLater { 15 | String message() default "Date must be between 1970-01-01 and today"; 16 | 17 | Class[] groups() default {}; 18 | 19 | Class[] payload() default {}; 20 | } 21 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/java/guru/qa/niffler/validation/UnixEpochOrLaterValidator.java: -------------------------------------------------------------------------------- 1 | package guru.qa.niffler.validation; 2 | 3 | import jakarta.validation.ConstraintValidator; 4 | import jakarta.validation.ConstraintValidatorContext; 5 | 6 | import java.util.Date; 7 | 8 | public class UnixEpochOrLaterValidator implements ConstraintValidator { 9 | private static final long UNIX_EPOCH_TIME = 0L; 10 | 11 | @Override 12 | public boolean isValid(Date date, ConstraintValidatorContext context) { 13 | if (date == null) { 14 | return false; 15 | } 16 | long time = date.getTime(); 17 | long now = System.currentTimeMillis(); 18 | return time >= UNIX_EPOCH_TIME && time <= now; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /niffler-gateway/src/main/resources/application-test.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | autoconfigure: 3 | exclude: 4 | - 'org.springframework.cloud.vault.config.VaultAutoConfiguration' 5 | - 'org.springframework.cloud.vault.config.VaultObservationAutoConfiguration' 6 | - 'org.springframework.cloud.vault.config.VaultReactiveAutoConfiguration' 7 | 8 | niffler-currency: 9 | base-uri: 'http://127.0.0.1:8091' 10 | niffler-userdata: 11 | base-uri: 'http://127.0.0.1:8089' 12 | niffler-spend: 13 | base-uri: 'http://127.0.0.1:8093' 14 | niffler-front: 15 | base-uri: 'http://127.0.0.1:3000' 16 | niffler-gateway: 17 | base-uri: 'http://127.0.0.1:8090' 18 | 19 | grpc: 20 | client: 21 | grpcCurrencyClient: 22 | address: 'static://localhost:8092' 23 | negotiationType: PLAINTEXT -------------------------------------------------------------------------------- /niffler-gateway/src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-gateway/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /niffler-grpc-common/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'idea' 3 | id 'com.google.protobuf' version '0.9.5' 4 | } 5 | 6 | group = 'guru.qa' 7 | version = '1.0.3' 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | implementation "io.grpc:grpc-protobuf:${project.ext.grpcVersion}" 15 | implementation "io.grpc:grpc-stub:${project.ext.grpcVersion}" 16 | implementation "com.google.protobuf:protobuf-java:${project.ext.protobufVersion}" 17 | compileOnly 'jakarta.annotation:jakarta.annotation-api:1.3.5' // Java 9+ compatibility - Do NOT update to 2.0.0 18 | } 19 | 20 | protobuf { 21 | protoc { 22 | artifact = "com.google.protobuf:protoc:${project.ext.protobufVersion}" 23 | } 24 | clean { 25 | delete generatedFilesBaseDir 26 | } 27 | plugins { 28 | grpc { 29 | artifact = "io.grpc:protoc-gen-grpc-java:${project.ext.grpcVersion}" 30 | } 31 | } 32 | generateProtoTasks { 33 | all()*.plugins { 34 | grpc {} 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /niffler-grpc-common/src/main/proto/niffler-currency.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/empty.proto"; 4 | 5 | package guru.qa.grpc.niffler; 6 | 7 | option java_multiple_files = true; 8 | option java_package = "guru.qa.niffler.grpc"; 9 | option java_outer_classname = "NifflerCurrencyProto"; 10 | 11 | service NifflerCurrencyService { 12 | rpc GetAllCurrencies (google.protobuf.Empty) returns (CurrencyResponse) {} 13 | rpc CalculateRate (CalculateRequest) returns (CalculateResponse) {} 14 | } 15 | 16 | message CurrencyResponse { 17 | repeated Currency allCurrencies = 1; 18 | } 19 | 20 | message Currency { 21 | CurrencyValues currency = 1; 22 | double currencyRate = 2; 23 | } 24 | 25 | message CalculateRequest { 26 | CurrencyValues spendCurrency = 1; 27 | CurrencyValues desiredCurrency = 2; 28 | double amount = 3; 29 | } 30 | 31 | message CalculateResponse { 32 | double calculatedAmount = 1; 33 | } 34 | 35 | enum CurrencyValues { 36 | UNSPECIFIED = 0; 37 | RUB = 1; 38 | USD = 2; 39 | EUR = 3; 40 | KZT = 4; 41 | } 42 | -------------------------------------------------------------------------------- /niffler-ng-client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vite 3 | dist 4 | -------------------------------------------------------------------------------- /niffler-ng-client/.env: -------------------------------------------------------------------------------- 1 | VITE_AUTH_URL=http://127.0.0.1:9000 2 | VITE_API_URL=http://127.0.0.1:8090 3 | VITE_FRONT_HOST=127.0.0.1 4 | VITE_FRONT_URL=http://127.0.0.1:3000 5 | VITE_CLIENT_ID=client 6 | -------------------------------------------------------------------------------- /niffler-ng-client/.env.docker: -------------------------------------------------------------------------------- 1 | VITE_AUTH_URL=http://auth.niffler.dc:9000 2 | VITE_API_URL=http://gateway.niffler.dc:8090 3 | VITE_FRONT_HOST=frontend.niffler.dc 4 | VITE_FRONT_URL=http://frontend.niffler.dc 5 | VITE_CLIENT_ID=client 6 | -------------------------------------------------------------------------------- /niffler-ng-client/.env.prod: -------------------------------------------------------------------------------- 1 | VITE_AUTH_URL=https://auth.niffler.qa.guru 2 | VITE_API_URL=https://api.niffler.qa.guru 3 | VITE_FRONT_HOST=niffler.qa.guru 4 | VITE_FRONT_URL=https://niffler.qa.guru 5 | VITE_CLIENT_ID=client 6 | -------------------------------------------------------------------------------- /niffler-ng-client/.env.staging: -------------------------------------------------------------------------------- 1 | VITE_AUTH_URL=https://auth.niffler-stage.qa.guru 2 | VITE_API_URL=https://api.niffler-stage.qa.guru 3 | VITE_FRONT_HOST=niffler-stage.qa.guru 4 | VITE_FRONT_URL=https://niffler-stage.qa.guru 5 | VITE_CLIENT_ID=client 6 | -------------------------------------------------------------------------------- /niffler-ng-client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: {browser: true, es2020: true}, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | {allowConstantExport: true}, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /niffler-ng-client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | .vite 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /niffler-ng-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.6.0-alpine AS build 2 | ARG NPM_COMMAND 3 | ARG VERSION 4 | WORKDIR /app 5 | COPY package.json ./ 6 | RUN npm install 7 | COPY . ./ 8 | RUN npm run ${NPM_COMMAND} 9 | 10 | # release step 11 | FROM nginx:1.27.1-alpine AS release 12 | LABEL maintainer="Irina Stiazhkina @irin_alexis" 13 | LABEL version=${VERSION} 14 | ENV TZ=Europe/Moscow 15 | COPY nginx.conf /etc/nginx/conf.d/default.conf 16 | COPY --from=build /app/dist /usr/share/nginx/html/ 17 | EXPOSE 80 18 | CMD ["nginx", "-g", "daemon off;"] 19 | -------------------------------------------------------------------------------- /niffler-ng-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Niffler 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /niffler-ng-client/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri /index.html; 9 | } 10 | 11 | error_page 500 502 503 504 /50x.html; 12 | location = /50x.html { 13 | root /usr/share/nginx/html; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /niffler-ng-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/public/favicon.ico -------------------------------------------------------------------------------- /niffler-ng-client/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/App.css -------------------------------------------------------------------------------- /niffler-ng-client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import {AppContent} from "./components/AppContent"; 3 | import {SnackBarProvider} from "./context/SnackBarContext"; 4 | import {DialogProvider} from "./context/DialogContext.tsx"; 5 | 6 | 7 | const App = () => { 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /niffler-ng-client/src/api/url/auth.ts: -------------------------------------------------------------------------------- 1 | const AUTH_URL = `${import.meta.env.VITE_AUTH_URL}`; 2 | const FRONT_URL = `${import.meta.env.VITE_FRONT_URL}`; 3 | const CLIENT_ID = `${import.meta.env.VITE_CLIENT_ID}`; 4 | 5 | const authorizeUrl = (codeChallenge: string) => { 6 | return `${AUTH_URL}/oauth2/authorize?response_type=code&client_id=${CLIENT_ID}&scope=openid&redirect_uri=${FRONT_URL}/authorized&code_challenge=${codeChallenge}&code_challenge_method=S256`; 7 | } 8 | 9 | const tokenUrl = () => { 10 | return `${AUTH_URL}/oauth2/token`; 11 | } 12 | 13 | const logoutUrl = (token: string) => { 14 | return `${AUTH_URL}/connect/logout?id_token_hint=${token}&post_logout_redirect_uri=${FRONT_URL}/logout`; 15 | } 16 | 17 | const revokeAccessTokenUrl = () => { 18 | return `${AUTH_URL}/oauth2/revoke`; 19 | } 20 | 21 | export { 22 | authorizeUrl, 23 | logoutUrl, 24 | tokenUrl, 25 | revokeAccessTokenUrl, 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/fonts/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/assets/fonts/Inter-Regular.woff2 -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/fonts/YoungSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/assets/fonts/YoungSerif-Regular.ttf -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/fonts/YoungSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/assets/fonts/YoungSerif-Regular.woff -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/fonts/YoungSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/assets/fonts/YoungSerif-Regular.woff2 -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_add_friend.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_archive.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_cal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_check.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_friends.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_signout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/icons/ic_user.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/images/niffler-with-a-coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/assets/images/niffler-with-a-coin.png -------------------------------------------------------------------------------- /niffler-ng-client/src/assets/images/niffler-with-a-coin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qa-guru/niffler/e45eea987a05efa1b62776d10b2108e2d3b25497/niffler-ng-client/src/assets/images/niffler-with-a-coin2.png -------------------------------------------------------------------------------- /niffler-ng-client/src/components/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import MuiButton, {ButtonProps} from '@mui/material/Button'; 2 | import {styled} from '@mui/material/styles'; 3 | import {FC} from "react"; 4 | 5 | const Button = styled(MuiButton)(() => ({ 6 | padding: "12px 24px", 7 | borderRadius: "8px", 8 | textTransform: "none", 9 | weight: 600, 10 | fontSize: 16, 11 | lineHeight: 1.3, 12 | })); 13 | 14 | export const PrimaryButton: FC = (props) => { 15 | return