├── .gitattributes ├── .gitignore ├── README.md ├── config-server ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── rshtishi │ │ │ └── configserver │ │ │ └── ConfigurationServerApplication.java │ └── resources │ │ ├── application.properties │ │ ├── bootstrap.properties │ │ ├── keystore │ │ └── server.jks │ │ └── static │ │ └── images │ │ └── configuration-server-architecture.png │ └── test │ └── java │ └── com │ └── github │ └── rshtishi │ └── configserver │ └── ConfigurationServerApplicationTests.java ├── department ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── rshtishi │ │ │ └── department │ │ │ ├── DepartmentApplication.java │ │ │ ├── UserContext.java │ │ │ ├── UserContextHolder.java │ │ │ ├── configuration │ │ │ ├── I18NConfiguration.java │ │ │ ├── MvcConfiguration.java │ │ │ ├── RedisConfigProperties.java │ │ │ ├── RedisConfiguration.java │ │ │ └── SwaggerConfiguration.java │ │ │ ├── controller │ │ │ ├── DepartmentRestController.java │ │ │ └── advice │ │ │ │ └── RestExceptionHandler.java │ │ │ ├── dto │ │ │ ├── EmployeeCountChangeModel.java │ │ │ ├── ErrorDetail.java │ │ │ └── ValidationError.java │ │ │ ├── entity │ │ │ ├── Department.java │ │ │ └── enums │ │ │ │ └── EmployeeActionEnum.java │ │ │ ├── exception │ │ │ └── ResourceNotFoundException.java │ │ │ ├── filter │ │ │ └── UserContextFilter.java │ │ │ ├── helper │ │ │ ├── DepartmentHelper.java │ │ │ └── Translator.java │ │ │ ├── hystrix │ │ │ ├── DelegatingUserContextCallable.java │ │ │ ├── ThreadLocalAwareStrategy.java │ │ │ └── ThreadLocalConfiguration.java │ │ │ ├── interceptor │ │ │ └── UserContextInterceptor.java │ │ │ ├── repository │ │ │ ├── DepartmentRepository.java │ │ │ ├── EmployeeCountRedisRepository.java │ │ │ └── EmployeeCountRedisrepositoryImpl.java │ │ │ ├── security │ │ │ ├── JWTTokenEnhancer.java │ │ │ ├── JWTTokenStoreConfig.java │ │ │ ├── ResourceServerConfig.java │ │ │ └── SecurityProperties.java │ │ │ ├── service │ │ │ ├── DepartmentService.java │ │ │ ├── DepartmentServiceImpl.java │ │ │ ├── EmployeeCountRedisService.java │ │ │ └── EmployeeCountRedisServiceImpl.java │ │ │ ├── sink │ │ │ └── EmployeeCountSink.java │ │ │ └── thirdparty │ │ │ └── EmployeeRestTemplate.java │ └── resources │ │ ├── bootstrap.properties │ │ ├── db │ │ ├── changelog │ │ │ ├── 01-create-department-scheme.xml │ │ │ ├── 02-data-insert-departments.xml │ │ │ └── 03-alter-departments.xml │ │ └── liquibase-changelog.xml │ │ ├── logback.xml │ │ ├── messages.properties │ │ ├── messages_al.properties │ │ ├── public.txt │ │ └── static │ │ ├── images │ │ └── department-service-architecture.jpeg │ │ └── swagger-ui │ │ ├── css │ │ ├── reset.css │ │ └── screen.css │ │ ├── images │ │ ├── explorer_icons.png │ │ ├── logo_small.png │ │ ├── pet_store_api.png │ │ ├── throbber.gif │ │ └── wordnik_api.png │ │ ├── index.html │ │ ├── o2c.html │ │ ├── swagger-ui.js │ │ └── swagger-ui.min.js │ └── test │ ├── java │ └── com │ │ └── github │ │ └── rshtishi │ │ └── department │ │ ├── controller │ │ └── DepartmentRestControllerUnitTest.java │ │ ├── service │ │ ├── DepartmentServiceImplUnitTest.java │ │ └── EmployeeCountRedisServiceImplTest.java │ │ └── thirdparty │ │ └── EmployeeRestTemplateTest.java │ └── resources │ └── test.properties ├── employee ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── rshtishi │ │ │ └── payroll │ │ │ └── employee │ │ │ ├── EmployeeApplication.java │ │ │ ├── configuration │ │ │ ├── MvcConfiguration.java │ │ │ └── SwaggerConfiguration.java │ │ │ ├── entity │ │ │ ├── Employee.java │ │ │ ├── EmployeeActionEnum.java │ │ │ ├── EmployeeCountChangeModel.java │ │ │ ├── ErrorDetail.java │ │ │ └── ValidationError.java │ │ │ ├── exception │ │ │ └── ResourceNotFoundException.java │ │ │ ├── helper │ │ │ ├── EmployeeHelper.java │ │ │ └── Translator.java │ │ │ ├── repository │ │ │ └── EmployeeRepository.java │ │ │ ├── rest │ │ │ ├── EmployeeRestController.java │ │ │ └── advice │ │ │ │ └── RestExceptionHandler.java │ │ │ ├── security │ │ │ ├── JWTTokenEnhancer.java │ │ │ ├── JWTTokenStoreConfig.java │ │ │ ├── ResourceServerConfig.java │ │ │ └── SecurityProperties.java │ │ │ ├── service │ │ │ ├── EmployeeService.java │ │ │ └── EmployeeServiceImp.java │ │ │ └── source │ │ │ └── EmployeeSource.java │ └── resources │ │ ├── bootstrap.properties │ │ ├── db │ │ ├── changelog │ │ │ ├── 01-create-employee-scheme.xml │ │ │ ├── 02-data-insert-employees.xml │ │ │ └── 03-alter-employees.xml │ │ └── liquibase-changelog.xml │ │ ├── logback.xml │ │ ├── messages.properties │ │ ├── messages_al.properties │ │ ├── public.txt │ │ └── static │ │ ├── images │ │ └── employee-service-architecture.jpeg │ │ └── swagger-ui │ │ ├── css │ │ ├── reset.css │ │ └── screen.css │ │ ├── images │ │ ├── explorer_icons.png │ │ ├── logo_small.png │ │ ├── pet_store_api.png │ │ ├── throbber.gif │ │ └── wordnik_api.png │ │ ├── index.html │ │ ├── o2c.html │ │ ├── swagger-ui.js │ │ └── swagger-ui.min.js │ └── test │ ├── java │ └── com │ │ └── github │ │ └── rshtishi │ │ └── payroll │ │ └── employee │ │ ├── rest │ │ └── EmployeeRestControllerUnitTest.java │ │ └── service │ │ └── EmployeeServiceImpUnitTest.java │ └── resources │ └── test.properties ├── eureka-server ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── rshtishi │ │ └── eurekaserver │ │ └── EurekaServerApplication.java │ └── resources │ ├── bootstrap.properties │ └── static │ └── images │ └── eureka-server-architecture.png ├── gateway-server ├── pom.xml ├── readme.md └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── rshtishi │ │ └── gatewayserver │ │ ├── GatewayServerApplication.java │ │ └── filter │ │ └── ResponseFilter.java │ └── resources │ ├── bootstrap.properties │ ├── keystore │ ├── gateway.jks │ └── gateway.p12 │ ├── logback.xml │ └── static │ └── images │ └── gateway-server.jpeg ├── oauth2-server ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── rshtishi │ │ └── oauth2server │ │ ├── Oauth2ServerApplication.java │ │ ├── configuration │ │ ├── JWTTokenStoreConfig.java │ │ ├── JwtTokenEnhancer.java │ │ ├── OAuth2Config.java │ │ ├── OAuth2ConfigParameters.java │ │ └── WebSecurityConfigurer.java │ │ └── rest │ │ └── UserRestController.java │ └── resources │ ├── bootstrap.properties │ ├── db │ ├── changelog │ │ ├── 01-create-scheme.xml │ │ └── 02-data-insert.xml │ └── liquibase-changelog.xml │ ├── oauth2cer.jks │ └── static │ └── images │ └── oauth2-server.jpeg ├── pom.xml ├── static └── images │ └── payroll-architecture.jpeg └── webapp ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── pom.xml ├── proxy.conf.json ├── src ├── app │ ├── access-denied │ │ ├── access-denied-routing.module.ts │ │ ├── access-denied.module.ts │ │ └── access-denied │ │ │ ├── access-denied.component.css │ │ │ ├── access-denied.component.html │ │ │ ├── access-denied.component.spec.ts │ │ │ └── access-denied.component.ts │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.settings.ts │ ├── layout │ │ ├── dashboard │ │ │ ├── dashboard-routing.module.ts │ │ │ ├── dashboard.module.ts │ │ │ └── dashboard │ │ │ │ ├── dashboard.component.css │ │ │ │ ├── dashboard.component.html │ │ │ │ ├── dashboard.component.spec.ts │ │ │ │ └── dashboard.component.ts │ │ ├── department │ │ │ ├── department-add │ │ │ │ ├── department-add.component.css │ │ │ │ ├── department-add.component.html │ │ │ │ ├── department-add.component.spec.ts │ │ │ │ └── department-add.component.ts │ │ │ ├── department-edit │ │ │ │ ├── department-edit.component.css │ │ │ │ ├── department-edit.component.html │ │ │ │ ├── department-edit.component.spec.ts │ │ │ │ └── department-edit.component.ts │ │ │ ├── department-routing.module.ts │ │ │ ├── department-view │ │ │ │ ├── department-view.component.css │ │ │ │ ├── department-view.component.html │ │ │ │ ├── department-view.component.spec.ts │ │ │ │ └── department-view.component.ts │ │ │ ├── department.module.ts │ │ │ ├── department │ │ │ │ ├── department.component.css │ │ │ │ ├── department.component.html │ │ │ │ ├── department.component.spec.ts │ │ │ │ └── department.component.ts │ │ │ └── directive │ │ │ │ └── department-binding.directive.ts │ │ ├── employee │ │ │ ├── directive │ │ │ │ └── employee-binding.directive.ts │ │ │ ├── employee-add │ │ │ │ ├── employee-add.component.css │ │ │ │ ├── employee-add.component.html │ │ │ │ ├── employee-add.component.spec.ts │ │ │ │ └── employee-add.component.ts │ │ │ ├── employee-edit │ │ │ │ ├── employee-edit.component.css │ │ │ │ ├── employee-edit.component.html │ │ │ │ ├── employee-edit.component.spec.ts │ │ │ │ └── employee-edit.component.ts │ │ │ ├── employee-routing.module.ts │ │ │ ├── employee-view │ │ │ │ ├── employee-view.component.css │ │ │ │ ├── employee-view.component.html │ │ │ │ ├── employee-view.component.spec.ts │ │ │ │ └── employee-view.component.ts │ │ │ ├── employee.module.ts │ │ │ └── employee │ │ │ │ ├── employee.component.css │ │ │ │ ├── employee.component.html │ │ │ │ ├── employee.component.spec.ts │ │ │ │ └── employee.component.ts │ │ ├── layout-routing.module.ts │ │ ├── layout.module.ts │ │ └── layout │ │ │ ├── layout.component.css │ │ │ ├── layout.component.html │ │ │ ├── layout.component.spec.ts │ │ │ ├── layout.component.ts │ │ │ └── template │ │ │ ├── header │ │ │ ├── header.component.css │ │ │ ├── header.component.html │ │ │ ├── header.component.spec.ts │ │ │ └── header.component.ts │ │ │ └── sidebar │ │ │ ├── sidebar.component.css │ │ │ ├── sidebar.component.html │ │ │ ├── sidebar.component.spec.ts │ │ │ └── sidebar.component.ts │ ├── login │ │ ├── login-routing.module.ts │ │ ├── login.module.ts │ │ └── login │ │ │ ├── login.component.css │ │ │ ├── login.component.html │ │ │ ├── login.component.spec.ts │ │ │ └── login.component.ts │ ├── not-found │ │ ├── not-found-routing.module.ts │ │ ├── not-found.module.ts │ │ └── not-found │ │ │ ├── not-found.component.css │ │ │ ├── not-found.component.html │ │ │ ├── not-found.component.spec.ts │ │ │ └── not-found.component.ts │ └── shared │ │ ├── guards │ │ └── auth.guard.ts │ │ ├── interceptor │ │ └── auth-interceptor.service.ts │ │ ├── model │ │ ├── department.model.ts │ │ ├── employee.model.ts │ │ └── logging-user.ts │ │ └── service │ │ ├── auth.service.ts │ │ ├── department.service.ts │ │ └── employee.service.ts ├── assets │ ├── .gitkeep │ └── images │ │ └── logo.png ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css └── test.ts ├── tsconfig.app.json ├── tsconfig.base.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.java linguist-detectable=true 2 | *.js linguist-detectable=false 3 | *.html linguist-detectable=false 4 | *.xml linguist-detectable=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *# 2 | *.iml 3 | *.ipr 4 | *.iws 5 | *.jar 6 | *.sw? 7 | *~ 8 | .#* 9 | .*.md.html 10 | .DS_Store 11 | .classpath 12 | .factorypath 13 | .gradle 14 | .idea 15 | .metadata 16 | .project 17 | .recommenders 18 | .settings 19 | .springBeans 20 | /code 21 | MANIFEST.MF 22 | _site/ 23 | activemq-data 24 | bin 25 | build 26 | !/**/src/**/bin 27 | !/**/src/**/build 28 | build.log 29 | dependency-reduced-pom.xml 30 | dump.rdb 31 | interpolated*.xml 32 | lib/ 33 | manifest.yml 34 | out 35 | overridedb.* 36 | target 37 | transaction-logs 38 | .flattened-pom.xml 39 | secrets.yml 40 | .gradletasknamecache 41 | .sts4-cache -------------------------------------------------------------------------------- /config-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | payroll 8 | com.github.rshtishi 9 | 1.0-SNAPSHOT 10 | 11 | config-server 12 | jar 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-config-server 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | org.junit.vintage 28 | junit-vintage-engine 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /config-server/src/main/java/com/github/rshtishi/configserver/ConfigurationServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.configserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @SpringBootApplication 8 | @EnableConfigServer 9 | public class ConfigurationServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ConfigurationServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /config-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=config-server 2 | server.port = 8888 3 | spring.cloud.config.server.git.uri=https://github.com/rshtishi/config-repo 4 | spring.cloud.config.server.git.searchPaths=department, employee, eureka, gateway, oauth2 -------------------------------------------------------------------------------- /config-server/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | encrypt.key-store.location=classpath:keystore/server.jks 2 | encrypt.key-store.password=payroll-security 3 | encrypt.key-store.alias=serverConfigKey -------------------------------------------------------------------------------- /config-server/src/main/resources/keystore/server.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/config-server/src/main/resources/keystore/server.jks -------------------------------------------------------------------------------- /config-server/src/main/resources/static/images/configuration-server-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/config-server/src/main/resources/static/images/configuration-server-architecture.png -------------------------------------------------------------------------------- /config-server/src/test/java/com/github/rshtishi/configserver/ConfigurationServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.configserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ConfigurationServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/DepartmentApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 11 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 12 | import org.springframework.cloud.stream.annotation.EnableBinding; 13 | import org.springframework.cloud.stream.messaging.Sink; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.ComponentScan; 16 | import org.springframework.http.client.ClientHttpRequestInterceptor; 17 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 18 | import org.springframework.web.client.RestTemplate; 19 | 20 | import com.github.rshtishi.department.interceptor.UserContextInterceptor; 21 | import com.mangofactory.swagger.plugin.EnableSwagger; 22 | 23 | @SpringBootApplication 24 | @EnableSwagger 25 | @EnableCircuitBreaker 26 | @EnableResourceServer 27 | @EnableBinding(Sink.class) 28 | public class DepartmentApplication { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(DepartmentApplication.class); 31 | 32 | @LoadBalanced 33 | @Bean 34 | public RestTemplate restTemplate() { 35 | RestTemplate restTemplate = new RestTemplate(); 36 | List interceptors = restTemplate.getInterceptors(); 37 | if (interceptors == null) { 38 | restTemplate.setInterceptors(Collections.singletonList(new UserContextInterceptor())); 39 | } else { 40 | interceptors.add(new UserContextInterceptor()); 41 | restTemplate.setInterceptors(interceptors); 42 | } 43 | return restTemplate; 44 | } 45 | 46 | public static void main(String[] args) { 47 | SpringApplication.run(DepartmentApplication.class, args); 48 | LOGGER.info("Department Application Started"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/UserContext.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class UserContext { 9 | 10 | public static final String AUTHORIZATION = "Authorization"; 11 | public static final String TRACE_ID = "trace-id"; 12 | 13 | private String authorization = new String(); 14 | private String traceId = new String(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/UserContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department; 2 | 3 | public class UserContextHolder { 4 | 5 | private static final ThreadLocal userContext = new ThreadLocal(); 6 | 7 | public static final UserContext getContext() { 8 | UserContext context = userContext.get(); 9 | 10 | if (context == null) { 11 | context = createEmptyContext(); 12 | userContext.set(context); 13 | 14 | } 15 | return userContext.get(); 16 | } 17 | 18 | public static final void setContext(UserContext context) { 19 | userContext.set(context); 20 | } 21 | 22 | public static final UserContext createEmptyContext() { 23 | return new UserContext(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/configuration/I18NConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.configuration; 2 | 3 | import java.util.Locale; 4 | 5 | import org.springframework.context.MessageSource; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.support.ResourceBundleMessageSource; 9 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 11 | import org.springframework.web.servlet.i18n.CookieLocaleResolver; 12 | import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 13 | 14 | @Configuration 15 | public class I18NConfiguration implements WebMvcConfigurer { 16 | 17 | @Override 18 | public void addInterceptors(InterceptorRegistry registry) { 19 | registry.addInterceptor(localeChangeInterceptor()); 20 | } 21 | 22 | @Bean 23 | public LocaleChangeInterceptor localeChangeInterceptor() { 24 | LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); 25 | localeChangeInterceptor.setParamName("language"); 26 | return localeChangeInterceptor; 27 | } 28 | 29 | @Bean 30 | public CookieLocaleResolver cookieLocaleResolver() { 31 | CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); 32 | cookieLocaleResolver.setCookieName("language"); 33 | cookieLocaleResolver.setCookieMaxAge(3600); 34 | cookieLocaleResolver.setDefaultLocale(new Locale("en")); 35 | return cookieLocaleResolver; 36 | } 37 | 38 | @Bean 39 | public MessageSource messageSource() { 40 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 41 | messageSource.setBasename("messages"); 42 | return messageSource; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/configuration/MvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class MvcConfiguration implements WebMvcConfigurer { 9 | 10 | @Override 11 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 12 | registry 13 | .addResourceHandler("/resources/**") 14 | .addResourceLocations("/resources/"); 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/configuration/RedisConfigProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.configuration; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Getter 9 | @Setter 10 | @ConfigurationProperties(prefix="redis") 11 | public class RedisConfigProperties { 12 | 13 | private String host; 14 | private int port; 15 | } 16 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/configuration/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Primary; 7 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 8 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; 11 | 12 | @Configuration 13 | @EnableRedisRepositories 14 | public class RedisConfiguration { 15 | 16 | @Autowired 17 | private RedisConfigProperties redisConfigProperties; 18 | 19 | @Bean 20 | public RedisConfigProperties RedisConfigProperties() { 21 | return new RedisConfigProperties(); 22 | } 23 | 24 | @Bean 25 | public JedisConnectionFactory jedisConnectionFactory() { 26 | RedisStandaloneConfiguration redisStandaloneConfig =new RedisStandaloneConfiguration(); 27 | redisStandaloneConfig.setHostName(redisConfigProperties.getHost()); 28 | redisStandaloneConfig.setPort(redisConfigProperties.getPort()); 29 | JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfig); 30 | return jedisConnectionFactory; 31 | } 32 | 33 | @Primary 34 | @Bean 35 | public RedisTemplate redisTemplate() { 36 | RedisTemplate redisTemplate = new RedisTemplate(); 37 | redisTemplate.setConnectionFactory(jedisConnectionFactory()); 38 | return redisTemplate; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/configuration/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import com.mangofactory.swagger.configuration.SpringSwaggerConfig; 8 | import com.mangofactory.swagger.models.dto.ApiInfo; 9 | import com.mangofactory.swagger.models.dto.builder.ApiInfoBuilder; 10 | import com.mangofactory.swagger.plugin.EnableSwagger; 11 | import com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin; 12 | 13 | @Configuration 14 | @EnableSwagger 15 | public class SwaggerConfiguration { 16 | 17 | @Autowired 18 | private SpringSwaggerConfig springSwaggerConfig; 19 | 20 | @Bean 21 | public SwaggerSpringMvcPlugin configureSwagger() { 22 | SwaggerSpringMvcPlugin swaggerSpringMvcPlugin = new SwaggerSpringMvcPlugin(this.springSwaggerConfig); 23 | ApiInfo apiInfo = new ApiInfoBuilder().title("Department Rest API") 24 | .description("Department API for creating and managing departments").contact("randoshtishi@yahoo.com") 25 | .license("MIT License").licenseUrl("https://opensource.org/licenses/MIT").build(); 26 | swaggerSpringMvcPlugin.apiInfo(apiInfo).apiVersion("1.0").includePatterns("/departments/*.*"); 27 | return swaggerSpringMvcPlugin; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/dto/EmployeeCountChangeModel.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class EmployeeCountChangeModel { 9 | 10 | private int departmentId; 11 | private String action; 12 | private String typeName; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/dto/ErrorDetail.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.dto; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import lombok.Data; 8 | 9 | @Data 10 | public class ErrorDetail { 11 | 12 | private String title; 13 | private int status; 14 | private String detail; 15 | private long timeStamp; 16 | private String developerMessage; 17 | private Map> errors = new HashMap<>(); 18 | } 19 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/dto/ValidationError.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ValidationError { 7 | 8 | private String code; 9 | private String message; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/entity/Department.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.entity; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.Table; 8 | import javax.validation.constraints.NotNull; 9 | 10 | import lombok.Data; 11 | 12 | @Entity 13 | @Table(name = "department") 14 | @Data 15 | public class Department { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private int id; 20 | @NotNull 21 | private String name; 22 | private int noOfEmployees; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/entity/enums/EmployeeActionEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.entity.enums; 2 | 3 | public enum EmployeeActionEnum { 4 | 5 | CREATE("create"), 6 | UPDATE("update"), 7 | DELETE("delete"); 8 | 9 | private String action; 10 | 11 | EmployeeActionEnum(String action) { 12 | this.action=action; 13 | } 14 | 15 | public String action() { 16 | return action; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | public ResourceNotFoundException() {} 12 | 13 | public ResourceNotFoundException(String message) { 14 | super(message); 15 | } 16 | 17 | public ResourceNotFoundException(String message, Throwable cause) { 18 | super(message,cause); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/filter/UserContextFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.filter; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.Filter; 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.ServletRequest; 9 | import javax.servlet.ServletResponse; 10 | import javax.servlet.http.HttpServletRequest; 11 | 12 | import org.springframework.stereotype.Component; 13 | 14 | import com.github.rshtishi.department.UserContext; 15 | import com.github.rshtishi.department.UserContextHolder; 16 | 17 | @Component 18 | public class UserContextFilter implements Filter { 19 | 20 | @Override 21 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 22 | throws IOException, ServletException { 23 | HttpServletRequest httpServletRequest = (HttpServletRequest) request; 24 | UserContextHolder.getContext().setAuthorization(httpServletRequest.getHeader(UserContext.AUTHORIZATION)); 25 | UserContextHolder.getContext().setTraceId(httpServletRequest.getHeader(UserContext.TRACE_ID)); 26 | chain.doFilter(httpServletRequest, response); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/helper/DepartmentHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.helper; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import com.github.rshtishi.department.entity.Department; 6 | import com.github.rshtishi.department.exception.ResourceNotFoundException; 7 | import com.github.rshtishi.department.service.DepartmentService; 8 | 9 | @Component 10 | public class DepartmentHelper { 11 | 12 | public Department verifyDeparmentExistence(DepartmentService departmentService, int id) { 13 | Department department = departmentService.findById(id); 14 | if (department == null) { 15 | throw new ResourceNotFoundException("Department with id: " + id + " not found."); 16 | } 17 | return department; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/helper/Translator.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.helper; 2 | 3 | import java.util.Locale; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.MessageSource; 7 | import org.springframework.context.annotation.DependsOn; 8 | import org.springframework.context.i18n.LocaleContextHolder; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class Translator { 13 | 14 | @Autowired 15 | private MessageSource messageSource; 16 | 17 | public String toLocale(String msgCode) { 18 | Locale locale = LocaleContextHolder.getLocale(); 19 | return messageSource.getMessage(msgCode, null, locale); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/hystrix/DelegatingUserContextCallable.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.hystrix; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | import com.github.rshtishi.department.UserContext; 6 | import com.github.rshtishi.department.UserContextHolder; 7 | 8 | public class DelegatingUserContextCallable implements Callable { 9 | 10 | private final Callable delegate; 11 | private UserContext originalUserContext; 12 | 13 | public DelegatingUserContextCallable(Callable delegate, UserContext userContext) { 14 | this.delegate = delegate; 15 | this.originalUserContext = userContext; 16 | } 17 | 18 | public DelegatingUserContextCallable(Callable delegate) { 19 | this(delegate, UserContextHolder.getContext()); 20 | } 21 | 22 | @Override 23 | public V call() throws Exception { 24 | UserContextHolder.setContext(originalUserContext); 25 | try { 26 | return delegate.call(); 27 | } finally { 28 | this.originalUserContext = null; 29 | } 30 | } 31 | 32 | public String toString() { 33 | return delegate.toString(); 34 | } 35 | 36 | public static Callable create(Callable delegate, UserContext userContext) { 37 | return new DelegatingUserContextCallable(delegate, userContext); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/hystrix/ThreadLocalAwareStrategy.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.hystrix; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.Callable; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import com.github.rshtishi.department.UserContextHolder; 9 | import com.netflix.hystrix.HystrixThreadPoolKey; 10 | import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; 11 | import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; 12 | import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; 13 | import com.netflix.hystrix.strategy.properties.HystrixProperty; 14 | 15 | public class ThreadLocalAwareStrategy extends HystrixConcurrencyStrategy { 16 | 17 | private HystrixConcurrencyStrategy existingConcurrencyStrategy; 18 | 19 | public ThreadLocalAwareStrategy(HystrixConcurrencyStrategy existingConcurrencyStrategy) { 20 | this.existingConcurrencyStrategy = existingConcurrencyStrategy; 21 | } 22 | 23 | @Override 24 | public BlockingQueue getBlockingQueue(int maxQueueSize) { 25 | return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize) 26 | : super.getBlockingQueue(maxQueueSize); 27 | } 28 | 29 | @Override 30 | public HystrixRequestVariable getRequestVariable(HystrixRequestVariableLifecycle rv) { 31 | return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getRequestVariable(rv) 32 | : super.getRequestVariable(rv); 33 | } 34 | 35 | @Override 36 | public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty corePoolSize, 37 | HystrixProperty maximumPoolSize, HystrixProperty keepAliveTime, TimeUnit unit, 38 | BlockingQueue workQueue) { 39 | return existingConcurrencyStrategy != null 40 | ? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, 41 | unit, workQueue) 42 | : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); 43 | } 44 | 45 | @Override 46 | public Callable wrapCallable(Callable callable) { 47 | 48 | return existingConcurrencyStrategy != null 49 | ? existingConcurrencyStrategy 50 | .wrapCallable(new DelegatingUserContextCallable(callable, UserContextHolder.getContext())) 51 | : super.wrapCallable(new DelegatingUserContextCallable(callable, UserContextHolder.getContext())); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/hystrix/ThreadLocalConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.hystrix; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import com.netflix.hystrix.strategy.HystrixPlugins; 9 | import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; 10 | import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; 11 | import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; 12 | import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; 13 | import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; 14 | 15 | @Configuration 16 | public class ThreadLocalConfiguration { 17 | 18 | @Autowired(required = false) 19 | private HystrixConcurrencyStrategy existingConcurrencyStrategy; 20 | 21 | @PostConstruct 22 | public void init() { 23 | // Keeps references of existing Hystrix plugins. 24 | HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); 25 | HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher(); 26 | HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); 27 | HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook(); 28 | 29 | HystrixPlugins.reset(); 30 | 31 | // Registers existing plugins excepts the Concurrent Strategy plugin. 32 | HystrixPlugins.getInstance() 33 | .registerConcurrencyStrategy(new ThreadLocalAwareStrategy(existingConcurrencyStrategy)); 34 | HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); 35 | HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); 36 | HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); 37 | HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/interceptor/UserContextInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.interceptor; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.http.HttpHeaders; 6 | import org.springframework.http.HttpRequest; 7 | import org.springframework.http.client.ClientHttpRequestExecution; 8 | import org.springframework.http.client.ClientHttpRequestInterceptor; 9 | import org.springframework.http.client.ClientHttpResponse; 10 | 11 | import com.github.rshtishi.department.UserContext; 12 | import com.github.rshtishi.department.UserContextHolder; 13 | 14 | public class UserContextInterceptor implements ClientHttpRequestInterceptor { 15 | 16 | @Override 17 | public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) 18 | throws IOException { 19 | HttpHeaders headers = request.getHeaders(); 20 | request.getHeaders().remove(UserContext.AUTHORIZATION); 21 | headers.add(UserContext.AUTHORIZATION, UserContextHolder.getContext().getAuthorization()); 22 | headers.add(UserContext.TRACE_ID, UserContextHolder.getContext().getTraceId()); 23 | return execution.execute(request, body); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/repository/DepartmentRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.repository; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.repository.CrudRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import com.github.rshtishi.department.entity.Department; 9 | 10 | @Repository 11 | public interface DepartmentRepository extends CrudRepository { 12 | 13 | public Page findAll(Pageable pageable); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/repository/EmployeeCountRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.repository; 2 | 3 | public interface EmployeeCountRedisRepository { 4 | 5 | void saveEmployeeCount(int departmentId, long employeeCount); 6 | 7 | void deleteEmployeeCount(int dpeartmentId); 8 | 9 | long findEmployeeCount(int departmentId); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/repository/EmployeeCountRedisrepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.repository; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.data.redis.core.HashOperations; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.stereotype.Repository; 9 | 10 | @Repository 11 | public class EmployeeCountRedisrepositoryImpl implements EmployeeCountRedisRepository { 12 | 13 | private static final String HASH_NAME="EmployeeCount"; 14 | 15 | private RedisTemplate redisTemplate; 16 | private HashOperations hashOperations; 17 | 18 | @Autowired 19 | public EmployeeCountRedisrepositoryImpl(RedisTemplate redisTemplate) { 20 | this.redisTemplate =redisTemplate; 21 | } 22 | 23 | @PostConstruct 24 | private void init() { 25 | hashOperations = redisTemplate.opsForHash(); 26 | } 27 | 28 | @Override 29 | public void saveEmployeeCount(int departmentId, long employeeCount) { 30 | hashOperations.put(HASH_NAME, departmentId, employeeCount); 31 | } 32 | 33 | @Override 34 | public void deleteEmployeeCount(int departmentId) { 35 | hashOperations.delete(HASH_NAME, departmentId); 36 | } 37 | 38 | @Override 39 | public long findEmployeeCount(int departmentId) { 40 | return hashOperations.get(HASH_NAME, departmentId); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/security/JWTTokenEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.security; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 7 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 8 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 9 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 10 | 11 | public class JWTTokenEnhancer implements TokenEnhancer { 12 | 13 | @Override 14 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 15 | Map info = new HashMap<>(); 16 | info.put("details", authentication.getDetails()); 17 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); 18 | return accessToken; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/security/JWTTokenStoreConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.security; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | import org.apache.commons.io.IOUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 10 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 11 | import org.springframework.security.oauth2.provider.token.TokenStore; 12 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 13 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 14 | 15 | 16 | @Configuration 17 | public class JWTTokenStoreConfig { 18 | 19 | @Autowired 20 | private SecurityProperties securityProperties; 21 | 22 | @Bean 23 | public SecurityProperties securityProperties() { 24 | return new SecurityProperties(); 25 | } 26 | 27 | private String getPublicKeyAsString() { 28 | try { 29 | return IOUtils.toString(securityProperties.getJwt().getPublicKey().getInputStream(), 30 | StandardCharsets.UTF_8); 31 | } catch (Exception e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | @Bean 37 | public JwtAccessTokenConverter jwtAccessTokenConverter() { 38 | JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); 39 | jwtAccessTokenConverter.setVerifierKey(getPublicKeyAsString()); 40 | return jwtAccessTokenConverter; 41 | } 42 | 43 | @Bean 44 | public TokenStore tokenStore() { 45 | return new JwtTokenStore(jwtAccessTokenConverter()); 46 | } 47 | 48 | @Bean 49 | public TokenEnhancer tokenEnhancer() { 50 | return new JWTTokenEnhancer(); 51 | } 52 | 53 | @Bean 54 | public DefaultTokenServices defaultTokenServices() { 55 | DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); 56 | defaultTokenServices.setTokenStore(tokenStore()); 57 | defaultTokenServices.setTokenEnhancer(tokenEnhancer()); 58 | return defaultTokenServices; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/security/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 7 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 8 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 9 | import org.springframework.security.oauth2.provider.token.TokenStore; 10 | 11 | @Configuration 12 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 13 | 14 | @Autowired 15 | private TokenStore tokenStore; 16 | @Autowired 17 | private DefaultTokenServices defaultTokenServices; 18 | 19 | @Override 20 | public void configure(HttpSecurity httpSecurity) throws Exception { 21 | httpSecurity.authorizeRequests().antMatchers("/static/**", "/v2/api-docs", "/configuration/**", "/swagger*/**", 22 | "/webjars/**", "/api-docs/**").permitAll().anyRequest().authenticated(); 23 | } 24 | 25 | @Override 26 | public void configure(final ResourceServerSecurityConfigurer resources) { 27 | resources.tokenStore(tokenStore).tokenServices(defaultTokenServices); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/security/SecurityProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.security; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.core.io.Resource; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @ConfigurationProperties(prefix="security",ignoreUnknownFields = true) 10 | @Getter 11 | @Setter 12 | public class SecurityProperties { 13 | 14 | private Jwt jwt; 15 | 16 | @Getter 17 | @Setter 18 | public static class Jwt { 19 | private Resource publicKey; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/service/DepartmentService.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.service; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.Pageable; 5 | 6 | import com.github.rshtishi.department.entity.Department; 7 | 8 | public interface DepartmentService { 9 | 10 | public Page findAll(Pageable pageable); 11 | 12 | public Department findById(int id); 13 | 14 | public Department createDepartment(Department department); 15 | 16 | public Department updateDepartment(Department department); 17 | 18 | public void deleteDepartment(int id); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/service/EmployeeCountRedisService.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.service; 2 | 3 | 4 | public interface EmployeeCountRedisService { 5 | 6 | public long findEmployeeCountFromCache(int departmentId); 7 | 8 | public void saveEmployeeCountInCache(int departmentId, long employeeCount); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/service/EmployeeCountRedisServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | import com.github.rshtishi.department.repository.EmployeeCountRedisRepository; 6 | import brave.Span; 7 | import brave.Tracing; 8 | 9 | @Service 10 | public class EmployeeCountRedisServiceImpl implements EmployeeCountRedisService { 11 | 12 | @Autowired 13 | private EmployeeCountRedisRepository employeeCountRedisRepository; 14 | @Autowired 15 | private Tracing tracing; 16 | 17 | @Override 18 | public long findEmployeeCountFromCache(int departmentId) { 19 | Span span = tracing.tracer().nextSpan().name("ReadEmployeeCountFromCache") 20 | .tag("peer.service", "redis") 21 | .start(); 22 | try { 23 | return employeeCountRedisRepository.findEmployeeCount(departmentId); 24 | } catch (Exception exception) { 25 | return -1; 26 | } finally { 27 | span.finish(); 28 | } 29 | } 30 | 31 | @Override 32 | public void saveEmployeeCountInCache(int departmentId, long employeeCount) { 33 | Span span = tracing.tracer().nextSpan().name("SaveEmployeeCountInCache"); 34 | try { 35 | employeeCountRedisRepository.saveEmployeeCount(departmentId, employeeCount); 36 | } catch (Exception exception) { 37 | } finally { 38 | span.finish(); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/sink/EmployeeCountSink.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.sink; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.cloud.stream.annotation.StreamListener; 5 | import org.springframework.cloud.stream.messaging.Sink; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.github.rshtishi.department.dto.EmployeeCountChangeModel; 9 | import com.github.rshtishi.department.entity.enums.EmployeeActionEnum; 10 | import com.github.rshtishi.department.service.EmployeeCountRedisService; 11 | 12 | @Component 13 | public class EmployeeCountSink { 14 | 15 | @Autowired 16 | private EmployeeCountRedisService employeeCountRedisService; 17 | 18 | @StreamListener(Sink.INPUT) 19 | public void employeeChangeSink(EmployeeCountChangeModel change) { 20 | long employeeCount = employeeCountRedisService.findEmployeeCountFromCache(change.getDepartmentId()); 21 | if(change.getAction().equals(EmployeeActionEnum.CREATE.action())) { 22 | employeeCount++; 23 | employeeCountRedisService.saveEmployeeCountInCache(change.getDepartmentId(), employeeCount); 24 | } else if (change.getAction().equals(EmployeeActionEnum.DELETE.action())) { 25 | employeeCount--; 26 | employeeCountRedisService.saveEmployeeCountInCache(change.getDepartmentId(), employeeCount); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /department/src/main/java/com/github/rshtishi/department/thirdparty/EmployeeRestTemplate.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.thirdparty; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | import com.github.rshtishi.department.service.EmployeeCountRedisService; 10 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | 16 | @Component 17 | public class EmployeeRestTemplate { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeRestTemplate.class); 20 | 21 | @Autowired 22 | private RestTemplate restTemplate; 23 | @Autowired 24 | private EmployeeCountRedisService employeeCountRedisService; 25 | 26 | @HystrixCommand(fallbackMethod = "countEmployeesByDepartmentIdFallback") 27 | public long countEmployeesByDepartmentId(int departmentId) { 28 | LOGGER.info("Called countEmployeesByDepartmentId, departmentId: "+departmentId); 29 | long employeeCount = employeeCountRedisService.findEmployeeCountFromCache(departmentId); 30 | if(employeeCount>-1) { 31 | return employeeCount; 32 | } 33 | ResponseEntity response = restTemplate.exchange("http://employee/employees/{departmentId}/count", 34 | HttpMethod.GET, null, Long.class, departmentId); 35 | employeeCount = response.getBody(); 36 | if(employeeCount>-1) { 37 | employeeCountRedisService.saveEmployeeCountInCache(departmentId, employeeCount); 38 | } 39 | return response.getBody(); 40 | } 41 | 42 | public long countEmployeesByDepartmentIdFallback(int departmentId) { 43 | return -1; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /department/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=department 2 | spring.profiles.active=default 3 | cloud.config.uri=http://localhost:8888 4 | -------------------------------------------------------------------------------- /department/src/main/resources/db/changelog/01-create-department-scheme.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /department/src/main/resources/db/changelog/02-data-insert-departments.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /department/src/main/resources/db/changelog/03-alter-departments.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /department/src/main/resources/db/liquibase-changelog.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /department/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | 17 | 18 | 20 | ${LOG_STASH_LOCATION}/department.log 21 | 23 | ${LOG_STASH_LOCATION}/department.%d{yyyy-MM-dd}.log 24 | 25 | 7 26 | 27 | 28 | 29 | 30 | 31 | 33 | ${LOG_LOCATION}/department.log 34 | 35 | ${LOG_PATTERN} 36 | 37 | 39 | ${LOG_LOCATION}/archived/department-%d{yyyy-MM-dd}.%i.log 40 | 41 | 43 | 10MB 44 | 45 | 46 | 47 | 48 | 50 | 51 | ${LOG_PATTERN} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /department/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | error.resourceNotFound=Resource Not Found 2 | error.departmentNotFound=Department Not Found 3 | error.validationFailed=Validation Failed 4 | error.inputValidationFailed=Input Validation Failed -------------------------------------------------------------------------------- /department/src/main/resources/messages_al.properties: -------------------------------------------------------------------------------- 1 | error.resourceNotFound=Burimi nuk ekziston 2 | error.departmentNotFound=Departamenti nuk ekziston 3 | error.validationFailed=Kontrolli deshtoi 4 | error.inputValidationFailed=Kontrolli i dhenave hyrese deshtoi -------------------------------------------------------------------------------- /department/src/main/resources/public.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvJXQdLvlF1MzQn1IGdfF 3 | Y9VRI3DaYq2cD3Mb3pBQ6KWk2M2J8UM8KAY9mqCh27U/NQ/+rhkxbTVlAGkIS4jL 4 | hx+AAzmNpuD89XPFAcmrvCt7CTGzi0bd/3WzK8dP2clxnVFANh7mbu24U91jK9ZS 5 | OqoxI6AYUDs2a328Ljj0PmHKGZjTfMRw5tPLus/yd4q1oLssK/GHfxgHvmD9o9H3 6 | WN1UdKEN7IvtlwuJ7lZQk9uLZCAkj7RHaTzzVt65dEJ6zJMXi72d3P+ZJQveRQCO 7 | vxAEQjI1c3U3V8KrkxJbskyzD0cKxCrKhh82fenraFrfHxKOW5jJj2n/LL5DNaGF 8 | CwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /department/src/main/resources/static/images/department-service-architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/department/src/main/resources/static/images/department-service-architecture.jpeg -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/department/src/main/resources/static/swagger-ui/images/explorer_icons.png -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/department/src/main/resources/static/swagger-ui/images/logo_small.png -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/department/src/main/resources/static/swagger-ui/images/pet_store_api.png -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/department/src/main/resources/static/swagger-ui/images/throbber.gif -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/department/src/main/resources/static/swagger-ui/images/wordnik_api.png -------------------------------------------------------------------------------- /department/src/main/resources/static/swagger-ui/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /department/src/test/java/com/github/rshtishi/department/service/EmployeeCountRedisServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.service; 2 | 3 | 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.times; 6 | import static org.mockito.Mockito.verify; 7 | import static org.mockito.Mockito.when; 8 | 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.InjectMocks; 13 | import org.mockito.Mock; 14 | import org.mockito.MockitoAnnotations; 15 | 16 | import static org.mockito.Mockito.anyString; 17 | import static org.mockito.Mockito.anyInt; 18 | import static org.mockito.Mockito.anyLong; 19 | import static org.mockito.Mockito.doNothing; 20 | 21 | import com.github.rshtishi.department.repository.EmployeeCountRedisRepository; 22 | 23 | import brave.Span; 24 | import brave.Tracer; 25 | import brave.Tracing; 26 | 27 | class EmployeeCountRedisServiceImplTest { 28 | 29 | @InjectMocks 30 | private EmployeeCountRedisServiceImpl employeeCountRedisService; 31 | @Mock 32 | private EmployeeCountRedisRepository employeeCountRedisRepository; 33 | @Mock 34 | private Tracing tracing; 35 | 36 | @BeforeEach 37 | public void setUp() { 38 | MockitoAnnotations.initMocks(this); 39 | } 40 | 41 | @Test 42 | void testFindEmployeeCountFromCache() { 43 | //setup 44 | int departmentId = 1; 45 | Tracer tracer = mock(Tracer.class); 46 | Span span = mock(Span.class); 47 | doNothing().when(span).finish(); 48 | when(span.start()).thenReturn(span); 49 | when(span.tag(anyString(), anyString())).thenReturn(span); 50 | when(span.name(anyString())).thenReturn(span); 51 | when(tracer.nextSpan()).thenReturn(span); 52 | when(tracing.tracer()).thenReturn(tracer); 53 | long expectedEmployeeCount = 2L; 54 | when(employeeCountRedisRepository.findEmployeeCount(departmentId)).thenReturn(expectedEmployeeCount); 55 | //execute 56 | long actualEmployeeCount = employeeCountRedisService.findEmployeeCountFromCache(departmentId); 57 | //verify 58 | Assertions.assertEquals(expectedEmployeeCount,actualEmployeeCount); 59 | } 60 | 61 | @Test 62 | public void testSaveEmployeeCountInCache() { 63 | //setup 64 | int departmentId=1; 65 | long employeeCount=3L; 66 | Tracer tracer = mock(Tracer.class); 67 | Span span = mock(Span.class); 68 | when(tracer.nextSpan()).thenReturn(span); 69 | when(span.name(anyString())).thenReturn(span); 70 | doNothing().when(span).finish(); 71 | doNothing().when(employeeCountRedisRepository).saveEmployeeCount(anyInt(), anyLong()); 72 | //execute 73 | employeeCountRedisRepository.saveEmployeeCount(departmentId, employeeCount); 74 | //verify 75 | verify(employeeCountRedisRepository,times(1)).saveEmployeeCount(departmentId, employeeCount); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /department/src/test/java/com/github/rshtishi/department/thirdparty/EmployeeRestTemplateTest.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.department.thirdparty; 2 | 3 | import static org.mockito.Mockito.when; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.Mockito; 11 | import org.mockito.MockitoAnnotations; 12 | import org.springframework.http.HttpMethod; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import com.github.rshtishi.department.service.EmployeeCountRedisService; 17 | 18 | class EmployeeRestTemplateTest { 19 | 20 | @InjectMocks 21 | private EmployeeRestTemplate employeeRestTemplate; 22 | @Mock 23 | private RestTemplate restTemplate; 24 | @Mock 25 | private EmployeeCountRedisService employeeCountRedisService; 26 | 27 | @BeforeEach 28 | public void setUp() { 29 | MockitoAnnotations.initMocks(this); 30 | } 31 | 32 | @Test 33 | public void testCountEmployeesByDepartmentId_whenCacheIsNotEmpty() { 34 | // setup 35 | int departmentId = 1; 36 | long employeeNoExpected = 2L; 37 | when(employeeCountRedisService.findEmployeeCountFromCache(Mockito.anyInt())).thenReturn(employeeNoExpected); 38 | // execute 39 | long employeeNoActual = employeeRestTemplate.countEmployeesByDepartmentId(departmentId); 40 | // verify 41 | Assertions.assertEquals(employeeNoExpected, employeeNoActual); 42 | } 43 | 44 | @Test 45 | public void testCountEmployeesByDepartmentId_whenCacheIsEmpty() { 46 | int departmentId = 1; 47 | long employeeNoExpected = 2L; 48 | String uri = "http://employee/employees/{departmentId}/count"; 49 | ResponseEntity responseExpected = ResponseEntity.ok(employeeNoExpected); 50 | when(employeeCountRedisService.findEmployeeCountFromCache(Mockito.anyInt())).thenReturn(-1L); 51 | when(restTemplate.exchange(uri, HttpMethod.GET, null, Long.class, departmentId)) 52 | .thenReturn(responseExpected); 53 | // execute 54 | long employeeNoActual = employeeRestTemplate.countEmployeesByDepartmentId(departmentId); 55 | // verify 56 | Assertions.assertEquals(employeeNoExpected, employeeNoActual); 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /department/src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=department 2 | server.port=8081 3 | 4 | #Liquibase 5 | spring.liquibase.change-log=classpath:db/liquibase-changelog.xml 6 | spring.liquibase.enabled=true 7 | 8 | #H2 DB 9 | spring.jpa.hibernate.ddl-auto=none 10 | spring.h2.console.enabled=true 11 | spring.datasource.url=jdbc:h2:mem:departmentdb 12 | spring.datasource.driverClassName=org.h2.Driver 13 | spring.datasource.username=sa 14 | spring.datasource.password=password 15 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 16 | 17 | #OAuth2 18 | security.oauth2.resource.userInfoUri=http://localhost:8901/user 19 | security.jwt.public-key=classpath:public.txt -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/EmployeeApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.cloud.stream.annotation.EnableBinding; 8 | import org.springframework.cloud.stream.messaging.Source; 9 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 10 | 11 | import com.mangofactory.swagger.plugin.EnableSwagger; 12 | 13 | 14 | @SpringBootApplication 15 | @EnableSwagger 16 | @EnableResourceServer 17 | @EnableBinding(Source.class) 18 | public class EmployeeApplication { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeApplication.class); 21 | 22 | public static void main(String[] args) { 23 | LOGGER.info("Employee Application Started"); 24 | SpringApplication.run(EmployeeApplication.class, args); 25 | } 26 | } -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/configuration/MvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class MvcConfiguration implements WebMvcConfigurer { 9 | 10 | @Override 11 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 12 | registry 13 | .addResourceHandler("/resources/**") 14 | .addResourceLocations("/resources/"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/configuration/SwaggerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import com.mangofactory.swagger.configuration.SpringSwaggerConfig; 8 | import com.mangofactory.swagger.models.dto.ApiInfo; 9 | import com.mangofactory.swagger.models.dto.builder.ApiInfoBuilder; 10 | import com.mangofactory.swagger.plugin.EnableSwagger; 11 | import com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin; 12 | 13 | @Configuration 14 | @EnableSwagger 15 | public class SwaggerConfiguration { 16 | 17 | @Autowired 18 | private SpringSwaggerConfig springSwaggerConfig; 19 | 20 | @Bean 21 | public SwaggerSpringMvcPlugin configureSwagger() { 22 | SwaggerSpringMvcPlugin swaggerSpringMvcPlugin = new SwaggerSpringMvcPlugin(this.springSwaggerConfig); 23 | ApiInfo apiInfo = new ApiInfoBuilder().title("Employee Rest API") 24 | .description("Employee API for creating and managing employees").contact("randoshtishi@yahoo.com") 25 | .license("MIT License").licenseUrl("https://opensource.org/licenses/MIT").build(); 26 | swaggerSpringMvcPlugin.apiInfo(apiInfo).apiVersion("1.0").includePatterns("/employees/*.*"); 27 | return swaggerSpringMvcPlugin; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/entity/Employee.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.entity; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.SequenceGenerator; 8 | import javax.persistence.Table; 9 | import javax.validation.constraints.NotNull; 10 | 11 | import lombok.Data; 12 | 13 | @Entity 14 | @Table(name = "employee") 15 | @Data 16 | public class Employee { 17 | 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private int id; 22 | @NotNull 23 | private String firstname; 24 | @NotNull 25 | private String lastname; 26 | private String address; 27 | private String phone; 28 | private int departmentId; 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/entity/EmployeeActionEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.entity; 2 | 3 | public enum EmployeeActionEnum { 4 | CREATE("create"), UPDATE("update"), DELETE("delete"); 5 | 6 | private String action; 7 | 8 | EmployeeActionEnum(String action) { 9 | this.action = action; 10 | } 11 | 12 | public String action() { 13 | return action; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/entity/EmployeeCountChangeModel.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class EmployeeCountChangeModel { 9 | 10 | private int departmentId; 11 | private String action; 12 | private String typeName; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/entity/ErrorDetail.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.entity; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import lombok.Data; 8 | 9 | @Data 10 | public class ErrorDetail { 11 | 12 | private String title; 13 | private int status; 14 | private String detail; 15 | private long timeStamp; 16 | private String developerMessage; 17 | private Map> errors = new HashMap<>(); 18 | } 19 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/entity/ValidationError.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.entity; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ValidationError { 7 | 8 | private String code; 9 | private String message; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | public ResourceNotFoundException() {} 12 | 13 | public ResourceNotFoundException(String message) { 14 | super(message); 15 | } 16 | 17 | public ResourceNotFoundException(String message, Throwable cause) { 18 | super(message,cause); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/helper/EmployeeHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.helper; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import com.github.rshtishi.payroll.employee.entity.Employee; 6 | import com.github.rshtishi.payroll.employee.exception.ResourceNotFoundException; 7 | import com.github.rshtishi.payroll.employee.service.EmployeeService; 8 | 9 | @Component 10 | public class EmployeeHelper { 11 | 12 | public Employee verifyEmployeeExistance(EmployeeService employeeService, int id) { 13 | Employee employee = employeeService.findById(id); 14 | if(employee==null) { 15 | throw new ResourceNotFoundException("Employee with id: "+id+" not found."); 16 | } 17 | return employee; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/helper/Translator.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.helper; 2 | 3 | import java.util.Locale; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.MessageSource; 7 | import org.springframework.context.i18n.LocaleContextHolder; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class Translator { 12 | 13 | @Autowired 14 | private MessageSource messageSource; 15 | 16 | public String toLocale(String msgCode) { 17 | Locale locale = LocaleContextHolder.getLocale(); 18 | return messageSource.getMessage(msgCode, null, locale); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.repository.CrudRepository; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import com.github.rshtishi.payroll.employee.entity.Employee; 11 | 12 | @Repository 13 | public interface EmployeeRepository extends CrudRepository { 14 | 15 | 16 | public Page findAll(Pageable pageable); 17 | 18 | public long countByDepartmentId(int departmentId); 19 | 20 | public Employee save(Employee employee); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/security/JWTTokenEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.security; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 7 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 8 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 9 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 10 | 11 | public class JWTTokenEnhancer implements TokenEnhancer { 12 | 13 | @Override 14 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 15 | Map info = new HashMap<>(); 16 | info.put("details", authentication.getDetails()); 17 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); 18 | return accessToken; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/security/JWTTokenStoreConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.security; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | import org.apache.commons.io.IOUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 10 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 11 | import org.springframework.security.oauth2.provider.token.TokenStore; 12 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 13 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 14 | 15 | @Configuration 16 | public class JWTTokenStoreConfig { 17 | 18 | @Autowired 19 | private SecurityProperties securityProperties; 20 | 21 | @Bean 22 | public SecurityProperties securityProperties() { 23 | return new SecurityProperties(); 24 | } 25 | 26 | private String getPublicKeyAsString() { 27 | try { 28 | return IOUtils.toString(securityProperties.getJwt().getPublicKey().getInputStream(), 29 | StandardCharsets.UTF_8); 30 | } catch (Exception e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | @Bean 36 | public JwtAccessTokenConverter jwtAccessTokenConverter() { 37 | JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); 38 | jwtAccessTokenConverter.setVerifierKey(getPublicKeyAsString()); 39 | return jwtAccessTokenConverter; 40 | } 41 | 42 | @Bean 43 | public TokenStore tokenStore() { 44 | return new JwtTokenStore(jwtAccessTokenConverter()); 45 | } 46 | 47 | @Bean 48 | public TokenEnhancer tokenEnhancer() { 49 | return new JWTTokenEnhancer(); 50 | } 51 | 52 | @Bean 53 | public DefaultTokenServices defaultTokenServices() { 54 | DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); 55 | defaultTokenServices.setTokenStore(tokenStore()); 56 | defaultTokenServices.setTokenEnhancer(tokenEnhancer()); 57 | return defaultTokenServices; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/security/ResourceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 7 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 8 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 9 | import org.springframework.security.oauth2.provider.token.TokenStore; 10 | 11 | @Configuration 12 | public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 13 | 14 | @Autowired 15 | private TokenStore tokenStore; 16 | @Autowired 17 | private DefaultTokenServices defaultTokenServices; 18 | 19 | @Override 20 | public void configure(HttpSecurity httpSecurity) throws Exception { 21 | httpSecurity.authorizeRequests().antMatchers("/static/**", "/v2/api-docs", "/configuration/**", "/swagger*/**", 22 | "/webjars/**", "/api-docs/**").permitAll().anyRequest().authenticated(); 23 | } 24 | 25 | @Override 26 | public void configure(final ResourceServerSecurityConfigurer resources) { 27 | resources.tokenStore(tokenStore).tokenServices(defaultTokenServices); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/security/SecurityProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.security; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.core.io.Resource; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @ConfigurationProperties(prefix="security",ignoreUnknownFields = true) 10 | @Getter 11 | @Setter 12 | public class SecurityProperties { 13 | 14 | private Jwt jwt; 15 | 16 | @Getter 17 | @Setter 18 | public static class Jwt { 19 | private Resource publicKey; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/service/EmployeeService.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.service; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.Pageable; 5 | 6 | import com.github.rshtishi.payroll.employee.entity.Employee; 7 | 8 | public interface EmployeeService { 9 | 10 | public Page findAll(Pageable pageable); 11 | 12 | public Employee findById(int id); 13 | 14 | public long countByDepartmentId(int departmentId); 15 | 16 | public Employee createEmployee(Employee employee); 17 | 18 | public Employee updateEmployee(Employee employee); 19 | 20 | public void deleteEmployee(int employeeId); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/service/EmployeeServiceImp.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.service; 2 | 3 | import java.util.Optional; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.stereotype.Service; 11 | 12 | import com.github.rshtishi.payroll.employee.entity.Employee; 13 | import com.github.rshtishi.payroll.employee.entity.EmployeeActionEnum; 14 | import com.github.rshtishi.payroll.employee.repository.EmployeeRepository; 15 | import com.github.rshtishi.payroll.employee.source.EmployeeSource; 16 | 17 | @Service 18 | public class EmployeeServiceImp implements EmployeeService { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeServiceImp.class); 21 | 22 | @Autowired 23 | private EmployeeRepository employeeRepository; 24 | @Autowired 25 | private EmployeeSource employeeSource; 26 | 27 | @Override 28 | public Page findAll(Pageable pageable) { 29 | LOGGER.info("findAll called"); 30 | return employeeRepository.findAll(pageable); 31 | } 32 | 33 | @Override 34 | public Employee findById(int id) { 35 | LOGGER.info("findById called, id: " + id); 36 | Employee employee = null; 37 | Optional optionalEmployee = employeeRepository.findById(id); 38 | if (optionalEmployee.isPresent()) { 39 | employee = optionalEmployee.get(); 40 | } 41 | return employee; 42 | } 43 | 44 | @Override 45 | public long countByDepartmentId(int departmentId) { 46 | LOGGER.info("countByDepartmentId called, departmentId: " + departmentId); 47 | return employeeRepository.countByDepartmentId(departmentId); 48 | } 49 | 50 | @Override 51 | public Employee createEmployee(Employee employee) { 52 | LOGGER.info("createEmployee called, employee: " + employee); 53 | employee = employeeRepository.save(employee); 54 | employeeSource.publishEmployeeCountChange(employee.getDepartmentId(), EmployeeActionEnum.CREATE.action()); 55 | return employee; 56 | 57 | } 58 | 59 | @Override 60 | public Employee updateEmployee(Employee employee) { 61 | LOGGER.info("updateEmployee, employee: " + employee); 62 | employee = employeeRepository.save(employee); 63 | return employee; 64 | } 65 | 66 | @Override 67 | public void deleteEmployee(int employeeId) { 68 | LOGGER.info("deleteEmployee, employeeId: " + employeeId); 69 | Employee employee = employeeRepository.findById(employeeId).get(); 70 | employeeRepository.deleteById(employeeId); 71 | employeeSource.publishEmployeeCountChange(employee.getDepartmentId(), EmployeeActionEnum.DELETE.action()); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /employee/src/main/java/com/github/rshtishi/payroll/employee/source/EmployeeSource.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.payroll.employee.source; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.cloud.stream.messaging.Source; 5 | import org.springframework.integration.support.MessageBuilder; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.github.rshtishi.payroll.employee.entity.EmployeeCountChangeModel; 9 | 10 | @Component 11 | public class EmployeeSource { 12 | 13 | @Autowired 14 | private Source source; 15 | 16 | public void publishEmployeeCountChange(int departmentId, String action) { 17 | EmployeeCountChangeModel change = new EmployeeCountChangeModel(departmentId, action, 18 | EmployeeCountChangeModel.class.getTypeName()); 19 | source.output().send(MessageBuilder.withPayload(change).build()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /employee/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=employee 2 | spring.profiles.active=default 3 | cloud.config.uri=http://localhost:8888 4 | 5 | -------------------------------------------------------------------------------- /employee/src/main/resources/db/changelog/01-create-employee-scheme.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /employee/src/main/resources/db/changelog/02-data-insert-employees.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /employee/src/main/resources/db/changelog/03-alter-employees.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /employee/src/main/resources/db/liquibase-changelog.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /employee/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | ${LOG_STASH_LOCATION}/employee.log 17 | 19 | ${LOG_STASH_LOCATION}/employee.%d{yyyy-MM-dd}.log 20 | 21 | 7 22 | 23 | 24 | 25 | 26 | 27 | 28 | ${LOG_LOCATION}/employee.log 29 | 30 | ${LOG_PATTERN} 31 | 32 | 34 | ${LOG_LOCATION}/archived/employee-%d{yyyy-MM-dd}.%i.log 35 | 36 | 38 | 10MB 39 | 40 | 41 | 42 | 43 | 45 | 46 | ${LOG_PATTERN} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /employee/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | error.resourceNotFound=Resource Not Found 2 | error.departmentNotFound=Department Not Found 3 | error.validationFailed=Validation Failed 4 | error.inputValidationFailed=Input Validation Failed -------------------------------------------------------------------------------- /employee/src/main/resources/messages_al.properties: -------------------------------------------------------------------------------- 1 | error.resourceNotFound=Burimi nuk ekziston 2 | error.departmentNotFound=Departamenti nuk ekziston 3 | error.validationFailed=Kontrolli deshtoi 4 | error.inputValidationFailed=Kontrolli i dhenave hyrese deshtoi -------------------------------------------------------------------------------- /employee/src/main/resources/public.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvJXQdLvlF1MzQn1IGdfF 3 | Y9VRI3DaYq2cD3Mb3pBQ6KWk2M2J8UM8KAY9mqCh27U/NQ/+rhkxbTVlAGkIS4jL 4 | hx+AAzmNpuD89XPFAcmrvCt7CTGzi0bd/3WzK8dP2clxnVFANh7mbu24U91jK9ZS 5 | OqoxI6AYUDs2a328Ljj0PmHKGZjTfMRw5tPLus/yd4q1oLssK/GHfxgHvmD9o9H3 6 | WN1UdKEN7IvtlwuJ7lZQk9uLZCAkj7RHaTzzVt65dEJ6zJMXi72d3P+ZJQveRQCO 7 | vxAEQjI1c3U3V8KrkxJbskyzD0cKxCrKhh82fenraFrfHxKOW5jJj2n/LL5DNaGF 8 | CwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /employee/src/main/resources/static/images/employee-service-architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/employee/src/main/resources/static/images/employee-service-architecture.jpeg -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/employee/src/main/resources/static/swagger-ui/images/explorer_icons.png -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/employee/src/main/resources/static/swagger-ui/images/logo_small.png -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/employee/src/main/resources/static/swagger-ui/images/pet_store_api.png -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/employee/src/main/resources/static/swagger-ui/images/throbber.gif -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/employee/src/main/resources/static/swagger-ui/images/wordnik_api.png -------------------------------------------------------------------------------- /employee/src/main/resources/static/swagger-ui/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /employee/src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=department 2 | server.port=8081 3 | 4 | #Liquibase 5 | spring.liquibase.change-log=classpath:db/liquibase-changelog.xml 6 | spring.liquibase.enabled=true 7 | 8 | #H2 DB 9 | spring.jpa.hibernate.ddl-auto=none 10 | spring.h2.console.enabled=true 11 | spring.datasource.url=jdbc:h2:mem:employeedb 12 | spring.datasource.driverClassName=org.h2.Driver 13 | spring.datasource.username=sa 14 | spring.datasource.password=password 15 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 16 | 17 | #OAuth2 18 | security.oauth2.resource.userInfoUri=http://localhost:8901/user 19 | security.jwt.public-key=classpath:public.txt -------------------------------------------------------------------------------- /eureka-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | payroll 8 | com.github.rshtishi 9 | 1.0-SNAPSHOT 10 | 11 | eureka-server 12 | jar 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-netflix-eureka-server 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | org.junit.vintage 28 | junit-vintage-engine 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-config-client 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /eureka-server/src/main/java/com/github/rshtishi/eurekaserver/EurekaServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.eurekaserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaServer 9 | public class EurekaServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(EurekaServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /eureka-server/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=eureka 2 | spring.profiles.active=default 3 | cloud.config.uri=http://localhost:8888 4 | -------------------------------------------------------------------------------- /eureka-server/src/main/resources/static/images/eureka-server-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/eureka-server/src/main/resources/static/images/eureka-server-architecture.png -------------------------------------------------------------------------------- /gateway-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | payroll 8 | com.github.rshtishi 9 | 1.0-SNAPSHOT 10 | 11 | gateway-server 12 | jar 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-netflix-zuul 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-client 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-test 29 | test 30 | 31 | 32 | org.junit.vintage 33 | junit-vintage-engine 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-config-client 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-actuator 46 | 47 | 48 | 49 | org.springframework.cloud 50 | spring-cloud-starter-sleuth 51 | 52 | 53 | 54 | net.logstash.logback 55 | logstash-logback-encoder 56 | 4.9 57 | 58 | 59 | 60 | org.springframework.cloud 61 | spring-cloud-starter-zipkin 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /gateway-server/src/main/java/com/github/rshtishi/gatewayserver/GatewayServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.gatewayserver; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 11 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.web.cors.CorsConfiguration; 14 | import org.springframework.web.cors.CorsConfigurationSource; 15 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 16 | import org.springframework.web.filter.CorsFilter; 17 | 18 | 19 | @SpringBootApplication 20 | @EnableZuulProxy 21 | @EnableDiscoveryClient 22 | public class GatewayServerApplication { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(GatewayServerApplication.class); 25 | 26 | public static void main(String[] args) { 27 | LOGGER.info("Gateway Application Started"); 28 | SpringApplication.run(GatewayServerApplication.class, args); 29 | } 30 | 31 | @Bean 32 | public CorsFilter corsConfigurationSource() { 33 | CorsConfiguration configuration = new CorsConfiguration(); 34 | configuration.addAllowedOrigin("*"); 35 | configuration.setAllowedMethods(Arrays.asList("POST, PUT, GET, OPTIONS, DELETE")); 36 | configuration.addAllowedHeader("*"); 37 | configuration.addAllowedMethod("*"); 38 | configuration.setAllowCredentials(true); 39 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 40 | source.registerCorsConfiguration("/**", configuration); 41 | return new CorsFilter(source); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /gateway-server/src/main/java/com/github/rshtishi/gatewayserver/filter/ResponseFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.gatewayserver.filter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.netflix.zuul.ZuulFilter; 9 | import com.netflix.zuul.context.RequestContext; 10 | import com.netflix.zuul.exception.ZuulException; 11 | 12 | import brave.Tracer; 13 | 14 | @Component 15 | public class ResponseFilter extends ZuulFilter { 16 | 17 | private static final int FILTER_ORDER = 1; 18 | private static final boolean SHOULD_FILTER = true; 19 | private static final String FILTER_TYPE = "post"; 20 | private static final Logger LOGGER = LoggerFactory.getLogger(ResponseFilter.class); 21 | 22 | @Autowired 23 | private Tracer tracer; 24 | 25 | @Override 26 | public boolean shouldFilter() { 27 | return SHOULD_FILTER; 28 | } 29 | 30 | @Override 31 | public Object run() throws ZuulException { 32 | RequestContext ctx = RequestContext.getCurrentContext(); 33 | ctx.getResponse().addHeader("trace-id", tracer.currentSpan().context().traceIdString()); 34 | return null; 35 | } 36 | 37 | @Override 38 | public String filterType() { 39 | return FILTER_TYPE; 40 | } 41 | 42 | @Override 43 | public int filterOrder() { 44 | return FILTER_ORDER; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /gateway-server/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=gateway 2 | spring.profiles.active=default 3 | cloud.config.uri=http://localhost:8888 4 | 5 | zuul.sensitiveHeaders=Cookie, Set-Cookie 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /gateway-server/src/main/resources/keystore/gateway.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/gateway-server/src/main/resources/keystore/gateway.jks -------------------------------------------------------------------------------- /gateway-server/src/main/resources/keystore/gateway.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/gateway-server/src/main/resources/keystore/gateway.p12 -------------------------------------------------------------------------------- /gateway-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 16 | ${LOG_STASH_LOCATION}/gateway.log 17 | 19 | ${LOG_STASH_LOCATION}/gateway.%d{yyyy-MM-dd}.log 20 | 21 | 7 22 | 23 | 24 | 25 | 26 | 27 | 28 | ${LOG_LOCATION}/gateway.log 29 | 30 | ${LOG_PATTERN} 31 | 32 | 34 | ${LOG_LOCATION}/archived/gateway-%d{yyyy-MM-dd}.%i.log 35 | 36 | 38 | 10MB 39 | 40 | 41 | 42 | 43 | 45 | 46 | ${LOG_PATTERN} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /gateway-server/src/main/resources/static/images/gateway-server.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/gateway-server/src/main/resources/static/images/gateway-server.jpeg -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/github/rshtishi/oauth2server/Oauth2ServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.oauth2server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 8 | 9 | @SpringBootApplication 10 | @EnableResourceServer 11 | @EnableAuthorizationServer 12 | public class Oauth2ServerApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Oauth2ServerApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/github/rshtishi/oauth2server/configuration/JWTTokenStoreConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.oauth2server.configuration; 2 | 3 | import java.security.KeyPair; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Primary; 9 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 10 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 11 | import org.springframework.security.oauth2.provider.token.TokenStore; 12 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 13 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 14 | import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; 15 | 16 | @Configuration 17 | public class JWTTokenStoreConfig { 18 | 19 | @Autowired 20 | private OAuth2ConfigParameters oAuth2ConfigParameters; 21 | 22 | @Bean 23 | public JwtAccessTokenConverter jwtAccessTokenConverter() { 24 | KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(oAuth2ConfigParameters.getJwt().getKeyStore(), 25 | oAuth2ConfigParameters.getJwt().getKeyStorePassword().toCharArray()); 26 | KeyPair keyPair = keyStoreKeyFactory.getKeyPair(oAuth2ConfigParameters.getJwt().getKeyPairAlias()); 27 | JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); 28 | // jwtAccessTokenConverter.setSigningKey("345345fsdgsf5345"); 29 | jwtAccessTokenConverter.setKeyPair(keyPair); 30 | return jwtAccessTokenConverter; 31 | } 32 | 33 | @Bean 34 | public TokenStore tokenStore() { 35 | return new JwtTokenStore(jwtAccessTokenConverter()); 36 | } 37 | 38 | @Bean 39 | @Primary 40 | public DefaultTokenServices defaultTokenServices() { 41 | DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); 42 | defaultTokenServices.setTokenStore(tokenStore()); 43 | defaultTokenServices.setSupportRefreshToken(true); 44 | return defaultTokenServices; 45 | } 46 | 47 | @Bean 48 | public OAuth2ConfigParameters oAuth2ConfigParameters() { 49 | return new OAuth2ConfigParameters(); 50 | } 51 | 52 | @Bean 53 | public TokenEnhancer tokenEnhancer() { 54 | return new JwtTokenEnhancer(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/github/rshtishi/oauth2server/configuration/JwtTokenEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.oauth2server.configuration; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 7 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 8 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 9 | import org.springframework.security.oauth2.provider.token.TokenEnhancer; 10 | import org.springframework.stereotype.Component; 11 | 12 | 13 | public class JwtTokenEnhancer implements TokenEnhancer { 14 | 15 | @Override 16 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 17 | Map info = new HashMap<>(); 18 | info.put("details", authentication.getDetails()); 19 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); 20 | return accessToken; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/github/rshtishi/oauth2server/configuration/OAuth2ConfigParameters.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.oauth2server.configuration; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.core.io.Resource; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @ConfigurationProperties(prefix = "oauth2", ignoreUnknownFields = false) 12 | public class OAuth2ConfigParameters { 13 | 14 | private Client client; 15 | private Jwt jwt; 16 | 17 | @Getter 18 | @Setter 19 | public static class Client { 20 | private String id; 21 | private String secret; 22 | } 23 | 24 | @Getter 25 | @Setter 26 | public static class Jwt { 27 | private Resource keyStore; 28 | private String keyStorePassword; 29 | private String keyPairAlias; 30 | private String keyPairPassword; 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/github/rshtishi/oauth2server/configuration/WebSecurityConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.oauth2server.configuration; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | 14 | @Configuration 15 | public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { 16 | 17 | @Autowired 18 | DataSource dataSource; 19 | 20 | @Override 21 | @Bean 22 | public AuthenticationManager authenticationManagerBean() throws Exception { 23 | return super.authenticationManagerBean(); 24 | } 25 | 26 | @Override 27 | @Bean 28 | public UserDetailsService userDetailsServiceBean() throws Exception { 29 | return super.userDetailsServiceBean(); 30 | } 31 | 32 | @Bean 33 | public BCryptPasswordEncoder passwordEncoder() { 34 | return new BCryptPasswordEncoder(); 35 | } 36 | 37 | @Override 38 | public void configure(AuthenticationManagerBuilder auth) throws Exception { 39 | // auth.inMemoryAuthentication().withUser("rando").password(passwordEncoder().encode("test")).roles("USER"); 40 | auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder()) 41 | .usersByUsernameQuery("select username,password,enabled from users where username = ?") 42 | .authoritiesByUsernameQuery("select username,authority from authorities where username = ?"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/github/rshtishi/oauth2server/rest/UserRestController.java: -------------------------------------------------------------------------------- 1 | package com.github.rshtishi.oauth2server.rest; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 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 | @RestController 12 | @RequestMapping("/user") 13 | public class UserRestController { 14 | 15 | @GetMapping(produces="application/json") 16 | public Map getUser(OAuth2Authentication user){ 17 | Map userInfo = new HashMap<>(); 18 | userInfo.put("user",user.getUserAuthentication().getPrincipal()); 19 | userInfo.put("authorities", user.getUserAuthentication().getAuthorities()); 20 | return userInfo; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /oauth2-server/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=oauth2 2 | spring.profiles.active=default 3 | cloud.config.uri=http://localhost:8888 4 | -------------------------------------------------------------------------------- /oauth2-server/src/main/resources/db/changelog/01-create-scheme.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /oauth2-server/src/main/resources/db/changelog/02-data-insert.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /oauth2-server/src/main/resources/db/liquibase-changelog.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /oauth2-server/src/main/resources/oauth2cer.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/oauth2-server/src/main/resources/oauth2cer.jks -------------------------------------------------------------------------------- /oauth2-server/src/main/resources/static/images/oauth2-server.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/oauth2-server/src/main/resources/static/images/oauth2-server.jpeg -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.2.5.RELEASE 10 | 11 | 12 | com.github.rshtishi 13 | payroll 14 | 1.0-SNAPSHOT 15 | payroll 16 | Payroll Application 17 | pom 18 | 19 | 20 | 11 21 | Hoxton.SR3 22 | 23 | 24 | 25 | config-server 26 | eureka-server 27 | gateway-server 28 | oauth2-server 29 | employee 30 | department 31 | webapp 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.cloud 47 | spring-cloud-dependencies 48 | ${spring-cloud.version} 49 | pom 50 | import 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /static/images/payroll-architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/static/images/payroll-architecture.jpeg -------------------------------------------------------------------------------- /webapp/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /webapp/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /webapp/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /webapp/README.md: -------------------------------------------------------------------------------- 1 | # WebApp 2 | 3 | *WebApp* provides the user interface for interacting with the payroll application. 4 | 5 | ## Business Case 6 | 7 | *payroll* needs to have a user interface that will allow the user to access the functionalities of the application. The web application that is going to be build need to be: 8 | 9 | - Re-Usable, similar components are greatly encapsulated, or we can say self-sufficient, so we can reuse them on different parts of an app. 10 | - Readable, we can read the code better and reach productivity faster. 11 | - Maintainable, the components that are easily decoupled from each other can be easily replaced with better implementation. So it is easier to maintain and update code within the iterative development workflow 12 | 13 | ## Technology 14 | 15 | - NodeJs [version:v13.9.0] (the JavaScript runtime environment) 16 | - NPM [version:6.13.7] (the package manager for the JavaScript programming language) 17 | - Javascript [version: ES9] (the programming language for building web application) 18 | - Angular [version:10.0.4] (the platform for building the web application) 19 | 20 | ## Architecture 21 | 22 | ## Implementation Details 23 | 24 | 25 | ## Setup 26 | 27 | Prerequisite needed before setup: 28 | 29 | - NodeJs [version:v13.9.0] (should be installed) 30 | - NPM [version:6.13.7] (should be installed) 31 | - payroll [] (all payroll services should be started and running) 32 | 33 | Execute the following command to start *webapp*: 34 | 35 | ``` npm start``` (to run the web app) 36 | 37 | -------------------------------------------------------------------------------- /webapp/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /webapp/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('test app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /webapp/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /webapp/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /webapp/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/test'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --proxy-config proxy.conf.json", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~10.0.5", 15 | "@angular/common": "~10.0.5", 16 | "@angular/compiler": "~10.0.5", 17 | "@angular/core": "~10.0.5", 18 | "@angular/forms": "~10.0.5", 19 | "@angular/localize": "~10.0.5", 20 | "@angular/platform-browser": "~10.0.5", 21 | "@angular/platform-browser-dynamic": "~10.0.5", 22 | "@angular/router": "~10.0.5", 23 | "@progress/kendo-angular-charts": "^4.1.6", 24 | "@progress/kendo-angular-common": "^1.0.0", 25 | "@progress/kendo-angular-grid": "^4.7.3", 26 | "@progress/kendo-angular-intl": "^2.0.0", 27 | "@progress/kendo-angular-l10n": "^2.0.0", 28 | "@progress/kendo-angular-popup": "^3.0.0", 29 | "@progress/kendo-drawing": "^1.0.0", 30 | "@progress/kendo-theme-default": "latest", 31 | "bootstrap": "^4.5.0", 32 | "hammerjs": "^2.0.0", 33 | "ngx-bootstrap": "^5.6.1", 34 | "rxjs": "~6.5.5", 35 | "tslib": "^2.0.0", 36 | "zone.js": "~0.10.3", 37 | "@progress/kendo-angular-buttons": "^5.0.0", 38 | "@progress/kendo-angular-dropdowns": "^4.0.0", 39 | "@progress/kendo-angular-inputs": "^6.0.0", 40 | "@progress/kendo-data-query": "^1.0.0", 41 | "@progress/kendo-angular-excel-export": "^3.0.0", 42 | "@progress/kendo-angular-dateinputs": "^4.0.0", 43 | "@progress/kendo-angular-pdf-export": "^2.0.0" 44 | }, 45 | "devDependencies": { 46 | "@angular-devkit/build-angular": "~0.1000.4", 47 | "@angular/cli": "~10.0.4", 48 | "@angular/compiler-cli": "~10.0.5", 49 | "@types/node": "^12.11.1", 50 | "@types/jasmine": "~3.5.0", 51 | "@types/jasminewd2": "~2.0.3", 52 | "codelyzer": "^6.0.0", 53 | "jasmine-core": "~3.5.0", 54 | "jasmine-spec-reporter": "~5.0.0", 55 | "karma": "~5.0.0", 56 | "karma-chrome-launcher": "~3.1.0", 57 | "karma-coverage-istanbul-reporter": "~3.0.2", 58 | "karma-jasmine": "~3.3.0", 59 | "karma-jasmine-html-reporter": "^1.5.0", 60 | "protractor": "~7.0.0", 61 | "ts-node": "~8.3.0", 62 | "tslint": "~6.1.0", 63 | "typescript": "~3.9.5" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webapp/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.github.rshtishi 7 | payroll 8 | 1.0-SNAPSHOT 9 | 10 | webapp 11 | webapp 12 | pom 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.codehaus.mojo 26 | exec-maven-plugin 27 | 28 | 29 | npm install 30 | 31 | exec 32 | 33 | install 34 | 35 | npm 36 | 37 | install 38 | 39 | 40 | 41 | 42 | npm build 43 | 44 | exec 45 | 46 | install 47 | 48 | npm 49 | 50 | run 51 | build 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /webapp/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "", 4 | "secure": true, 5 | "changeOrigin": true, 6 | "pathRewrite": { 7 | "^/api": "" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webapp/src/app/access-denied/access-denied-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { AccessDeniedComponent } from './access-denied/access-denied.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', component: AccessDeniedComponent 8 | } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forChild(routes)], 13 | exports: [RouterModule] 14 | }) 15 | export class AccessDeniedRoutingModule { 16 | } -------------------------------------------------------------------------------- /webapp/src/app/access-denied/access-denied.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { AccessDeniedRoutingModule } from './access-denied-routing.module'; 4 | import { AccessDeniedComponent } from './access-denied/access-denied.component'; 5 | 6 | 7 | 8 | @NgModule({ 9 | declarations: [AccessDeniedComponent], 10 | imports: [ 11 | CommonModule, 12 | AccessDeniedRoutingModule 13 | ] 14 | }) 15 | export class AccessDeniedModule { } 16 | -------------------------------------------------------------------------------- /webapp/src/app/access-denied/access-denied/access-denied.component.css: -------------------------------------------------------------------------------- 1 | .access-denied { 2 | margin-top: 10em; 3 | text-align: center; 4 | } 5 | 6 | .app-logo { 7 | margin-top: 50px; 8 | } -------------------------------------------------------------------------------- /webapp/src/app/access-denied/access-denied/access-denied.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Oops!

5 |

403 Access Denied

6 |
7 | Access to the requested page has been denied! 8 |
9 |
10 | 11 |
-------------------------------------------------------------------------------- /webapp/src/app/access-denied/access-denied/access-denied.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AccessDeniedComponent } from './access-denied.component'; 4 | 5 | describe('AccessDeniedComponent', () => { 6 | let component: AccessDeniedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AccessDeniedComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AccessDeniedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/access-denied/access-denied/access-denied.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-access-denied', 5 | templateUrl: './access-denied.component.html', 6 | styleUrls: ['./access-denied.component.css'] 7 | }) 8 | export class AccessDeniedComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /webapp/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { AuthGuard } from './shared/guards/auth.guard'; 4 | 5 | const routes: Routes = [ 6 | { path: '', loadChildren: './layout/layout.module#LayoutModule',canActivate: [AuthGuard]}, 7 | { path: 'login', loadChildren: './login/login.module#LoginModule' }, 8 | { path: 'access-denied', loadChildren: './access-denied/access-denied.module#AccessDeniedModule' }, 9 | { path: 'not-found', loadChildren: './not-found/not-found.module#NotFoundModule' }, 10 | { path: '**', redirectTo: 'not-found' } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class AppRoutingModule { 18 | } -------------------------------------------------------------------------------- /webapp/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/app.component.css -------------------------------------------------------------------------------- /webapp/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'test'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app.title).toEqual('test'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.nativeElement; 29 | expect(compiled.querySelector('.content span').textContent).toContain('test app is running!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /webapp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'test'; 10 | } 11 | -------------------------------------------------------------------------------- /webapp/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { AppRoutingModule } from './app-routing.module'; 6 | import { LoginModule } from './login/login.module'; 7 | import { AccessDeniedModule } from './access-denied/access-denied.module'; 8 | import { NotFoundModule } from './not-found/not-found.module'; 9 | import { LayoutModule } from './layout/layout.module'; 10 | import { AuthService } from './shared/service/auth.service'; 11 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 12 | import { AuthGuard } from './shared/guards/auth.guard'; 13 | import { ChartsModule } from '@progress/kendo-angular-charts'; 14 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 15 | import { AuthorizationInterceptor } from './shared/interceptor/auth-interceptor.service'; 16 | import { DepartmentService } from './shared/service/department.service'; 17 | import { EmployeeService } from './shared/service/employee.service'; 18 | import { GridModule } from '@progress/kendo-angular-grid'; 19 | 20 | 21 | 22 | 23 | 24 | @NgModule({ 25 | declarations: [ 26 | AppComponent 27 | ], 28 | imports: [ 29 | BrowserModule, 30 | AppRoutingModule, 31 | LoginModule, 32 | AccessDeniedModule, 33 | NotFoundModule, 34 | LayoutModule, 35 | HttpClientModule, 36 | ChartsModule, 37 | BrowserAnimationsModule, 38 | GridModule 39 | ], 40 | providers: [AuthService, AuthGuard, DepartmentService, EmployeeService, 41 | { provide: HTTP_INTERCEPTORS, useClass: AuthorizationInterceptor, multi: true } 42 | ], 43 | bootstrap: [AppComponent] 44 | }) 45 | export class AppModule { } 46 | -------------------------------------------------------------------------------- /webapp/src/app/app.settings.ts: -------------------------------------------------------------------------------- 1 | export class AppSettings { 2 | 3 | public static APP_NAME = 'payroll'; 4 | 5 | public static SERVER_URL = 'https://localhost:5555'; 6 | 7 | public static API_ENDPOINT = '/payroll'; 8 | 9 | public static LOGIN_ENDPOINT = AppSettings.SERVER_URL + AppSettings.API_ENDPOINT + '/oauth2/oauth/token'; 10 | 11 | public static USER_ENDPOINT = AppSettings.SERVER_URL + AppSettings.API_ENDPOINT + '/oauth2/user'; 12 | 13 | public static ACCESS_TOKEN = 'access_token'; 14 | 15 | public static CURRENT_USER = 'current_user'; 16 | 17 | public static DEPARTMENT_ENDPOINT = AppSettings.SERVER_URL + AppSettings.API_ENDPOINT + "/department/departments"; 18 | 19 | public static EMPLOYEE_ENDPOINT = AppSettings.SERVER_URL + AppSettings.API_ENDPOINT + "/employee/employees"; 20 | 21 | public static AUTHORIZATION_HEADER_NAME = 'Authorization'; 22 | } -------------------------------------------------------------------------------- /webapp/src/app/layout/dashboard/dashboard-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { Routes, RouterModule } from '@angular/router'; 4 | import { DashboardComponent } from './dashboard/dashboard.component'; 5 | 6 | const routes:Routes=[ 7 | { 8 | path:'', 9 | component:DashboardComponent 10 | } 11 | ] 12 | @NgModule({ 13 | 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class DashboardRoutingModule { } -------------------------------------------------------------------------------- /webapp/src/app/layout/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { DashboardComponent } from './dashboard/dashboard.component'; 4 | import { DashboardRoutingModule } from './dashboard-routing.module'; 5 | import { ChartsModule } from '@progress/kendo-angular-charts'; 6 | 7 | 8 | 9 | @NgModule({ 10 | declarations: [DashboardComponent], 11 | imports: [ 12 | CommonModule, 13 | DashboardRoutingModule, 14 | ChartsModule 15 | ] 16 | }) 17 | export class DashboardModule { } 18 | -------------------------------------------------------------------------------- /webapp/src/app/layout/dashboard/dashboard/dashboard.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/dashboard/dashboard/dashboard.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/dashboard/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Dashboard

3 |
4 |
5 | 6 | 7 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/dashboard/dashboard/dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DashboardComponent } from './dashboard.component'; 4 | 5 | describe('DashboardComponent', () => { 6 | let component: DashboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DashboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DashboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/dashboard/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DepartmentService } from '../../../shared/service/department.service'; 3 | import 'hammerjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { Department } from '../../../shared/model/department.model'; 6 | 7 | @Component({ 8 | selector: 'app-dashboard', 9 | templateUrl: './dashboard.component.html', 10 | styleUrls: ['./dashboard.component.css'] 11 | }) 12 | export class DashboardComponent implements OnInit { 13 | 14 | public chartTitle: any = { text: 'Payroll Chart' }; 15 | public departments: Department[]; 16 | 17 | constructor(private departmentService: DepartmentService) { } 18 | 19 | ngOnInit(): void { 20 | this.departmentService.fetchAll().subscribe(result => { 21 | this.departments=result['content']; 22 | }); 23 | } 24 | 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-add/department-add.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/department/department-add/department-add.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-add/department-add.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Department Add Form

3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 | 12 |
13 |
Name is Required
14 |
15 |
16 |
17 |
18 |
19 | 22 |
23 |
24 |
25 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-add/department-add.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DepartmentAddComponent } from './department-add.component'; 4 | 5 | describe('DepartmentAddComponent', () => { 6 | let component: DepartmentAddComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DepartmentAddComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DepartmentAddComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-add/department-add.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { Department } from '../../../shared/model/department.model'; 4 | import { DepartmentService } from '../../../shared/service/department.service'; 5 | import { Router } from '@angular/router'; 6 | import { Location } from '@angular/common'; 7 | 8 | @Component({ 9 | selector: 'app-department-add', 10 | templateUrl: './department-add.component.html', 11 | styleUrls: ['./department-add.component.css'] 12 | }) 13 | export class DepartmentAddComponent implements OnInit { 14 | 15 | public addDepartmentForm: FormGroup; 16 | submitted = false; 17 | 18 | constructor(private _formBuilder: FormBuilder, 19 | private _router: Router, 20 | private _departmentService: DepartmentService, 21 | private _location: Location) { 22 | } 23 | 24 | ngOnInit(): void { 25 | this.createForm(); 26 | } 27 | 28 | private createForm() { 29 | this.addDepartmentForm = this._formBuilder.group({ 30 | name: ['', Validators.required] 31 | }); 32 | } 33 | 34 | get inputFieldValue() { 35 | return this.addDepartmentForm.controls; 36 | } 37 | 38 | public submit() { 39 | this.submitted = true; 40 | if (this.addDepartmentForm.invalid) { 41 | return; 42 | } 43 | this._departmentService.save(this.addDepartmentForm.value).subscribe(result => { 44 | this._router.navigate(['department']); 45 | }); 46 | } 47 | 48 | cancel() { 49 | this.addDepartmentForm.reset(); 50 | } 51 | 52 | back() { 53 | this._location.back(); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-edit/department-edit.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/department/department-edit/department-edit.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-edit/department-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Department Edit Form

3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
Name is Required
18 |
19 |
20 |
21 |
22 |
23 | 26 |
27 |
28 |
29 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-edit/department-edit.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DepartmentEditComponent } from './department-edit.component'; 4 | 5 | describe('DepartmentEditComponent', () => { 6 | let component: DepartmentEditComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DepartmentEditComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DepartmentEditComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-edit/department-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { DepartmentService } from '../../../shared/service/department.service'; 5 | import { Location } from '@angular/common'; 6 | import { Subject } from 'rxjs'; 7 | import { takeUntil } from 'rxjs/operators'; 8 | import { Department } from '../../../shared/model/department.model'; 9 | 10 | @Component({ 11 | selector: 'app-department-edit', 12 | templateUrl: './department-edit.component.html', 13 | styleUrls: ['./department-edit.component.css'] 14 | }) 15 | export class DepartmentEditComponent implements OnInit { 16 | 17 | public editDepartmentForm: FormGroup; 18 | submitted = false; 19 | private _$alive = new Subject(); 20 | 21 | 22 | constructor(private _formBuilder: FormBuilder, 23 | private _router: Router, 24 | private _activatedRoute: ActivatedRoute, 25 | private _departmentService: DepartmentService, 26 | private _location: Location) { 27 | } 28 | 29 | ngOnInit(): void { 30 | this.createForm(); 31 | this._activatedRoute.params.pipe(takeUntil(this._$alive)).subscribe(params =>{ 32 | let id = params['id']; 33 | this._departmentService.fetchById(id).subscribe(result =>{ 34 | this.editDepartmentForm.get('id').setValue(id); 35 | this.editDepartmentForm.get('name').setValue(result['name']); 36 | }); 37 | }); 38 | 39 | } 40 | 41 | private createForm() { 42 | this.editDepartmentForm = this._formBuilder.group({ 43 | id: [''], 44 | name: ['', Validators.required] 45 | }); 46 | } 47 | 48 | get inputFieldValue() { 49 | return this.editDepartmentForm.controls; 50 | } 51 | 52 | public submit() { 53 | this.submitted = true; 54 | if (this.editDepartmentForm.invalid) { 55 | return; 56 | } 57 | this._departmentService.update(this.editDepartmentForm.value).subscribe(result =>{ 58 | this._router.navigate(['department']); 59 | }); 60 | } 61 | 62 | cancel() { 63 | this.editDepartmentForm.get('name').reset(); 64 | } 65 | 66 | back() { 67 | this._location.back(); 68 | } 69 | 70 | public ngOnDestroy(): void { 71 | this._$alive.next(); 72 | this._$alive.complete(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { Routes, RouterModule } from '@angular/router'; 4 | import { DepartmentComponent } from './department/department.component'; 5 | import { DepartmentViewComponent } from './department-view/department-view.component'; 6 | import { DepartmentAddComponent } from './department-add/department-add.component'; 7 | import { DepartmentEditComponent } from './department-edit/department-edit.component'; 8 | 9 | const routes:Routes=[ 10 | { 11 | path:'', 12 | component:DepartmentComponent 13 | }, 14 | { 15 | path:'new', 16 | component:DepartmentAddComponent 17 | }, 18 | { 19 | path:':id/view', 20 | component:DepartmentViewComponent 21 | }, 22 | { 23 | path:':id/edit', 24 | component:DepartmentEditComponent 25 | } 26 | ] 27 | @NgModule({ 28 | 29 | imports: [RouterModule.forChild(routes)], 30 | exports: [RouterModule] 31 | }) 32 | export class DepartmentRoutingModule { } -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-view/department-view.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/department/department-view/department-view.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-view/department-view.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Department Detail

3 |
4 |
5 |
6 |
7 |
8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
10 | 11 |
Id:{{department?.id}}
Name{{department?.name}}
Number of Employees:{{department?.noOfEmployees}}
27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-view/department-view.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DepartmentViewComponent } from './department-view.component'; 4 | 5 | describe('DepartmentViewComponent', () => { 6 | let component: DepartmentViewComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DepartmentViewComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DepartmentViewComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department-view/department-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | import { DepartmentService } from '../../../shared/service/department.service'; 5 | import { takeUntil } from 'rxjs/operators'; 6 | import { Department} from '../../../shared/model/department.model'; 7 | import { Location } from '@angular/common'; 8 | 9 | @Component({ 10 | selector: 'app-department-view', 11 | templateUrl: './department-view.component.html', 12 | styleUrls: ['./department-view.component.css'] 13 | }) 14 | export class DepartmentViewComponent implements OnInit { 15 | 16 | private _$alive = new Subject(); 17 | public department:Department; 18 | 19 | constructor( 20 | private _router: Router, 21 | private _activatedRoute: ActivatedRoute, 22 | private _location:Location, 23 | private _departmentService: DepartmentService) { } 24 | 25 | ngOnInit(): void { 26 | this._activatedRoute.params.pipe(takeUntil(this._$alive)).subscribe(params => { 27 | let id = params['id']; 28 | this._departmentService.fetchById(id).pipe(takeUntil(this._$alive)).subscribe(result =>{ 29 | this.department = result; 30 | }); 31 | }); 32 | } 33 | 34 | public back(){ 35 | this._location.back(); 36 | } 37 | 38 | public ngOnDestroy(): void { 39 | this._$alive.next(); 40 | this._$alive.complete(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { DepartmentComponent } from './department/department.component'; 4 | import { DepartmentRoutingModule } from './department-routing.module'; 5 | import { GridModule } from '@progress/kendo-angular-grid'; 6 | import { DepartmentBindingDirective } from './directive/department-binding.directive'; 7 | import { DepartmentViewComponent } from './department-view/department-view.component'; 8 | import { DepartmentAddComponent } from './department-add/department-add.component'; 9 | import { ReactiveFormsModule } from '@angular/forms'; 10 | import { DepartmentEditComponent } from './department-edit/department-edit.component'; 11 | 12 | 13 | 14 | @NgModule({ 15 | declarations: [DepartmentComponent, DepartmentBindingDirective, DepartmentViewComponent, DepartmentAddComponent, DepartmentEditComponent], 16 | imports: [ 17 | CommonModule, 18 | DepartmentRoutingModule, 19 | GridModule, 20 | ReactiveFormsModule 21 | ] 22 | }) 23 | export class DepartmentModule { } 24 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department/department.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/department/department/department.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department/department.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Department

3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 25 | 26 | 27 | 28 | 29 | 30 | View | 31 | Edit | 32 | Delete 33 | 34 | 35 | 36 |
37 |
38 |
39 | 40 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/department/department/department.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DepartmentComponent } from './department.component'; 4 | 5 | describe('DepartmentComponent', () => { 6 | let component: DepartmentComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DepartmentComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DepartmentComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/department/department.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DepartmentService } from '../../../shared/service/department.service'; 3 | import { Router } from '@angular/router'; 4 | import { state } from '@angular/animations'; 5 | 6 | @Component({ 7 | selector: 'app-department', 8 | templateUrl: './department.component.html', 9 | styleUrls: ['./department.component.css'] 10 | }) 11 | export class DepartmentComponent implements OnInit { 12 | 13 | constructor(private _router: Router, 14 | private departmentService: DepartmentService) { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | newBtnClick() { 20 | this._router.navigate(['department','new']); 21 | } 22 | 23 | delete(id:string){ 24 | this.departmentService.delete(id).subscribe(result=>{ 25 | this.departmentService.query({}); 26 | }); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /webapp/src/app/layout/department/directive/department-binding.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, OnInit, OnDestroy } from "@angular/core"; 2 | import { DataBindingDirective, GridComponent, GridDataResult } from "@progress/kendo-angular-grid"; 3 | import { DepartmentService } from "../../../shared/service/department.service"; 4 | import { takeUntil } from 'rxjs/operators'; 5 | import { Subject, Observable } from "rxjs"; 6 | import { State } from "@progress/kendo-data-query"; 7 | 8 | @Directive({ 9 | selector: '[departmentBinding]' 10 | }) 11 | export class DepartmentBindingDirective extends DataBindingDirective implements OnInit, OnDestroy { 12 | 13 | private _$alives = new Subject(); 14 | 15 | constructor(grid: GridComponent, 16 | private departmentService: DepartmentService) { 17 | super(grid); 18 | } 19 | 20 | public ngOnInit(): void { 21 | this.departmentService.pipe(takeUntil(this._$alives)).subscribe( 22 | (result) => { 23 | this.grid.loading = false; 24 | this.grid.data = result; 25 | this.notifyDataChange(); 26 | } 27 | ); 28 | super.ngOnInit(); 29 | this.rebind(); 30 | } 31 | 32 | public ngOnDestroy(): void { 33 | this._$alives.next(); 34 | this._$alives.complete(); 35 | super.ngOnDestroy(); 36 | } 37 | 38 | public rebind(): void { 39 | this.grid.loading = true; 40 | this.departmentService.query(this.state); 41 | } 42 | 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/directive/employee-binding.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, OnInit, OnDestroy } from "@angular/core"; 2 | import { DataBindingDirective, GridComponent, GridDataResult } from "@progress/kendo-angular-grid"; 3 | import { takeUntil } from 'rxjs/operators'; 4 | import { Subject, Observable } from "rxjs"; 5 | import { State } from "@progress/kendo-data-query"; 6 | import { EmployeeService } from "../../../shared/service/employee.service"; 7 | 8 | @Directive({ 9 | selector: '[employeeBinding]' 10 | }) 11 | export class EmployeeBindingDirective extends DataBindingDirective implements OnInit, OnDestroy { 12 | 13 | private _$alives = new Subject(); 14 | 15 | constructor(grid: GridComponent, 16 | private employeeService: EmployeeService) { 17 | super(grid); 18 | } 19 | 20 | public ngOnInit(): void { 21 | this.employeeService.pipe(takeUntil(this._$alives)).subscribe( 22 | (result) => { 23 | this.grid.loading = false; 24 | this.grid.data = result; 25 | this.notifyDataChange(); 26 | } 27 | ); 28 | super.ngOnInit(); 29 | this.rebind(); 30 | } 31 | 32 | public ngOnDestroy(): void { 33 | this._$alives.next(); 34 | this._$alives.complete(); 35 | super.ngOnDestroy(); 36 | } 37 | 38 | public rebind(): void { 39 | this.grid.loading = true; 40 | this.employeeService.query(this.state); 41 | } 42 | 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-add/employee-add.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/employee/employee-add/employee-add.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-add/employee-add.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmployeeAddComponent } from './employee-add.component'; 4 | 5 | describe('EmployeeAddComponent', () => { 6 | let component: EmployeeAddComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EmployeeAddComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmployeeAddComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-add/employee-add.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { Subject } from 'rxjs'; 4 | import { Router } from '@angular/router'; 5 | import { EmployeeService } from '../../../shared/service/employee.service'; 6 | import { Location } from '@angular/common'; 7 | import { DepartmentService } from '../../../shared/service/department.service'; 8 | import { Department } from '../../../shared/model/department.model'; 9 | 10 | @Component({ 11 | selector: 'app-employee-add', 12 | templateUrl: './employee-add.component.html', 13 | styleUrls: ['./employee-add.component.css'] 14 | }) 15 | export class EmployeeAddComponent implements OnInit { 16 | 17 | public addEmployeeForm: FormGroup; 18 | submitted = false; 19 | private _$alive = new Subject(); 20 | public departments: Department[]; 21 | 22 | constructor(private _formBuilder: FormBuilder, 23 | private _router: Router, 24 | private _employeeService: EmployeeService, 25 | private _departmentService: DepartmentService, 26 | private _location: Location) { } 27 | 28 | ngOnInit(): void { 29 | this.createForm(); 30 | this._departmentService.fetchAll().subscribe(result => { 31 | this.departments = result["content"]; 32 | }); 33 | } 34 | 35 | 36 | private createForm() { 37 | this.addEmployeeForm = this._formBuilder.group({ 38 | firstname: ['', Validators.required], 39 | lastname: ['', Validators.required], 40 | address: ['', Validators.required], 41 | phone: ['', Validators.required], 42 | departmentId: ['', Validators.required] 43 | 44 | }); 45 | } 46 | 47 | get inputFieldValue() { 48 | return this.addEmployeeForm.controls; 49 | } 50 | 51 | public submit() { 52 | this.submitted = true; 53 | if (this.addEmployeeForm.invalid) { 54 | return; 55 | } 56 | this._employeeService.save(this.addEmployeeForm.value).subscribe(result => { 57 | this._router.navigate(['employee']); 58 | }); 59 | } 60 | 61 | cancel() { 62 | this.addEmployeeForm.reset(); 63 | } 64 | 65 | back() { 66 | this._location.back(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-edit/employee-edit.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/employee/employee-edit/employee-edit.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-edit/employee-edit.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmployeeEditComponent } from './employee-edit.component'; 4 | 5 | describe('EmployeeEditComponent', () => { 6 | let component: EmployeeEditComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EmployeeEditComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmployeeEditComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { Routes, RouterModule } from '@angular/router'; 4 | import { EmployeeComponent } from './employee/employee.component'; 5 | import { EmployeeViewComponent } from './employee-view/employee-view.component'; 6 | import { EmployeeAddComponent } from './employee-add/employee-add.component'; 7 | import { EmployeeEditComponent } from './employee-edit/employee-edit.component'; 8 | 9 | const routes:Routes=[ 10 | { 11 | path:'', 12 | component:EmployeeComponent 13 | }, 14 | { 15 | path:'new', 16 | component:EmployeeAddComponent 17 | }, 18 | { 19 | path:':id/view', 20 | component:EmployeeViewComponent 21 | }, 22 | { 23 | path:':id/edit', 24 | component:EmployeeEditComponent 25 | } 26 | ] 27 | @NgModule({ 28 | 29 | imports: [RouterModule.forChild(routes)], 30 | exports: [RouterModule] 31 | }) 32 | export class EmployeeRoutingModule { } -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-view/employee-view.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/employee/employee-view/employee-view.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-view/employee-view.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Employee Detail

3 |
4 |
5 |
6 |
7 |
8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
10 | 11 |
Id:{{employee?.id}}
Firstname:{{employee?.firstname}}
Lastname:{{employee?.lastname}}
Address:{{employee?.address}}
Phone:{{employee?.phone}}
35 |
36 |
37 |
38 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-view/employee-view.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmployeeViewComponent } from './employee-view.component'; 4 | 5 | describe('EmployeeViewComponent', () => { 6 | let component: EmployeeViewComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EmployeeViewComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmployeeViewComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee-view/employee-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { Location } from '@angular/common'; 3 | import { EmployeeService } from '../../../shared/service/employee.service'; 4 | import { Subject } from 'rxjs'; 5 | import { Router, ActivatedRoute } from '@angular/router'; 6 | import { takeUntil } from 'rxjs/operators'; 7 | import { Employee} from '../../../shared/model/employee.model'; 8 | 9 | @Component({ 10 | selector: 'app-employee-view', 11 | templateUrl: './employee-view.component.html', 12 | styleUrls: ['./employee-view.component.css'] 13 | }) 14 | export class EmployeeViewComponent implements OnInit, OnDestroy { 15 | 16 | private _$alive = new Subject(); 17 | public employee:Employee; 18 | 19 | constructor(private _router: Router, 20 | private _activatedRoute: ActivatedRoute, 21 | private _location: Location, 22 | private _employeeService: EmployeeService) { } 23 | 24 | ngOnInit(): void { 25 | this._activatedRoute.params.pipe(takeUntil(this._$alive)).subscribe(params => { 26 | let id = params['id']; 27 | this._employeeService.fetchById(id).subscribe(result => this.employee=result); 28 | }); 29 | } 30 | 31 | public back() { 32 | this._location.back(); 33 | } 34 | 35 | ngOnDestroy() { 36 | this._$alive.next(); 37 | this._$alive.complete(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { EmployeeComponent } from './employee/employee.component'; 4 | import { EmployeeRoutingModule } from './employee-routing.module'; 5 | import { GridModule } from '@progress/kendo-angular-grid'; 6 | import { EmployeeBindingDirective } from './directive/employee-binding.directive'; 7 | import { EmployeeViewComponent } from './employee-view/employee-view.component'; 8 | import { EmployeeAddComponent } from './employee-add/employee-add.component'; 9 | import { EmployeeEditComponent } from './employee-edit/employee-edit.component'; 10 | import { ReactiveFormsModule } from '@angular/forms'; 11 | 12 | 13 | 14 | @NgModule({ 15 | declarations: [EmployeeComponent, EmployeeBindingDirective, EmployeeViewComponent, EmployeeAddComponent, EmployeeEditComponent], 16 | imports: [ 17 | CommonModule, 18 | EmployeeRoutingModule, 19 | GridModule, 20 | ReactiveFormsModule 21 | 22 | ] 23 | }) 24 | export class EmployeeModule { } 25 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee/employee.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/app/layout/employee/employee/employee.component.css -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee/employee.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Employee

3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | View | 33 | Edit | 34 | Delete 35 | 36 | 37 | 38 |
39 |
40 |
41 | 42 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee/employee.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmployeeComponent } from './employee.component'; 4 | 5 | describe('EmployeeComponent', () => { 6 | let component: EmployeeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EmployeeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmployeeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/employee/employee/employee.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { EmployeeService } from '../../../shared/service/employee.service'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-employee', 7 | templateUrl: './employee.component.html', 8 | styleUrls: ['./employee.component.css'] 9 | }) 10 | export class EmployeeComponent implements OnInit { 11 | 12 | constructor(private _router: Router, 13 | private _employeeService: EmployeeService) { } 14 | 15 | ngOnInit(): void { 16 | } 17 | 18 | public goToNewEmployeView() { 19 | this._router.navigate(['employee','new']); 20 | } 21 | 22 | public delete(id:string){ 23 | this._employeeService.delete(id).subscribe(result => { 24 | this._employeeService.query({}); 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { LayoutComponent } from './layout/layout.component'; 4 | 5 | 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: LayoutComponent, 11 | children: [ 12 | { path: '', redirectTo: 'dashboard', pathMatch: 'prefix' }, 13 | { path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardModule' }, 14 | { path: 'department', loadChildren: './department/department.module#DepartmentModule' }, 15 | { path: 'employee', loadChildren: './employee/employee.module#EmployeeModule' } 16 | ] 17 | } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [RouterModule.forChild(routes)], 22 | exports: [RouterModule] 23 | }) 24 | export class LayoutRoutingModule { 25 | } -------------------------------------------------------------------------------- /webapp/src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LayoutComponent } from './layout/layout.component'; 4 | import { LayoutRoutingModule } from './layout-routing.module'; 5 | import { DashboardModule } from './dashboard/dashboard.module'; 6 | import { DepartmentModule } from './department/department.module'; 7 | import { EmployeeModule } from './employee/employee.module'; 8 | import { HeaderComponent } from './layout/template/header/header.component'; 9 | import { SidebarComponent } from './layout/template/sidebar/sidebar.component'; 10 | 11 | 12 | 13 | @NgModule({ 14 | declarations: [LayoutComponent, HeaderComponent, SidebarComponent], 15 | imports: [ 16 | CommonModule, 17 | LayoutRoutingModule, 18 | DashboardModule, 19 | DepartmentModule, 20 | EmployeeModule 21 | ] 22 | }) 23 | export class LayoutModule { } 24 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/layout.component.css: -------------------------------------------------------------------------------- 1 | .main-container { 2 | margin-top: 56px; 3 | margin-left: 235px; 4 | padding: 15px; 5 | -ms-overflow-x: hidden; 6 | position: relative; 7 | overflow: hidden; 8 | transition: all 0.2s ease-in-out; 9 | } 10 | @media screen and (max-width: 992px) { 11 | .main-container { 12 | margin-left: 0 !important; 13 | } 14 | } 15 | @media print { 16 | .main-container { 17 | margin-top: 0 !important; 18 | margin-left: 0 !important; 19 | } 20 | } 21 | .compressed-sidebar { 22 | margin-left: 4rem; 23 | } 24 | .nav-item { 25 | font-size: 20px; 26 | } 27 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /webapp/src/app/layout/layout/layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LayoutComponent } from './layout.component'; 4 | 5 | describe('LayoutComponent', () => { 6 | let component: LayoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LayoutComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LayoutComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-layout', 5 | templateUrl: './layout.component.html', 6 | styleUrls: ['./layout.component.css'] 7 | }) 8 | export class LayoutComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/header/header.component.css: -------------------------------------------------------------------------------- 1 | :host .navbar { 2 | padding-left: 0; 3 | height: 56px; 4 | background-color: #456191; 5 | } 6 | :host .navbar .navbar-nav-brend { 7 | width: 235px; 8 | } 9 | :host .navbar .navbar-brand { 10 | color: #fff; 11 | } 12 | :host .search-bar { 13 | position: absolute; 14 | top: 8px; 15 | right: 16px; 16 | font-size: 1rem; 17 | } 18 | :host .search-bar .search-box { 19 | border-radius: 20px; 20 | border: 2px solid #f0ead8; 21 | } 22 | @media screen and (max-width: 992px) { 23 | :host .navbar { 24 | padding-left: 1rem; 25 | } 26 | :host .navbar .navbar-nav-brend { 27 | width: auto; 28 | } 29 | :host .navbar .navbar-brand { 30 | padding-left: 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/header/header.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AppSettings } from '../../../../app.settings'; 3 | 4 | @Component({ 5 | selector: 'app-header', 6 | templateUrl: './header.component.html', 7 | styleUrls: ['./header.component.css'] 8 | }) 9 | export class HeaderComponent implements OnInit { 10 | 11 | appName: string = AppSettings.APP_NAME; 12 | 13 | constructor() { } 14 | 15 | ngOnInit(): void { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/sidebar/sidebar.component.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: fixed; 3 | z-index: 1000; 4 | top: 56px; 5 | left: 235px; 6 | width: 235px; 7 | margin-left: -235px; 8 | overflow-y: auto; 9 | background-color: #e9ecef; 10 | bottom: 0; 11 | overflow-x: hidden; 12 | padding-bottom: 40px; 13 | padding-top: 12px; 14 | font-size: 1rem; 15 | transition: all 0.2s ease-in-out; 16 | border-left: none; 17 | border-right: 1px solid #ddd; 18 | border-top: 1px solid rgba(255, 255, 255, 0.3); 19 | border-radius: 0; 20 | } 21 | 22 | .sidebar.compressed-sidebar { 23 | width: 4rem; 24 | } 25 | 26 | @media screen and (max-width: 992px) { 27 | .sidebar { 28 | left: 0; 29 | } 30 | .sidebar.compressed-sidebar { 31 | width: 235px; 32 | } 33 | } 34 | 35 | :host .navbar { 36 | padding-left: 0; 37 | height: 56px; 38 | background-color: #456191; 39 | } 40 | 41 | :host .navbar .navbar-nav-brend { 42 | width: 235px; 43 | } 44 | 45 | :host .navbar .navbar-brand { 46 | color: #fff; 47 | } 48 | 49 | :host .search-bar { 50 | position: absolute; 51 | top: 8px; 52 | right: 16px; 53 | font-size: 1rem; 54 | } 55 | 56 | :host .search-bar .search-box { 57 | border-radius: 20px; 58 | border: 2px solid #f0ead8; 59 | } 60 | 61 | @media screen and (max-width: 992px) { 62 | :host .navbar { 63 | padding-left: 1rem; 64 | } 65 | :host .navbar .navbar-nav-brend { 66 | width: auto; 67 | } 68 | :host .navbar .navbar-brand { 69 | padding-left: 0; 70 | } 71 | } -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/sidebar/sidebar.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/sidebar/sidebar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SidebarComponent } from './sidebar.component'; 4 | 5 | describe('SidebarComponent', () => { 6 | let component: SidebarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SidebarComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SidebarComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/layout/layout/template/sidebar/sidebar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AuthService } from '../../../../shared/service/auth.service'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-sidebar', 7 | templateUrl: './sidebar.component.html', 8 | styleUrls: ['./sidebar.component.css'] 9 | }) 10 | export class SidebarComponent implements OnInit { 11 | 12 | constructor(private authService: AuthService, private router: Router) { } 13 | 14 | ngOnInit(): void { 15 | } 16 | 17 | public logout() { 18 | this.authService.logOut(); 19 | this.router.navigate(["/login"]); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /webapp/src/app/login/login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { LoginComponent } from './login/login.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: LoginComponent 9 | } 10 | ]; 11 | 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class LoginRoutingModule { 18 | } -------------------------------------------------------------------------------- /webapp/src/app/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoginRoutingModule } from './login-routing.module'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { LoginComponent } from './login/login.component'; 6 | 7 | @NgModule({ 8 | declarations: [LoginComponent], 9 | imports: [ 10 | CommonModule, 11 | LoginRoutingModule, 12 | FormsModule 13 | ] 14 | }) 15 | export class LoginModule { } 16 | -------------------------------------------------------------------------------- /webapp/src/app/login/login/login.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .login-page { 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | right: 0; 10 | bottom: 0; 11 | overflow: auto; 12 | background: #f5f7fa; 13 | text-align: center; 14 | color: #3a3f51; 15 | padding: 3em; 16 | } 17 | 18 | .col-lg-4 { 19 | padding: 0; 20 | } 21 | 22 | .input-lg { 23 | height: 46px; 24 | padding: 10px 16px; 25 | font-size: 18px; 26 | line-height: 1.3333333; 27 | border-radius: 0; 28 | } 29 | 30 | .input-underline { 31 | background: 0 0; 32 | border: none; 33 | box-shadow: none; 34 | border-bottom: 1px solid #3a3f51; 35 | border-radius: 0; 36 | } 37 | 38 | .input-underline:focus { 39 | border-bottom: 2px solid #3a3f51; 40 | box-shadow: none; 41 | } 42 | 43 | .rounded-btn { 44 | -webkit-border-radius: 50px; 45 | border-radius: 50px; 46 | font-size: 18px; 47 | line-height: 40px; 48 | padding: 0 25px; 49 | } 50 | 51 | h1 { 52 | font-weight: 300; 53 | margin-top: 20px; 54 | margin-bottom: 10px; 55 | font-size: 36px; 56 | } 57 | 58 | small { 59 | color: #3a3f51; 60 | } 61 | 62 | .form-group { 63 | padding: 8px 0; 64 | } 65 | 66 | input::-webkit-input-placeholder { 67 | color: #3a3f51 !important; 68 | } 69 | 70 | input:-moz-placeholder { 71 | /* Firefox 18- */ 72 | color: #3a3f51 !important; 73 | } 74 | 75 | input::-moz-placeholder { 76 | /* Firefox 19+ */ 77 | color: #3a3f51 !important; 78 | } 79 | 80 | input:-ms-input-placeholder { 81 | color: #3a3f51 !important; 82 | } 83 | 84 | .form-content { 85 | padding: 40px 0; 86 | } 87 | 88 | .user-avatar { 89 | -webkit-border-radius: 50%; 90 | border-radius: 50%; 91 | } 92 | 93 | .ng-valid[required], .ng-valid.required { 94 | border-left: 5px solid #42A948; 95 | /* green */ 96 | } 97 | 98 | .ng-invalid:not(form).ng-touched { 99 | border-left: 5px solid #a94442; 100 | /* red */ 101 | } -------------------------------------------------------------------------------- /webapp/src/app/login/login/login.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/src/app/login/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/login/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 3 | import { LoggingUser } from '../../shared/model/logging-user'; 4 | import { AuthService } from '../../shared/service/auth.service'; 5 | import { AppSettings } from '../../app.settings'; 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.css'] 11 | }) 12 | export class LoginComponent implements OnInit { 13 | 14 | loggingUser = new LoggingUser(); 15 | appName = "payroll"; 16 | 17 | constructor( private router: Router, 18 | private activatedRoute: ActivatedRoute, 19 | private authService:AuthService) { } 20 | 21 | ngOnInit(): void { 22 | } 23 | 24 | login() { 25 | this.authService.authenticate(this.loggingUser.username,this.loggingUser.password).subscribe(response =>{ 26 | localStorage.setItem(AppSettings.ACCESS_TOKEN,response[AppSettings.ACCESS_TOKEN]); 27 | this.authService.retrieveUser().subscribe(user =>{ 28 | localStorage.setItem(AppSettings.CURRENT_USER,JSON.stringify(user)); 29 | if(user){ 30 | this.router.navigate(["/"]); 31 | } 32 | }); 33 | }); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /webapp/src/app/not-found/not-found-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { NotFoundComponent } from './not-found/not-found.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', component: NotFoundComponent 8 | } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forChild(routes)], 13 | exports: [RouterModule] 14 | }) 15 | export class NotFoundRoutingModule { 16 | } -------------------------------------------------------------------------------- /webapp/src/app/not-found/not-found.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NotFoundComponent } from './not-found/not-found.component'; 4 | import { NotFoundRoutingModule } from './not-found-routing.module'; 5 | 6 | 7 | 8 | @NgModule({ 9 | declarations: [NotFoundComponent], 10 | imports: [ 11 | CommonModule, 12 | NotFoundRoutingModule 13 | ] 14 | }) 15 | export class NotFoundModule { } 16 | -------------------------------------------------------------------------------- /webapp/src/app/not-found/not-found/not-found.component.css: -------------------------------------------------------------------------------- 1 | .not-found { 2 | margin-top: 10em; 3 | text-align: center; 4 | } 5 | 6 | .app-logo { 7 | margin-top: 50px; 8 | } -------------------------------------------------------------------------------- /webapp/src/app/not-found/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Oops!

5 |

404 Not Found

6 |
7 | Sorry, an error has occured, Requested page not found! 8 |
9 |
10 | 11 |
-------------------------------------------------------------------------------- /webapp/src/app/not-found/not-found/not-found.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NotFoundComponent } from './not-found.component'; 4 | 5 | describe('NotFoundComponent', () => { 6 | let component: NotFoundComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NotFoundComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NotFoundComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /webapp/src/app/not-found/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-not-found', 5 | templateUrl: './not-found.component.html', 6 | styleUrls: ['./not-found.component.css'] 7 | }) 8 | export class NotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /webapp/src/app/shared/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router"; 3 | import { AuthService } from "../service/auth.service"; 4 | 5 | @Injectable() 6 | export class AuthGuard implements CanActivate { 7 | 8 | constructor(private router: Router, private authService: AuthService) { 9 | } 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 12 | if (this.authService.isAuthenticated()) { 13 | return true; 14 | } 15 | this.router.navigate(['/login']); 16 | return false; 17 | } 18 | } -------------------------------------------------------------------------------- /webapp/src/app/shared/interceptor/auth-interceptor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | HttpErrorResponse, 4 | HttpEvent, 5 | HttpHandler, 6 | HttpInterceptor, 7 | HttpRequest, 8 | HttpResponse 9 | } from '@angular/common/http'; 10 | import { Observable } from "rxjs"; 11 | import { AppSettings } from "../../app.settings"; 12 | 13 | @Injectable() 14 | export class AuthorizationInterceptor implements HttpInterceptor { 15 | 16 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 17 | return next.handle(this.addAuthorizationHeader(req)); 18 | } 19 | 20 | private addAuthorizationHeader(req: HttpRequest) { 21 | if (req.url.startsWith(AppSettings.EMPLOYEE_ENDPOINT) || 22 | req.url.startsWith(AppSettings.DEPARTMENT_ENDPOINT) || 23 | req.url.startsWith(AppSettings.USER_ENDPOINT)) { 24 | let token = localStorage.getItem(AppSettings.ACCESS_TOKEN); 25 | if (token) { 26 | console.log('Append token "' + token + '" to request header...'); 27 | return req.clone({ setHeaders: { [AppSettings.AUTHORIZATION_HEADER_NAME]: 'Bearer ' + token } }); 28 | } 29 | } 30 | return req; 31 | } 32 | } -------------------------------------------------------------------------------- /webapp/src/app/shared/model/department.model.ts: -------------------------------------------------------------------------------- 1 | // Generated using typescript-generator version 2.0.400 on 2020-08-05 19:45:35. 2 | 3 | export interface Department { 4 | id?: number; 5 | name?: string; 6 | noOfEmployees?: number; 7 | } 8 | 9 | export const enum EmployeeActionEnum { 10 | CREATE = 'CREATE', 11 | UPDATE = 'UPDATE', 12 | DELETE = 'DELETE', 13 | } 14 | -------------------------------------------------------------------------------- /webapp/src/app/shared/model/employee.model.ts: -------------------------------------------------------------------------------- 1 | // Generated using typescript-generator version 2.0.400 on 2020-08-05 20:08:25. 2 | 3 | export interface Employee { 4 | id?: number; 5 | firstname?: string; 6 | lastname?: string; 7 | address?: string; 8 | phone?: string; 9 | departmentId?: number; 10 | } 11 | 12 | export interface EmployeeCountChangeModel { 13 | departmentId?: number; 14 | action?: string; 15 | typeName?: string; 16 | } 17 | 18 | export interface ErrorDetail { 19 | title?: string; 20 | status?: number; 21 | detail?: string; 22 | timeStamp?: number; 23 | developerMessage?: string; 24 | errors?: { [index: string]: ValidationError[] }; 25 | } 26 | 27 | export interface ValidationError { 28 | code?: string; 29 | message?: string; 30 | } 31 | 32 | export const enum EmployeeActionEnum { 33 | CREATE = 'CREATE', 34 | UPDATE = 'UPDATE', 35 | DELETE = 'DELETE', 36 | } 37 | -------------------------------------------------------------------------------- /webapp/src/app/shared/model/logging-user.ts: -------------------------------------------------------------------------------- 1 | export class LoggingUser { 2 | username: string; 3 | password: string; 4 | } -------------------------------------------------------------------------------- /webapp/src/app/shared/service/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { HttpClient, HttpHeaders } from "@angular/common/http"; 3 | import { AppSettings } from "../../app.settings"; 4 | 5 | @Injectable() 6 | export class AuthService { 7 | 8 | constructor( 9 | private httpClient: HttpClient) { 10 | } 11 | 12 | public authenticate(username: string, password: string) { 13 | let requestBody = new FormData(); 14 | requestBody.append("grant_type", "password"); 15 | requestBody.append("scope", "webclient"); 16 | requestBody.append("username", username); 17 | requestBody.append("password", password); 18 | let headers = new HttpHeaders(); 19 | headers = headers.set("Authorization", "Basic " + btoa("payroll:test")); 20 | let httpOptions = { 21 | "Content-Type": "multipart/form-data", 22 | headers: headers 23 | }; 24 | return this.httpClient.post(AppSettings.LOGIN_ENDPOINT, requestBody, httpOptions); 25 | } 26 | 27 | public retrieveUser() { 28 | return this.httpClient.get(AppSettings.USER_ENDPOINT); 29 | } 30 | 31 | public isAuthenticated():boolean { 32 | return (localStorage.getItem(AppSettings.ACCESS_TOKEN) 33 | && JSON.parse(localStorage.getItem(AppSettings.CURRENT_USER))) ? true:false; 34 | } 35 | 36 | public logOut(){ 37 | localStorage.removeItem(AppSettings.ACCESS_TOKEN); 38 | localStorage.removeItem(AppSettings.CURRENT_USER); 39 | } 40 | } -------------------------------------------------------------------------------- /webapp/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/assets/.gitkeep -------------------------------------------------------------------------------- /webapp/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/assets/images/logo.png -------------------------------------------------------------------------------- /webapp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /webapp/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. -------------------------------------------------------------------------------- /webapp/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rshtishi/payroll/b180ceb2cb8382c2ab1e9887eded1aaaa1c943bc/webapp/src/favicon.ico -------------------------------------------------------------------------------- /webapp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | payroll 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /webapp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /webapp/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /webapp/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /webapp/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /webapp/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /webapp/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.base.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------