├── .circleci └── config.yml ├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── README.md ├── assets ├── graphiql.gif └── login.gif ├── awscdk_typescript ├── .gitignore ├── .npmignore ├── README.md ├── bin │ └── awscdk.ts ├── cdk.context.json ├── cdk.json ├── jest.config.js ├── lib │ └── awscdk-stack.ts ├── package-lock.json ├── package.json ├── test │ └── awscdk.test.ts └── tsconfig.json ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src └── main │ ├── frontend │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Home.js │ │ ├── Login.js │ │ └── css │ │ │ └── Login.css │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── registerServiceWorker.js │ │ ├── services │ │ ├── authServices.js │ │ └── userService.js │ │ ├── setupProxy.js │ │ └── store │ │ ├── auth │ │ ├── actionTypes.js │ │ ├── actions.js │ │ └── reducer.js │ │ ├── reducers.js │ │ └── users │ │ ├── actionTypes.js │ │ ├── actions.js │ │ └── reducer.js │ ├── java │ └── com │ │ └── github │ │ └── thewaterwalker │ │ └── springbootldapreact │ │ ├── SpringBootLdapReactApplication.java │ │ ├── config │ │ └── WebSecurityConfig.java │ │ ├── entity │ │ ├── LineItem.java │ │ ├── LineItemRepository.java │ │ ├── User.java │ │ └── UserRepository.java │ │ └── graphql │ │ ├── CustomGraphQLContextBuilder.java │ │ ├── GraphQLConfig.java │ │ ├── QueryResolver.java │ │ ├── UserResolver.java │ │ └── batchloader │ │ ├── LineItemBatchLoader.java │ │ └── UserBatchLoader.java │ └── resources │ ├── application.properties │ ├── data.sql │ ├── graphql-schema.graphqls │ ├── schema.sql │ └── static │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.e38026ab211847a798bfc1b4d35341dc.js │ ├── service-worker.js │ └── static │ ├── css │ ├── 2.d17711cb.chunk.css │ ├── 2.d17711cb.chunk.css.map │ ├── main.546881c5.chunk.css │ └── main.546881c5.chunk.css.map │ └── js │ ├── 2.5772784c.chunk.js │ ├── 2.5772784c.chunk.js.LICENSE.txt │ ├── 2.5772784c.chunk.js.map │ ├── main.1025fbfc.chunk.js │ ├── main.1025fbfc.chunk.js.map │ ├── runtime-main.c8a21426.js │ └── runtime-main.c8a21426.js.map └── terraform ├── 1_terraform_simple ├── README.md ├── main.tf ├── outputs.tf ├── terraform.tfvars ├── userdata.sh └── variables.tf └── README.md /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/2.0/configuration-reference 3 | version: 2.1 4 | 5 | jobs: 6 | # Below is the definition of your job to build and test your app, you can rename and customize it as you want. 7 | build-and-test: 8 | # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/ 9 | # You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. 10 | # Be sure to update the Docker image tag below to openjdk version of your application. 11 | # A list of available CircleCI Docker Convenience Images are available here: https://circleci.com/developer/images/image/cimg/openjdk 12 | docker: 13 | - image: cimg/openjdk:11.0 14 | steps: 15 | # Checkout the code as the first step. 16 | - checkout 17 | # Use mvn clean and package as the standard maven build phase 18 | - run: 19 | name: Build 20 | command: mvn -B -DskipTests clean package 21 | # Then run your tests! 22 | - run: 23 | name: Test 24 | command: mvn test 25 | 26 | workflows: 27 | # Below is the definition of your workflow. 28 | # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above. 29 | # CircleCI will run this workflow on every commit. 30 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 31 | sample: 32 | jobs: 33 | - build-and-test 34 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v2 21 | with: 22 | java-version: '11' 23 | distribution: 'adopt' 24 | - name: Build with Maven 25 | run: mvn -B package --file pom.xml 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake 2 | cmake-build-*/ 3 | 4 | ### IntelliJ IDEA ### 5 | .idea/ 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # JIRA plugin 12 | atlassian-ide-plugin.xml 13 | 14 | # Crashlytics plugin (for Android Studio and IntelliJ) 15 | com_crashlytics_export_strings.xml 16 | crashlytics.properties 17 | crashlytics-build.properties 18 | fabric.properties 19 | 20 | *.iml 21 | modules.xml 22 | *.ipr 23 | 24 | ### Maven ### 25 | target/ 26 | pom.xml.tag 27 | pom.xml.releaseBackup 28 | pom.xml.versionsBackup 29 | pom.xml.next 30 | release.properties 31 | dependency-reduced-pom.xml 32 | buildNumber.properties 33 | .mvn/timing.properties 34 | 35 | ### NetBeans ### 36 | **/nbproject/private/ 37 | **/nbproject/Makefile-*.mk 38 | **/nbproject/Package-*.bash 39 | build/ 40 | nbbuild/ 41 | dist/ 42 | nbdist/ 43 | .nb-gradle/ 44 | 45 | # Terraform 46 | **/.terraform/* 47 | *.tfstate 48 | *.tfstate.* 49 | crash.log 50 | 51 | # Node 52 | logs 53 | *.log 54 | npm-debug.log* 55 | yarn-debug.log* 56 | yarn-error.log* 57 | lerna-debug.log* 58 | # Diagnostic reports (https://nodejs.org/api/report.html) 59 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 60 | # Runtime data 61 | pids 62 | *.pid 63 | *.seed 64 | *.pid.lock 65 | # Dependency directories 66 | node_modules/ 67 | jspm_packages/ 68 | # Optional npm cache directory 69 | .npm 70 | node/ 71 | .DS_Store 72 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajramay/spring-boot-ldap-react/24fda22d7102bc3389cac65b68d67ea9fae6861f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11-jre-slim 2 | COPY /target/spring-boot-react-0.0.1-SNAPSHOT.jar /home/spring-boot-react.jar 3 | CMD ["java", "-jar", "/home/spring-boot-react.jar"] 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | tools { 4 | nodejs 'node-10.13.0' 5 | jdk 'jdk-11' 6 | maven 'maven-3.6.1' 7 | } 8 | triggers { 9 | pollSCM('H/5 6-23 * * 1-6') 10 | } 11 | stages { 12 | stage('Build NPM front end') { 13 | steps { 14 | sh 'cd src/main/frontend && npm install' 15 | sh 'cd src/main/frontend && npm run-script build' 16 | } 17 | } 18 | stage('Build Java') { 19 | steps { 20 | sh 'mvn clean deploy' 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Simple Spring Boot ReactJS outline Application with GraphQL, ReactJS, Spring Data JPA and Spring Security enabled 2 | 3 | ## Table of Contents 4 | 5 | - [Introduction](#introduction) 6 | - [Building](#building) 7 | - [Releasing](#releasing) 8 | - [Login Credentials](#login-credentials) 9 | - [GraphiQL](#graphiql) 10 | - [Jenkins](#jenkins) 11 | - [Terraform](#terraform) 12 | 13 | ### Introduction 14 | 15 | This application is a lightweight Spring Boot and ReactJS outline application which can be used as the basis for your own apps. 16 | 17 | This application serves the GUI using a GraphQL API and demonstrates idiomatic usage of GraphQL with entity 18 | GraphQLResolvers and GraphQL BatchLoaders that remove the N+1 issue by preloading data from the database 19 | that the entity GraphQLResolver will need later in the request. The loaders are scoped to the *request*. 20 | 21 | GraphQL Batching and DataLoaders : https://github.com/graphql-java/java-dataloader 22 | 23 | ### Building 24 | 25 | Start the Spring Boot development server with hotreload as follows 26 | 27 | ```shell script 28 | $ mvn clean spring-boot:run -P hotreload 29 | ``` 30 | 31 | Now start the React development server (in the same folder as the package.json) and browse to `http://localhost:3000` for hot-reload of the ReactJS app while you are developing 32 | ```shell script 33 | $ cd src/main/frontend 34 | $ npm install 35 | $ npm start 36 | ``` 37 | 38 | You can now make code changes in ReactJS and Java and the code will be hot-reloaded into the running application 39 | 40 | ### Releasing 41 | 42 | Once development is complete, simply run the following from the directory that contains `pom.xml` to create a release version of the application 43 | ```shell script 44 | $ mvn clean install -P react 45 | ``` 46 | 47 | If you don't want to use Maven to build the release version of the ReactJS app run the following in the `frontend` directory and then run Maven separately 48 | ```shell script 49 | $ cd src/main/frontend 50 | $ npm run build 51 | $ cd - 52 | $ mvn clean install 53 | ``` 54 | 55 | You can now run the app using Java 11 or above as follows 56 | ```shell script 57 | $ ${JAVA_HOME}/bin/java -jar target/spring-boot-react-0.0.1-SNAPSHOT.jar 58 | ``` 59 | 60 | ### Login Credentials 61 | These are the hard-coded credentials for this demo app and they can be found in `application.properties` Please change 62 | these in your version, preferably deleting them and enabling LDAP based authentication. 63 | Got to localhost:9090 to login once you have started the application. 64 | 65 | ```text 66 | Username : user1 67 | Password : password123 68 | ``` 69 | 70 | ![Login](assets/login.gif) 71 | 72 | ### GraphiQL 73 | There is a built in interactive interface for GraphQL that you can use during development for testing and debugging. 74 | The interface on your local run instance can be found at URL http://localhost:9090/graphiql 75 | 76 | ![GraphiQL](assets/graphiql.gif) 77 | 78 | ### Jenkins 79 | Install Jenkins from jenkins.io ensuring that the Pipeline plugin is installed and configure with the [Jenkinsfile](https://www.jenkins.io/doc/book/pipeline/jenkinsfile/) in this repo 80 | 81 | ### Terraform 82 | 83 | See the [README.md](terraform/README.md) in the terraform directory for instructions on how to deploy this sample project on AWS using Terraform 84 | 85 | ### AWS CDK 86 | 87 | See the awscdk_typescript directory for deployment with AWS CDK which is a replacement for Terraform -------------------------------------------------------------------------------- /assets/graphiql.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajramay/spring-boot-ldap-react/24fda22d7102bc3389cac65b68d67ea9fae6861f/assets/graphiql.gif -------------------------------------------------------------------------------- /assets/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajramay/spring-boot-ldap-react/24fda22d7102bc3389cac65b68d67ea9fae6861f/assets/login.gif -------------------------------------------------------------------------------- /awscdk_typescript/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /awscdk_typescript/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /awscdk_typescript/README.md: -------------------------------------------------------------------------------- 1 | # Spring Data LDAP React with AWS CDK 2 | 3 | AWS CDK is Amazon's IaC (Infrastructure as Code) offering and is an alternative to HashiCorp's Terraform. 4 | CDK allows developers to use their programming language of choice rather than a DSL in Terraform (HCL). 5 | 6 | This sample is written in TypeScript and the main application level additions are in lib/awscdk-stack.ts 7 | 8 | 9 | 10 | # Welcome to your CDK TypeScript project! 11 | 12 | This is a blank project for TypeScript development with CDK. 13 | 14 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 15 | 16 | ## Useful commands 17 | 18 | * `npm run build` compile typescript to js 19 | * `npm run watch` watch for changes and compile 20 | * `npm run test` perform the jest unit tests 21 | * `cdk deploy` deploy this stack to your default AWS account/region 22 | * `cdk diff` compare deployed stack with current state 23 | * `cdk synth` emits the synthesized CloudFormation template 24 | -------------------------------------------------------------------------------- /awscdk_typescript/bin/awscdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import { AwscdkStack } from '../lib/awscdk-stack'; 5 | 6 | const myenv = {account: process.env.CDK_DEFAULT_ACCOUNT, 7 | region: process.env.CDK_DEFAULT_REGION} 8 | 9 | const app = new cdk.App(); 10 | new AwscdkStack(app, 'AwscdkStack', {env: myenv}); 11 | -------------------------------------------------------------------------------- /awscdk_typescript/cdk.context.json: -------------------------------------------------------------------------------- 1 | { 2 | "vpc-provider:account=543305220264:filter.isDefault=true:region=eu-west-2:returnAsymmetricSubnets=true": { 3 | "vpcId": "vpc-6d75fb05", 4 | "vpcCidrBlock": "172.31.0.0/16", 5 | "availabilityZones": [], 6 | "subnetGroups": [ 7 | { 8 | "name": "Public", 9 | "type": "Public", 10 | "subnets": [ 11 | { 12 | "subnetId": "subnet-5eda0424", 13 | "cidr": "172.31.16.0/20", 14 | "availabilityZone": "eu-west-2a", 15 | "routeTableId": "rtb-e1433889" 16 | }, 17 | { 18 | "subnetId": "subnet-09678a45", 19 | "cidr": "172.31.32.0/20", 20 | "availabilityZone": "eu-west-2b", 21 | "routeTableId": "rtb-e1433889" 22 | }, 23 | { 24 | "subnetId": "subnet-8c5ecce5", 25 | "cidr": "172.31.0.0/20", 26 | "availabilityZone": "eu-west-2c", 27 | "routeTableId": "rtb-e1433889" 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /awscdk_typescript/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/awscdk.ts", 3 | "context": { 4 | "@aws-cdk/core:enableStackNameDuplicates": "true", 5 | "aws-cdk:enableDiffNoFail": "true", 6 | "@aws-cdk/core:stackRelativeExports": "true", 7 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 8 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, 9 | "@aws-cdk/aws-kms:defaultKeyPolicies": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /awscdk_typescript/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/test'], 3 | testMatch: ['**/*.test.ts'], 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /awscdk_typescript/lib/awscdk-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as ec2 from '@aws-cdk/aws-ec2'; 3 | import * as asg from '@aws-cdk/aws-autoscaling'; 4 | import * as elb from '@aws-cdk/aws-elasticloadbalancingv2' 5 | import * as r53 from '@aws-cdk/aws-route53' 6 | import * as tgt from '@aws-cdk/aws-route53-targets' 7 | 8 | export class AwscdkStack extends cdk.Stack { 9 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 10 | super(scope, id, props); 11 | 12 | // VPC 13 | const vpc = ec2.Vpc.fromLookup(this, 'myvpc', { 14 | isDefault: true, 15 | }) 16 | 17 | // AMI 18 | const ami = ec2.MachineImage.latestAmazonLinux({ 19 | cpuType : ec2.AmazonLinuxCpuType.X86_64, 20 | generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2, 21 | virtualization: ec2.AmazonLinuxVirt.HVM, 22 | storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, 23 | edition: ec2.AmazonLinuxEdition.STANDARD 24 | }) 25 | 26 | const awsTags = [ 27 | {key:'Name', value:'Spring Boot Ldap React'}, 28 | {key:'Env', value:'Development'}, 29 | ] 30 | 31 | // Security Group - front end app servers 32 | const frontSg = new ec2.SecurityGroup(this, 'Frontend SG', { 33 | vpc, 34 | securityGroupName: 'FrontEnd SG', 35 | allowAllOutbound: true 36 | }) 37 | frontSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8080)) 38 | frontSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22)) 39 | 40 | // Security Group - ALB 41 | const elbSg = new ec2.SecurityGroup(this, 'ELB SG', { 42 | vpc, 43 | securityGroupName: 'ELB SG', 44 | allowAllOutbound: false 45 | }) 46 | elbSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443)) 47 | elbSg.addEgressRule(frontSg, ec2.Port.tcp(8080)) 48 | 49 | // Security Group - RDS 50 | const rdsSg = new ec2.SecurityGroup(this, 'RDS SG', { 51 | vpc, 52 | securityGroupName: 'RDS SG', 53 | allowAllOutbound: false 54 | }) 55 | rdsSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5432)) 56 | 57 | // User data for Auto Scaling Group 58 | const user_data = ec2.UserData.forLinux() 59 | user_data.addCommands('sudo yum install psql') 60 | user_data.addCommands('sudo amazon-linux-extras install java-openjdk11') 61 | 62 | // Auto Scaling Group 63 | const autoScalingGroup = new asg.AutoScalingGroup(this, 'ASG', { 64 | vpc, 65 | instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO), 66 | machineImage: ami, 67 | minCapacity: 1, 68 | maxCapacity: 2, 69 | allowAllOutbound: false, 70 | userData: user_data, 71 | keyName: 'spring_ldap_key_pair' 72 | }) 73 | autoScalingGroup.addSecurityGroup(frontSg) 74 | 75 | // Application Load Balancer 76 | const alb = new elb.ApplicationLoadBalancer(this, 'ALB', { 77 | vpc, 78 | internetFacing: true, 79 | http2Enabled: true, 80 | securityGroup: elbSg 81 | }) 82 | 83 | const alb_target_group = new elb.ApplicationTargetGroup(this, 'target group', { 84 | vpc, 85 | port: 8080, 86 | protocol: elb.ApplicationProtocol.HTTP, 87 | targets: [autoScalingGroup], 88 | //healthCheck: { 89 | // enabled: true, 90 | // path: '/healthcheck', 91 | // protocol: elb.Protocol.HTTP, 92 | // healthyHttpCodes: '200' 93 | //} 94 | }) 95 | 96 | const alb_listener = new elb.ApplicationListener(this, 'ALB Listener', { 97 | protocol: elb.ApplicationProtocol.HTTP, 98 | loadBalancer: alb, 99 | defaultTargetGroups: [alb_target_group] 100 | }) 101 | 102 | // Route 53 zone and DNS entry 103 | const hosted_zone = new r53.HostedZone(this, 'HostedZone', { 104 | zoneName: 'Spring_Boot_LDAP_Zone', 105 | vpcs: [vpc] 106 | }) 107 | 108 | const a_record = new r53.ARecord(this, 'ALB A Record', { 109 | recordName: 'springreactldap', 110 | zone: hosted_zone, 111 | target: r53.RecordTarget.fromAlias(new tgt.LoadBalancerTarget(alb)) 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /awscdk_typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awscdk", 3 | "version": "0.1.0", 4 | "bin": { 5 | "awscdk": "bin/awscdk.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@aws-cdk/assert": "1.83.0", 15 | "@types/jest": "^26.0.10", 16 | "@types/node": "10.17.27", 17 | "aws-cdk": "1.83.0", 18 | "jest": "^26.4.2", 19 | "ts-jest": "^26.2.0", 20 | "ts-node": "^9.0.0", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "@aws-cdk/aws-autoscaling": "^1.83.0", 25 | "@aws-cdk/aws-ec2": "^1.83.0", 26 | "@aws-cdk/aws-elasticloadbalancingv2": "^1.83.0", 27 | "@aws-cdk/aws-route53": "^1.83.0", 28 | "@aws-cdk/aws-route53-targets": "^1.83.0", 29 | "@aws-cdk/core": "1.83.0", 30 | "source-map-support": "^0.5.16" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /awscdk_typescript/test/awscdk.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; 2 | import * as cdk from '@aws-cdk/core'; 3 | import * as Awscdk from '../lib/awscdk-stack'; 4 | 5 | test('Empty Stack', () => { 6 | const app = new cdk.App(); 7 | // WHEN 8 | const stack = new Awscdk.AwscdkStack(app, 'MyTestStack'); 9 | // THEN 10 | expectCDK(stack).to(matchTemplate({ 11 | "Resources": {} 12 | }, MatchStyle.EXACT)) 13 | }); 14 | -------------------------------------------------------------------------------- /awscdk_typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.thewaterwalker 7 | spring-boot-react 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-react 12 | Demo project for Spring Boot with GraphQL and LDAP 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.6.6 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.18.12 25 | 11 26 | 11 27 | 2.22.2 28 | 3.10.1 29 | 1.10.0 30 | 7.0.1 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-security 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-actuator 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-data-jpa 46 | 47 | 48 | org.springframework.security 49 | spring-security-ldap 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 55 | 56 | com.graphql-java-kickstart 57 | graphql-spring-boot-starter 58 | ${graphql-spring-boot-starter.version} 59 | 60 | 61 | com.graphql-java-kickstart 62 | graphiql-spring-boot-starter 63 | ${graphql-spring-boot-starter.version} 64 | runtime 65 | 66 | 67 | 68 | org.projectlombok 69 | lombok 70 | ${lombok.version} 71 | provided 72 | 73 | 74 | 75 | com.h2database 76 | h2 77 | 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-test 83 | test 84 | 85 | 86 | junit 87 | junit 88 | 89 | 90 | 91 | 92 | spring-boot-ldap 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-maven-plugin 97 | 98 | 99 | maven-compiler-plugin 100 | ${maven-compiler-plugin.version} 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-surefire-plugin 105 | ${maven-surefire-plugin.version} 106 | 107 | 3 108 | true 109 | -Xmx1024m -XX:MaxPermSize=256m 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | hotreload 118 | 119 | 120 | org.springframework.boot 121 | spring-boot-devtools 122 | 123 | 124 | 125 | 126 | react 127 | 128 | 129 | 130 | com.github.eirslett 131 | frontend-maven-plugin 132 | ${frontend-maven-plugin.version} 133 | 134 | 135 | v12.18.4 136 | 6.14.6 137 | src/main/frontend 138 | 139 | 140 | 141 | 142 | install node and npm 143 | 144 | install-node-and-npm 145 | 146 | 147 | 148 | 149 | npm install 150 | 151 | npm 152 | 153 | 154 | 155 | install 156 | 157 | 158 | 159 | 160 | npm run-script build 161 | 162 | npm 163 | 164 | 165 | 166 | run-script build 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /src/main/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "acorn": ">=5.7.4", 7 | "axios": ">=0.21.2", 8 | "dot-prop": ">=5.1.1", 9 | "graphiql": "^1.4.7", 10 | "handlebars": ">=4.7.7", 11 | "http-proxy-middleware": "^1.0.5", 12 | "kind-of": ">=6.0.3", 13 | "lodash": ">=4.17.21", 14 | "minimist": ">=1.2.6", 15 | "react": "^16.13.1", 16 | "react-dom": "^16.13.1", 17 | "react-redux": "^5.0.7", 18 | "react-router": "^4.3.1", 19 | "react-router-dom": "^4.3.1", 20 | "react-scripts": "^5.0.1", 21 | "react-table": "^6.8.6", 22 | "redux": "^4.0.0", 23 | "redux-thunk": "^2.2.0", 24 | "seamless-immutable": "^7.1.3" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "postbuild": "rm -rf ../resources/static/* && mkdir -p ../resources/static && cp -r build/* ../resources/static", 30 | "test": "react-scripts test --env=jsdom", 31 | "eject": "react-scripts eject" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajramay/spring-boot-ldap-react/24fda22d7102bc3389cac65b68d67ea9fae6861f/src/main/frontend/public/favicon.ico -------------------------------------------------------------------------------- /src/main/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | React App 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/main/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import { Route } from 'react-router-dom' 4 | import Home from './components/Home' 5 | import Login from './components/Login' 6 | import * as authSelectors from './store/auth/reducer' 7 | import { connect } from 'react-redux' 8 | import { withRouter } from 'react-router' 9 | 10 | class App extends Component { 11 | render() { 12 | return ( 13 |
14 | 15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | function mapStateToProps(state) { 22 | return { loggedIn : authSelectors.getLoginDetails(state).loggedIn} 23 | } 24 | 25 | export default withRouter(connect(mapStateToProps)(App)); 26 | -------------------------------------------------------------------------------- /src/main/frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/main/frontend/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import * as authActions from '../store/auth/actions'; 4 | import * as authSelectors from '../store/auth/reducer'; 5 | import * as userActions from '../store/users/actions'; 6 | import * as userSelectors from '../store/users/reducer'; 7 | import { withRouter, Redirect } from "react-router"; 8 | import connect from "react-redux/es/connect/connect"; 9 | import ReactTable from 'react-table'; 10 | import 'react-table/react-table.css'; 11 | 12 | class Home extends React.Component { 13 | 14 | constructor(props) { 15 | super(props) 16 | this.handleLogout = this.handleLogout.bind(this) 17 | this.props.dispatch(userActions.getUsers()) 18 | } 19 | 20 | handleLogout() { 21 | this.props.dispatch(authActions.logout()) 22 | } 23 | 24 | renderNavBar() { 25 | return ( 26 | ) 38 | } 39 | 40 | render() { 41 | if (this.props.loggedIn === false) { 42 | return 43 | } 44 | 45 | return ( 46 |
47 | {this.renderNavBar()} 48 |

Users

49 |
50 | 64 | 65 |
66 |
67 | ); 68 | } 69 | } 70 | 71 | function mapStateToProps(state) { 72 | return { 73 | loggedIn : authSelectors.getLoginDetails(state).loggedIn, 74 | users : userSelectors.getUsers(state).users 75 | } 76 | } 77 | 78 | export default withRouter(connect(mapStateToProps)(Home)); 79 | -------------------------------------------------------------------------------- /src/main/frontend/src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as authSelectors from '../store/auth/reducer' 3 | import * as authActiokns from '../store/auth/actions' 4 | import { withRouter, Redirect } from "react-router"; 5 | import connect from "react-redux/es/connect/connect"; 6 | import './css/Login.css' 7 | 8 | class Login extends React.Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | username: '', 14 | password: '', 15 | submitted: false 16 | }; 17 | this.usernameChange = this.usernameChange.bind(this); 18 | this.passwordChange = this.passwordChange.bind(this); 19 | this.handleSubmit = this.handleSubmit.bind(this); 20 | } 21 | 22 | usernameChange(event) { 23 | const { name, value } = event.target; 24 | this.setState( {username: value}); 25 | this.setState( {submitted: false}); 26 | } 27 | 28 | passwordChange(event) { 29 | const { name, value } = event.target; 30 | this.setState( {password: value}); 31 | this.setState( {submitted: false}); 32 | } 33 | 34 | handleSubmit(event) { 35 | event.preventDefault(); 36 | this.setState( {submitted: true}); 37 | const { username, password } = this.state; 38 | this.props.dispatch(authActiokns.authenticate(username, password)); 39 | } 40 | 41 | render() { 42 | if (this.props.loggedIn === true) { 43 | return 44 | } 45 | 46 | return ( 47 |
48 |
49 |

Please login

50 | 51 | 52 |
53 | 54 |
55 |
56 | ) 57 | } 58 | } 59 | 60 | function mapStateToProps(state) { 61 | return authSelectors.getLoginDetails(state) 62 | } 63 | 64 | export default withRouter(connect(mapStateToProps)(Login)); 65 | -------------------------------------------------------------------------------- /src/main/frontend/src/components/css/Login.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #eee !important; 3 | } 4 | 5 | .wrapper { 6 | margin-top: 80px; 7 | margin-bottom: 80px; 8 | } 9 | 10 | .form-signin { 11 | max-width: 380px; 12 | padding: 15px 35px 45px; 13 | margin: 0 auto; 14 | background-color: #fff; 15 | border: 1px solid rgba(0, 0, 0, 0.1); 16 | } 17 | .form-signin .form-signin-heading, 18 | .form-signin .checkbox { 19 | margin-bottom: 30px; 20 | } 21 | .form-signin .checkbox { 22 | font-weight: normal; 23 | } 24 | .form-signin .form-control { 25 | position: relative; 26 | font-size: 16px; 27 | height: auto; 28 | padding: 10px; 29 | -webkit-box-sizing: border-box; 30 | -moz-box-sizing: border-box; 31 | box-sizing: border-box; 32 | } 33 | .form-signin .form-control:focus { 34 | z-index: 2; 35 | } 36 | .form-signin input[type="text"] { 37 | margin-bottom: 20px; 38 | border-radius: 10px; 39 | } 40 | .form-signin input[type="password"] { 41 | margin-bottom: 20px; 42 | border-radius: 10px; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux' 4 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux' 5 | import { BrowserRouter } from 'react-router-dom' 6 | import './index.css'; 7 | import App from './App'; 8 | import registerServiceWorker from './registerServiceWorker'; 9 | import thunk from 'redux-thunk' 10 | import * as reducers from './store/reducers' 11 | 12 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 13 | 14 | const store = createStore(combineReducers(reducers), composeEnhancers(applyMiddleware(thunk))) 15 | 16 | ReactDOM.render( 17 | 18 | 19 | 20 | 21 | , document.getElementById('root')); 22 | registerServiceWorker(); 23 | -------------------------------------------------------------------------------- /src/main/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/frontend/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/frontend/src/services/authServices.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | class AuthService { 4 | 5 | async login(username, password) { 6 | var fd = new FormData() 7 | fd.append('j_username', encodeURI(username)) 8 | fd.append('j_password', encodeURI(password)) 9 | 10 | try { 11 | const response = await axios({ 12 | url: '/auth', 13 | method: 'post', 14 | headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 15 | data: fd, 16 | withCredentials: true 17 | }); 18 | 19 | if (response.status === 200) { 20 | return { loggedIn: true, error:'', status: response.status } 21 | } else { 22 | return { loggedIn: false, error:response.error, status: response.status } 23 | } 24 | } catch (err) { 25 | return { status: err.response.status, error: err.response.statusText } 26 | } 27 | } 28 | 29 | async logout() { 30 | 31 | try { 32 | const response = await axios({ 33 | url: '/logout', 34 | method: 'post', 35 | headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 36 | withCredentials: true 37 | }); 38 | 39 | if (response.status === 200) { 40 | return { loggedIn: true, error:'', status: response.status } 41 | } else { 42 | return { loggedIn: false, error:'', status: response.status } 43 | } 44 | } catch (err) { 45 | return { status: err.response.status, error: err.response.statusText } 46 | } 47 | } 48 | } 49 | 50 | export default new AuthService() -------------------------------------------------------------------------------- /src/main/frontend/src/services/userService.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import * as _ from 'lodash'; 3 | 4 | class UserService { 5 | 6 | async getUsers() { 7 | try { 8 | const response = await axios({ 9 | url: '/graphql', 10 | method: 'post', 11 | data: { 12 | query: ` 13 | query { 14 | allUsers { 15 | id 16 | firstName 17 | lastName 18 | totalSpent 19 | maximumSpent 20 | } 21 | } 22 | ` 23 | }, 24 | withCredentials: true 25 | }); 26 | 27 | console.log(response.data.data.users) 28 | 29 | if (response.status === 200) { 30 | return { users: response.data.data.allUsers, status: response.status } 31 | } else { 32 | return { loggedIn: false, error:response.error, status: response.status } 33 | } 34 | } catch (err) { 35 | return { status: err.response.status, error: err.response.statusText } 36 | } 37 | } 38 | } 39 | 40 | export default new UserService() 41 | -------------------------------------------------------------------------------- /src/main/frontend/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const { createProxyMiddleware } = require("http-proxy-middleware") 2 | 3 | module.exports = function(app) { 4 | app.use(createProxyMiddleware('/graphql', {target: 'http://localhost:9090/'})) 5 | app.use(createProxyMiddleware('/auth', {target: 'http://localhost:9090/'})) 6 | app.use(createProxyMiddleware('/logout', {target: 'http://localhost:9090/'})) 7 | } -------------------------------------------------------------------------------- /src/main/frontend/src/store/auth/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const LOGGING_IN = "auth.LOGGING_IN" 2 | export const LOGGED_IN = "auth.LOGGED_IN" 3 | export const LOGGED_OUT = "auth.LOGGED_OUT" 4 | export const AUTH_ERROR = "auth.AUTH_ERROR" 5 | export const SERVER_ERROR = "auth.SERVER_ERROR" 6 | -------------------------------------------------------------------------------- /src/main/frontend/src/store/auth/actions.js: -------------------------------------------------------------------------------- 1 | import authService from '../../services/authServices' 2 | import * as types from './actionTypes' 3 | 4 | export function authenticate(username, password) { 5 | return async(dispatch, getState) => { 6 | dispatch({type: types.LOGGING_IN}); 7 | 8 | const { status, error } = await authService.login(username, password) 9 | 10 | switch (status) { 11 | case 200: 12 | dispatch({type: types.LOGGED_IN, username }); 13 | break; 14 | case 401: 15 | dispatch({type: types.AUTH_ERROR, status, error }); 16 | break; 17 | default: 18 | dispatch({type: types.SERVER_ERROR, status, error }); 19 | } 20 | } 21 | } 22 | 23 | export function logout() { 24 | return async (dispatch, getState) => { 25 | const { status, error } = await authService.logout(); 26 | 27 | switch (status) { 28 | case 200: 29 | dispatch({type: types.LOGGED_OUT, username:undefined}); 30 | break; 31 | case 401: 32 | dispatch({type: types.AUTH_ERROR, status, error }); 33 | break; 34 | default: 35 | dispatch({type: types.SERVER_ERROR, status, error }); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/frontend/src/store/auth/reducer.js: -------------------------------------------------------------------------------- 1 | 2 | import * as types from './actionTypes' 3 | 4 | const initialState = { 5 | loggedIn: false, 6 | username: undefined, 7 | error: '' 8 | }; 9 | 10 | export default function reduce(state=initialState, action={}) { 11 | switch (action.type) { 12 | case types.LOGGED_IN: 13 | return {loggedIn: true, username : action.username, error:''}; 14 | case types.LOGGING_IN: 15 | case types.LOGGED_OUT: 16 | return {loggedIn: false, username : undefined, error:''}; 17 | case types.AUTH_ERROR: 18 | return {loggedIn: false, username : undefined, error:'Authentication Error'}; 19 | case types.SERVER_ERROR: 20 | return {loggedIn: false, username : undefined, error:'Server Error'}; 21 | default: 22 | return state 23 | } 24 | } 25 | 26 | export function getLoginDetails(state) { 27 | return {loggedIn: state.auth.loggedIn, username: state.auth.username, error: state.auth.error } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/frontend/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | 2 | import auth from './auth/reducer' 3 | import users from './users/reducer' 4 | 5 | export { 6 | auth, 7 | users 8 | } -------------------------------------------------------------------------------- /src/main/frontend/src/store/users/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const LOADING = "users.LOADING" 2 | export const LOADED = "users.LOADED" 3 | -------------------------------------------------------------------------------- /src/main/frontend/src/store/users/actions.js: -------------------------------------------------------------------------------- 1 | import userService from '../../services/userService'; 2 | import * as types from './actionTypes'; 3 | import * as authTypes from '../auth/actionTypes'; 4 | 5 | export function getUsers() { 6 | return async (dispatch, getState) => { 7 | 8 | dispatch({type: types.LOADING}); 9 | 10 | const { users, status } = await userService.getUsers(); 11 | 12 | switch (status) { 13 | case 200: 14 | dispatch({type: types.LOADED, users }); 15 | break; 16 | case 401: 17 | dispatch({type: authTypes.AUTH_ERROR, status }); 18 | break; 19 | default: 20 | dispatch({type: authTypes.SERVER_ERROR, status }); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/frontend/src/store/users/reducer.js: -------------------------------------------------------------------------------- 1 | 2 | import * as actionTypes from './actionTypes'; 3 | 4 | const initialState = { 5 | users: [] 6 | }; 7 | 8 | export default function reducer(state=initialState, action={}) { 9 | switch (action.type) { 10 | case actionTypes.LOADING: 11 | return { users: [] } 12 | case actionTypes.LOADED: 13 | return { users: action.users } 14 | default: 15 | return state; 16 | } 17 | } 18 | 19 | export function getUsers(state) { 20 | return { users : state.users.users } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/SpringBootLdapReactApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.boot.autoconfigure.domain.EntityScan; 21 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 22 | 23 | @SpringBootApplication 24 | @EnableJpaRepositories 25 | @EntityScan 26 | public class SpringBootLdapReactApplication { 27 | 28 | public static void main(String[] args) { 29 | SpringApplication.run(SpringBootLdapReactApplication.class, args); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/config/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.config; 17 | 18 | import org.springframework.beans.factory.annotation.Value; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.ldap.core.support.BaseLdapPathContextSource; 21 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 22 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 23 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 24 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 25 | import org.springframework.security.core.Authentication; 26 | import org.springframework.security.core.AuthenticationException; 27 | import org.springframework.security.core.GrantedAuthority; 28 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 29 | import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; 30 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 31 | import org.springframework.security.ldap.DefaultSpringSecurityContextSource; 32 | import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; 33 | import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; 34 | import org.springframework.security.web.AuthenticationEntryPoint; 35 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; 36 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 37 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 38 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository; 39 | 40 | import javax.servlet.ServletException; 41 | import javax.servlet.http.HttpServletRequest; 42 | import javax.servlet.http.HttpServletResponse; 43 | import java.io.IOException; 44 | import java.util.ArrayList; 45 | import java.util.Collections; 46 | 47 | @EnableWebSecurity 48 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 49 | 50 | public final static String ROLE_APP_USER = "ROLE_APP_USER"; 51 | public final static String ROLE_APP_READONLY = "ROLE_APP_READONLY"; 52 | 53 | @Value("${myapp.username}") 54 | private String myAppUsername; 55 | 56 | @Value("${myapp.password}") 57 | private String myAppPassword; 58 | 59 | @Value("${myapp.ldap.provider.url}") 60 | private String ldapProviderUrl; 61 | 62 | @Value("${myapp.ldap.provider.userdn}") 63 | private String ldapProviderUserDn; 64 | 65 | @Value("${myapp.ldap.provider.password}") 66 | private String ldapProviderPassword; 67 | 68 | @Value("${myapp.ldap.user.dn.patterns}") 69 | private String ldapUserDnPatterns; 70 | 71 | @Value("${myapp.ldap.group.search.base}") 72 | private String ldapGroupSearchBase; 73 | 74 | @Bean 75 | public SimpleUrlAuthenticationFailureHandler failureHandler() { 76 | return new SimpleUrlAuthenticationFailureHandler() { 77 | @Override 78 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 79 | response.sendError(401, "Authentication failure"); 80 | } 81 | }; 82 | } 83 | 84 | @Bean 85 | public SimpleUrlAuthenticationSuccessHandler successHandler() { 86 | return new SimpleUrlAuthenticationSuccessHandler() { 87 | @Override 88 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 89 | response.setStatus(200); 90 | } 91 | }; 92 | } 93 | 94 | private LogoutSuccessHandler logoutSuccessHandler() { 95 | return (httpServletRequest, httpServletResponse, authentication) -> httpServletResponse.setStatus(200); 96 | } 97 | 98 | private AuthenticationEntryPoint authenticationEntryPoint() { 99 | return (httpServletRequest, httpServletResponse, e) -> httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); 100 | } 101 | 102 | private LdapAuthoritiesPopulator pop() { 103 | final DefaultLdapAuthoritiesPopulator pop = new DefaultLdapAuthoritiesPopulator(contextSource(), ldapGroupSearchBase); 104 | pop.setGroupSearchFilter("member={0}"); 105 | return pop; 106 | } 107 | 108 | private BaseLdapPathContextSource contextSource() { 109 | DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource( 110 | Collections.singletonList(ldapProviderUrl), "DC=org,DC=com"); 111 | contextSource.setUserDn(ldapProviderUserDn); 112 | contextSource.setPassword(ldapProviderPassword); 113 | 114 | return contextSource; 115 | } 116 | 117 | private GrantedAuthoritiesMapper mapper() { 118 | return collection -> { 119 | SimpleGrantedAuthority defaultAuthority = new SimpleGrantedAuthority(ROLE_APP_READONLY); 120 | ArrayList auths = new ArrayList<>(); 121 | auths.add(defaultAuthority); 122 | auths.addAll(collection); 123 | return auths; 124 | }; 125 | } 126 | 127 | @Override 128 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 129 | final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 130 | 131 | // username and password from properties file for local testing 132 | auth 133 | .inMemoryAuthentication() 134 | .passwordEncoder(passwordEncoder) 135 | .withUser(myAppUsername) 136 | .password(passwordEncoder.encode(myAppPassword)) 137 | .roles(ROLE_APP_READONLY.substring(5), ROLE_APP_USER.substring(5)); 138 | 139 | // username and password from ldap (such as ActivDirectory) 140 | // auth 141 | // .ldapAuthentication() 142 | // .userDnPatterns(ldapUserDnPatterns) 143 | // .ldapAuthoritiesPopulator(pop()) 144 | // .authoritiesMapper(mapper()) 145 | // .contextSource(contextSource()); 146 | } 147 | 148 | protected void configure(HttpSecurity httpSecurity) throws Exception { 149 | httpSecurity 150 | .csrf() 151 | .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 152 | .and() 153 | .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint()) 154 | .and() 155 | .formLogin() 156 | .loginProcessingUrl("/auth") 157 | .successHandler(successHandler()) 158 | .failureHandler(failureHandler()) 159 | .usernameParameter("j_username") 160 | .passwordParameter("j_password") 161 | .permitAll() 162 | .and() 163 | .httpBasic() 164 | .and() 165 | .logout() 166 | .logoutUrl("/logout") 167 | .logoutSuccessHandler(logoutSuccessHandler()) 168 | .permitAll() 169 | .and() 170 | .headers() 171 | .frameOptions() 172 | .deny() 173 | .and() 174 | .authorizeRequests() 175 | .antMatchers("/graphql").authenticated() 176 | .antMatchers("/", "/graphiql", "/auth", "/logout").permitAll(); 177 | 178 | httpSecurity.headers().cacheControl(); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/entity/LineItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.entity; 17 | 18 | import lombok.Data; 19 | 20 | import javax.persistence.Entity; 21 | import javax.persistence.Id; 22 | import java.math.BigDecimal; 23 | 24 | @Data 25 | @Entity 26 | public class LineItem { 27 | @Id 28 | private Long id; 29 | private String description; 30 | private BigDecimal amount; 31 | private Long userId; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/entity/LineItemRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.entity; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.config.WebSecurityConfig; 19 | import org.springframework.data.jpa.repository.JpaRepository; 20 | import org.springframework.security.access.annotation.Secured; 21 | import org.springframework.stereotype.Repository; 22 | 23 | import java.util.Collection; 24 | import java.util.Set; 25 | 26 | @Repository 27 | @Secured({WebSecurityConfig.ROLE_APP_READONLY, WebSecurityConfig.ROLE_APP_USER}) 28 | public interface LineItemRepository extends JpaRepository { 29 | Collection findByUserId(Long id); 30 | Collection findByUserIdIn(Set ids); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/entity/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.entity; 17 | 18 | import lombok.Data; 19 | import javax.persistence.*; 20 | 21 | @Data 22 | @Entity 23 | public class User { 24 | @Id 25 | private Long id; 26 | String firstName; 27 | String lastName; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/entity/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.entity; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.config.WebSecurityConfig; 19 | import org.springframework.data.jpa.repository.JpaRepository; 20 | import org.springframework.security.access.annotation.Secured; 21 | import org.springframework.stereotype.Repository; 22 | 23 | @Repository 24 | @Secured({WebSecurityConfig.ROLE_APP_READONLY, WebSecurityConfig.ROLE_APP_USER}) 25 | public interface UserRepository extends JpaRepository { 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/graphql/CustomGraphQLContextBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.graphql; 17 | 18 | import graphql.kickstart.execution.context.DefaultGraphQLContext; 19 | import graphql.kickstart.execution.context.GraphQLContext; 20 | import graphql.kickstart.servlet.context.DefaultGraphQLServletContext; 21 | import graphql.kickstart.servlet.context.DefaultGraphQLWebSocketContext; 22 | import graphql.kickstart.servlet.context.GraphQLServletContextBuilder; 23 | import lombok.RequiredArgsConstructor; 24 | import org.dataloader.DataLoaderRegistry; 25 | import org.springframework.stereotype.Component; 26 | 27 | import javax.servlet.http.HttpServletRequest; 28 | import javax.servlet.http.HttpServletResponse; 29 | import javax.websocket.Session; 30 | import javax.websocket.server.HandshakeRequest; 31 | 32 | @Component 33 | @RequiredArgsConstructor 34 | public class CustomGraphQLContextBuilder implements GraphQLServletContextBuilder { 35 | 36 | private final DataLoaderRegistry dataLoaderRegistry; 37 | 38 | @Override 39 | public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { 40 | return DefaultGraphQLServletContext 41 | .createServletContext(this.dataLoaderRegistry, null) 42 | .with(httpServletRequest) 43 | .with(httpServletResponse) 44 | .build(); 45 | } 46 | 47 | @Override 48 | public GraphQLContext build(Session session, HandshakeRequest handshakeRequest) { 49 | return DefaultGraphQLWebSocketContext.createWebSocketContext(dataLoaderRegistry, null) 50 | .with(session) 51 | .with(handshakeRequest) 52 | .build(); 53 | } 54 | 55 | @Override 56 | public GraphQLContext build() { 57 | return new DefaultGraphQLContext(dataLoaderRegistry, null); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/graphql/GraphQLConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.graphql; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.graphql.batchloader.LineItemBatchLoader; 19 | import com.github.thewaterwalker.springbootldapreact.graphql.batchloader.UserBatchLoader; 20 | import org.dataloader.DataLoader; 21 | import org.dataloader.DataLoaderRegistry; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.web.context.annotation.RequestScope; 25 | 26 | @Configuration 27 | public class GraphQLConfig { 28 | 29 | public final static String USER_DATA_LOADER = "USER_DATA_LOADER"; 30 | public final static String LINE_ITEM_DATA_LOADER = "LINE_ITEM_DATA_LOADER"; 31 | 32 | @Bean 33 | @RequestScope 34 | public DataLoaderRegistry dataLoaderRegistry(UserBatchLoader userBatchLoader, LineItemBatchLoader lineItemBatchLoader) { 35 | DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry(); 36 | dataLoaderRegistry.register(USER_DATA_LOADER, DataLoader.newDataLoader(userBatchLoader)); 37 | dataLoaderRegistry.register(LINE_ITEM_DATA_LOADER, DataLoader.newMappedDataLoader(lineItemBatchLoader)); 38 | return dataLoaderRegistry; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/graphql/QueryResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.graphql; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.entity.User; 19 | import com.github.thewaterwalker.springbootldapreact.entity.UserRepository; 20 | import graphql.kickstart.tools.GraphQLQueryResolver; 21 | import graphql.schema.DataFetchingEnvironment; 22 | import lombok.RequiredArgsConstructor; 23 | import org.dataloader.DataLoader; 24 | import org.springframework.stereotype.Component; 25 | 26 | import java.util.Collection; 27 | import java.util.List; 28 | import java.util.Optional; 29 | import java.util.stream.Collectors; 30 | 31 | @Component 32 | @RequiredArgsConstructor 33 | public class QueryResolver implements GraphQLQueryResolver { 34 | 35 | private final UserRepository userRepository; 36 | 37 | public Optional getUser(Long id, DataFetchingEnvironment dfe) { 38 | 39 | // Pre-load the LineItems that we will need in the UserResolver 40 | DataLoader dataLoader = dfe.getDataLoader(GraphQLConfig.LINE_ITEM_DATA_LOADER); 41 | Optional userOpt = this.userRepository.findById(id); 42 | userOpt.ifPresent( u -> { 43 | dataLoader.load(u.getId()); 44 | dataLoader.dispatchAndJoin(); 45 | }); 46 | return userOpt; 47 | } 48 | 49 | public Collection users(Collection ids, DataFetchingEnvironment dfe) { 50 | // Pre-load the LineItems that we will need in the UserResolver 51 | DataLoader dataLoader = dfe.getDataLoader(GraphQLConfig.LINE_ITEM_DATA_LOADER); 52 | List allusers = this.userRepository.findAllById(ids); 53 | final List userIds = allusers.stream().map(User::getId).collect(Collectors.toList()); 54 | dataLoader.loadMany(userIds); 55 | dataLoader.dispatchAndJoin(); 56 | return allusers; 57 | } 58 | 59 | public Collection allUsers(DataFetchingEnvironment dfe) { 60 | // Pre-load the LineItems that we will need in the UserResolver 61 | DataLoader dataLoader = dfe.getDataLoader(GraphQLConfig.LINE_ITEM_DATA_LOADER); 62 | List allusers = this.userRepository.findAll(); 63 | final List userIds = allusers.stream().map(User::getId).collect(Collectors.toList()); 64 | dataLoader.loadMany(userIds); 65 | dataLoader.dispatchAndJoin(); 66 | return allusers; 67 | } 68 | 69 | public String description() { 70 | return "hello"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/graphql/UserResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.graphql; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.entity.LineItem; 19 | import com.github.thewaterwalker.springbootldapreact.entity.LineItemRepository; 20 | import com.github.thewaterwalker.springbootldapreact.entity.User; 21 | import graphql.kickstart.tools.GraphQLResolver; 22 | import graphql.schema.DataFetchingEnvironment; 23 | import lombok.RequiredArgsConstructor; 24 | import org.dataloader.DataLoader; 25 | import org.springframework.stereotype.Component; 26 | 27 | import java.math.BigDecimal; 28 | import java.util.Collection; 29 | import java.util.Comparator; 30 | import java.util.List; 31 | import java.util.Optional; 32 | import java.util.concurrent.CompletableFuture; 33 | 34 | @Component 35 | @RequiredArgsConstructor 36 | public class UserResolver implements GraphQLResolver { 37 | 38 | private final LineItemRepository lineItemRepository; 39 | 40 | public BigDecimal totalSpent(User u, DataFetchingEnvironment dfe) { 41 | 42 | // User the DataLoader which will have preloaded the LineItems from QueryResolver, rather than 43 | // read the LineItems per invocation to each method in this resolver class 44 | DataLoader> dataLoader = dfe.getDataLoader(GraphQLConfig.LINE_ITEM_DATA_LOADER); 45 | Optional>> ifCompleted = dataLoader.getIfCompleted(u.getId()); 46 | if (ifCompleted.isPresent()) { 47 | try { 48 | List lineItems = ifCompleted.get().get(); 49 | if (lineItems != null) { 50 | return lineItems.stream().map(LineItem::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO); 51 | } else { 52 | return BigDecimal.ZERO; 53 | } 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | 59 | // Otherwise just load the line items again 60 | Collection lineItems = this.lineItemRepository.findByUserId(u.getId()); 61 | return lineItems.stream().map(LineItem::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO); 62 | } 63 | 64 | public BigDecimal maximumSpent(User u, DataFetchingEnvironment dfe) { 65 | 66 | // User the DataLoader which will have preloaded the LineItems from QueryResolver, rather than 67 | // read the LineItems per invocation to each method in this resolver class 68 | DataLoader> dataLoader = dfe.getDataLoader(GraphQLConfig.LINE_ITEM_DATA_LOADER); 69 | Optional>> ifCompleted = dataLoader.getIfCompleted(u.getId()); 70 | if (ifCompleted.isPresent()) { 71 | try { 72 | 73 | List lineItems = ifCompleted.get().get(); 74 | if (lineItems != null) { 75 | return lineItems.stream().map(LineItem::getAmount).max(Comparator.naturalOrder()).orElse(BigDecimal.ZERO); 76 | } else { 77 | return BigDecimal.ZERO; 78 | } 79 | } catch (Exception e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | 84 | // Otherwise just load the items again 85 | Collection lineItems = this.lineItemRepository.findByUserId(u.getId()); 86 | return lineItems.stream().map(LineItem::getAmount).max(Comparator.naturalOrder()).orElse(BigDecimal.ZERO); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/graphql/batchloader/LineItemBatchLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.graphql.batchloader; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.entity.LineItem; 19 | import com.github.thewaterwalker.springbootldapreact.entity.LineItemRepository; 20 | import lombok.RequiredArgsConstructor; 21 | import org.dataloader.MappedBatchLoader; 22 | import org.springframework.stereotype.Component; 23 | 24 | import java.util.Collection; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Set; 28 | import java.util.concurrent.CompletableFuture; 29 | import java.util.concurrent.CompletionStage; 30 | import java.util.stream.Collectors; 31 | 32 | @Component 33 | @RequiredArgsConstructor 34 | public class LineItemBatchLoader implements MappedBatchLoader> { 35 | 36 | private final LineItemRepository lineItemRepository; 37 | 38 | @Override 39 | public CompletionStage>> load(Set userIds) { 40 | return CompletableFuture.supplyAsync(() -> { 41 | // Preload the set of line items per user and store them against the user-id 42 | final Collection lineitems = this.lineItemRepository.findByUserIdIn(userIds); 43 | return lineitems.stream().collect(Collectors.groupingBy(LineItem::getUserId)); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/thewaterwalker/springbootldapreact/graphql/batchloader/UserBatchLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is free software: you can redistribute it and/or modify 3 | * it under the terms of the GNU General Public License as published by 4 | * the Free Software Foundation, either version 3 of the License, or 5 | * (at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU General Public License 13 | * along with this program. If not, see . 14 | */ 15 | 16 | package com.github.thewaterwalker.springbootldapreact.graphql.batchloader; 17 | 18 | import com.github.thewaterwalker.springbootldapreact.entity.User; 19 | import com.github.thewaterwalker.springbootldapreact.entity.UserRepository; 20 | import lombok.RequiredArgsConstructor; 21 | import org.dataloader.BatchLoader; 22 | import org.springframework.stereotype.Component; 23 | 24 | import java.util.List; 25 | import java.util.concurrent.CompletableFuture; 26 | import java.util.concurrent.CompletionStage; 27 | 28 | @RequiredArgsConstructor 29 | @Component 30 | public class UserBatchLoader implements BatchLoader { 31 | 32 | private final UserRepository userRepository; 33 | 34 | @Override 35 | public CompletionStage> load(List set) { 36 | return CompletableFuture.supplyAsync(() -> this.userRepository.findAllById(set)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 2 | spring.datasource.username=sa 3 | spring.datasource.password= 4 | spring.datasource.driver-class-name=org.h2.Driver 5 | # Useful to show the efficiency of the GraphQL DataLoaders which remove the N+1 loads 6 | spring.jpa.show-sql=true 7 | server.port=9090 8 | 9 | spring.jpa.hibernate.ddl-auto=none 10 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 11 | 12 | # credentials for local testing. Not needed for ActivDirectory 13 | myapp.username=user1 14 | myapp.password=password123 15 | 16 | # ActivDirectory settings. Enable AD in WebSecurityConfig 17 | 18 | # LDAP (ActivDirectory) URL 19 | myapp.ldap.provider.url=ldap://localhost:389 20 | 21 | # credentials needed to bind to ActivDirectory. Note that ActivDirectory does not allow anonymous bind by default. 22 | myapp.ldap.provider.userdn=cn=ldapuser,ou=users,ou=dept,ou=region,ou=company 23 | myapp.ldap.provider.password=Password4321 24 | 25 | # user patterns for authentication and group patterns for authorisation 26 | myapp.ldap.user.dn.patterns=cn={0},ou=users,ou=dept,ou=region,ou=company 27 | myapp.ldap.group.search.base=ou=groups,ou=dept,ou=region,ou=company 28 | 29 | # enable LDAP health indicator if you are actually using LDAP 30 | management.health.ldap.enabled=false 31 | -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (1, 'Nigel','Sheldon'); 2 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (2, 'Ozzie','Isaacs'); 3 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (3, 'Mellanie','Rescorai'); 4 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (4, 'Justine','Burnelli'); 5 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (5, 'Bradley','Johansson'); 6 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (6, 'Mark','Vernon'); 7 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (7, 'Dudley','Bose'); 8 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (8, 'Paula','Myo'); 9 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (9, 'Alessandra','Barron'); 10 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (10,'Kazimir','Burnelli'); 11 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (11,'Bruce','McFoster'); 12 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (12,'Gore','Burnelli'); 13 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (13,'Kazimir','McFoster'); 14 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (14,'Oscar','Monroe'); 15 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (15,'Anna','Kime'); 16 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (16,'Tiger','Pansy'); 17 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (17,'Catherine','Stewart'); 18 | INSERT INTO USER (ID, FIRST_NAME, LAST_NAME) VALUES (18,'Stig','McSobel'); 19 | 20 | INSERT INTO LINE_ITEM (ID, DESCRIPTION, AMOUNT, USER_ID) VALUES (1, 'BOOK', 23.02, 1); 21 | INSERT INTO LINE_ITEM (ID, DESCRIPTION, AMOUNT, USER_ID) VALUES (2, 'CHAIR', 102.02, 1); 22 | INSERT INTO LINE_ITEM (ID, DESCRIPTION, AMOUNT, USER_ID) VALUES (3, 'COFFEE', 3.00, 1); 23 | INSERT INTO LINE_ITEM (ID, DESCRIPTION, AMOUNT, USER_ID) VALUES (4, 'CAKE', 5.00, 1); 24 | INSERT INTO LINE_ITEM (ID, DESCRIPTION, AMOUNT, USER_ID) VALUES (5, 'COFFEE', 5.00, 2); 25 | 26 | commit; -------------------------------------------------------------------------------- /src/main/resources/graphql-schema.graphqls: -------------------------------------------------------------------------------- 1 | 2 | schema { 3 | query: Query 4 | } 5 | 6 | type User { 7 | id: Long 8 | firstName: String 9 | lastName: String 10 | 11 | # these items do not exist in the User entiry itself and so have to be resolved by the 12 | # UserResolver class which will look them up on demand 13 | totalSpent: Float 14 | maximumSpent: Float 15 | } 16 | 17 | type Query { 18 | allUsers : [User] 19 | users(ids: [Long]) : [User] 20 | user(id: Long) : User 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE USER 2 | ( 3 | ID NUMBER PRIMARY KEY, 4 | FIRST_NAME VARCHAR(256), 5 | LAST_NAME VARCHAR(256) 6 | ); 7 | 8 | CREATE TABLE LINE_ITEM 9 | ( 10 | ID NUMBER PRIMARY KEY, 11 | DESCRIPTION VARCHAR(256), 12 | AMOUNT NUMERIC(18, 2), 13 | USER_ID NUMBER REFERENCES USER(ID) 14 | ); 15 | -------------------------------------------------------------------------------- /src/main/resources/static/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.546881c5.chunk.css", 4 | "main.js": "/static/js/main.1025fbfc.chunk.js", 5 | "main.js.map": "/static/js/main.1025fbfc.chunk.js.map", 6 | "runtime-main.js": "/static/js/runtime-main.c8a21426.js", 7 | "runtime-main.js.map": "/static/js/runtime-main.c8a21426.js.map", 8 | "static/css/2.d17711cb.chunk.css": "/static/css/2.d17711cb.chunk.css", 9 | "static/js/2.5772784c.chunk.js": "/static/js/2.5772784c.chunk.js", 10 | "static/js/2.5772784c.chunk.js.map": "/static/js/2.5772784c.chunk.js.map", 11 | "index.html": "/index.html", 12 | "precache-manifest.e38026ab211847a798bfc1b4d35341dc.js": "/precache-manifest.e38026ab211847a798bfc1b4d35341dc.js", 13 | "service-worker.js": "/service-worker.js", 14 | "static/css/2.d17711cb.chunk.css.map": "/static/css/2.d17711cb.chunk.css.map", 15 | "static/css/main.546881c5.chunk.css.map": "/static/css/main.546881c5.chunk.css.map", 16 | "static/js/2.5772784c.chunk.js.LICENSE.txt": "/static/js/2.5772784c.chunk.js.LICENSE.txt" 17 | }, 18 | "entrypoints": [ 19 | "static/js/runtime-main.c8a21426.js", 20 | "static/css/2.d17711cb.chunk.css", 21 | "static/js/2.5772784c.chunk.js", 22 | "static/css/main.546881c5.chunk.css", 23 | "static/js/main.1025fbfc.chunk.js" 24 | ] 25 | } -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sajramay/spring-boot-ldap-react/24fda22d7102bc3389cac65b68d67ea9fae6861f/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /src/main/resources/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/static/precache-manifest.e38026ab211847a798bfc1b4d35341dc.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "e878e1582bd6ccd77e70233e21648350", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "2668fd0be756dfbc6485", 8 | "url": "/static/css/2.d17711cb.chunk.css" 9 | }, 10 | { 11 | "revision": "929df681d711a73994c0", 12 | "url": "/static/css/main.546881c5.chunk.css" 13 | }, 14 | { 15 | "revision": "2668fd0be756dfbc6485", 16 | "url": "/static/js/2.5772784c.chunk.js" 17 | }, 18 | { 19 | "revision": "c836aaadfe43dfd9fcb8ddeddfd61e18", 20 | "url": "/static/js/2.5772784c.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "929df681d711a73994c0", 24 | "url": "/static/js/main.1025fbfc.chunk.js" 25 | }, 26 | { 27 | "revision": "2747b914e9a60db2276f", 28 | "url": "/static/js/runtime-main.c8a21426.js" 29 | } 30 | ]); -------------------------------------------------------------------------------- /src/main/resources/static/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.e38026ab211847a798bfc1b4d35341dc.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /src/main/resources/static/static/css/2.d17711cb.chunk.css: -------------------------------------------------------------------------------- 1 | .ReactTable{position:relative;display:flex;flex-direction:column;border:1px solid rgba(0,0,0,.1)}.ReactTable *{box-sizing:border-box}.ReactTable .rt-table{flex:auto 1;display:flex;flex-direction:column;align-items:stretch;width:100%;border-collapse:collapse;overflow:auto}.ReactTable .rt-thead{flex:1 0 auto;display:flex;flex-direction:column;-webkit-user-select:none;-ms-user-select:none;user-select:none}.ReactTable .rt-thead.-headerGroups{background:rgba(0,0,0,.03)}.ReactTable .rt-thead.-filters,.ReactTable .rt-thead.-headerGroups{border-bottom:1px solid rgba(0,0,0,.05)}.ReactTable .rt-thead.-filters input,.ReactTable .rt-thead.-filters select{border:1px solid rgba(0,0,0,.1);background:#fff;padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:400;outline:none}.ReactTable .rt-thead.-filters .rt-th{border-right:1px solid rgba(0,0,0,.02)}.ReactTable .rt-thead.-header{box-shadow:0 2px 15px 0 rgba(0,0,0,.15)}.ReactTable .rt-thead .rt-tr{text-align:center}.ReactTable .rt-thead .rt-td,.ReactTable .rt-thead .rt-th{padding:5px;line-height:normal;position:relative;border-right:1px solid rgba(0,0,0,.05);transition:box-shadow .3s cubic-bezier(.175,.885,.32,1.275);box-shadow:inset 0 0 0 0 transparent}.ReactTable .rt-thead .rt-td.-sort-asc,.ReactTable .rt-thead .rt-th.-sort-asc{box-shadow:inset 0 3px 0 0 rgba(0,0,0,.6)}.ReactTable .rt-thead .rt-td.-sort-desc,.ReactTable .rt-thead .rt-th.-sort-desc{box-shadow:inset 0 -3px 0 0 rgba(0,0,0,.6)}.ReactTable .rt-thead .rt-td.-cursor-pointer,.ReactTable .rt-thead .rt-th.-cursor-pointer{cursor:pointer}.ReactTable .rt-thead .rt-td:last-child,.ReactTable .rt-thead .rt-th:last-child{border-right:0}.ReactTable .rt-thead .rt-resizable-header{overflow:visible}.ReactTable .rt-thead .rt-resizable-header:last-child{overflow:hidden}.ReactTable .rt-thead .rt-resizable-header-content{overflow:hidden;text-overflow:ellipsis}.ReactTable .rt-thead .rt-header-pivot{border-right-color:#f7f7f7}.ReactTable .rt-thead .rt-header-pivot:after,.ReactTable .rt-thead .rt-header-pivot:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.ReactTable .rt-thead .rt-header-pivot:after{border-color:hsla(0,0%,100%,0) hsla(0,0%,100%,0) hsla(0,0%,100%,0) #fff;border-width:8px;margin-top:-8px}.ReactTable .rt-thead .rt-header-pivot:before{border-color:hsla(0,0%,40%,0) hsla(0,0%,40%,0) hsla(0,0%,40%,0) #f7f7f7;border-width:10px;margin-top:-10px}.ReactTable .rt-tbody{flex:99999 1 auto;display:flex;flex-direction:column;overflow:auto}.ReactTable .rt-tbody .rt-tr-group{border-bottom:1px solid rgba(0,0,0,.05)}.ReactTable .rt-tbody .rt-tr-group:last-child{border-bottom:0}.ReactTable .rt-tbody .rt-td{border-right:1px solid rgba(0,0,0,.02)}.ReactTable .rt-tbody .rt-td:last-child{border-right:0}.ReactTable .rt-tbody .rt-expandable{cursor:pointer;text-overflow:clip}.ReactTable .rt-tr-group{flex:1 0 auto;display:flex;flex-direction:column;align-items:stretch}.ReactTable .rt-tr{flex:1 0 auto;display:inline-flex}.ReactTable .rt-td,.ReactTable .rt-th{flex:1 0;white-space:nowrap;text-overflow:ellipsis;padding:7px 5px;overflow:hidden;transition:.3s ease;transition-property:width,min-width,padding,opacity}.ReactTable .rt-td.-hidden,.ReactTable .rt-th.-hidden{width:0!important;min-width:0!important;padding:0!important;border:0!important;opacity:0!important}.ReactTable .rt-expander{display:inline-block;position:relative;color:transparent;margin:0 10px}.ReactTable .rt-expander:after{content:"";position:absolute;width:0;height:0;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-90deg);border-left:5.04px solid transparent;border-right:5.04px solid transparent;border-top:7px solid rgba(0,0,0,.8);transition:all .3s cubic-bezier(.175,.885,.32,1.275);cursor:pointer}.ReactTable .rt-expander.-open:after{transform:translate(-50%,-50%) rotate(0)}.ReactTable .rt-resizer{display:inline-block;position:absolute;width:36px;top:0;bottom:0;right:-18px;cursor:col-resize;z-index:10}.ReactTable .rt-tfoot{flex:1 0 auto;display:flex;flex-direction:column;box-shadow:0 0 15px 0 rgba(0,0,0,.15)}.ReactTable .rt-tfoot .rt-td{border-right:1px solid rgba(0,0,0,.05)}.ReactTable .rt-tfoot .rt-td:last-child{border-right:0}.ReactTable.-striped .rt-tr.-odd{background:rgba(0,0,0,.03)}.ReactTable.-highlight .rt-tbody .rt-tr:not(.-padRow):hover{background:rgba(0,0,0,.05)}.ReactTable .-pagination{z-index:1;display:flex;justify-content:space-between;align-items:stretch;flex-wrap:wrap;padding:3px;box-shadow:0 0 15px 0 rgba(0,0,0,.1);border-top:2px solid rgba(0,0,0,.1)}.ReactTable .-pagination input,.ReactTable .-pagination select{border:1px solid rgba(0,0,0,.1);background:#fff;padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:400;outline:none}.ReactTable .-pagination .-btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:block;width:100%;height:100%;border:0;border-radius:3px;padding:6px;font-size:1em;color:rgba(0,0,0,.6);background:rgba(0,0,0,.1);transition:all .1s ease;cursor:pointer;outline:none}.ReactTable .-pagination .-btn[disabled]{opacity:.5;cursor:default}.ReactTable .-pagination .-btn:not([disabled]):hover{background:rgba(0,0,0,.3);color:#fff}.ReactTable .-pagination .-next,.ReactTable .-pagination .-previous{flex:1 1;text-align:center}.ReactTable .-pagination .-center{flex:1.5 1;text-align:center;margin-bottom:0;display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:space-around}.ReactTable .-pagination .-pageInfo{display:inline-block;margin:3px 10px;white-space:nowrap}.ReactTable .-pagination .-pageJump{display:inline-block}.ReactTable .-pagination .-pageJump input{width:70px;text-align:center}.ReactTable .-pagination .-pageSizeOptions{margin:3px 10px}.ReactTable .rt-noData{left:50%;top:50%;transform:translate(-50%,-50%);z-index:1;padding:20px;color:rgba(0,0,0,.5)}.ReactTable .-loading,.ReactTable .rt-noData{display:block;position:absolute;background:hsla(0,0%,100%,.8);transition:all .3s ease;pointer-events:none}.ReactTable .-loading{left:0;right:0;top:0;bottom:0;z-index:-1;opacity:0}.ReactTable .-loading>div{position:absolute;display:block;text-align:center;width:100%;top:50%;left:0;font-size:15px;color:rgba(0,0,0,.6);transform:translateY(-52%);transition:all .3s cubic-bezier(.25,.46,.45,.94)}.ReactTable .-loading.-active{opacity:1;z-index:2;pointer-events:all}.ReactTable .-loading.-active>div{transform:translateY(50%)}.ReactTable .rt-resizing .rt-td,.ReactTable .rt-resizing .rt-th{transition:none!important;cursor:col-resize;-webkit-user-select:none;-ms-user-select:none;user-select:none} 2 | /*# sourceMappingURL=2.d17711cb.chunk.css.map */ -------------------------------------------------------------------------------- /src/main/resources/static/static/css/2.d17711cb.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["react-table.css"],"names":[],"mappings":"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","file":"2.d17711cb.chunk.css","sourcesContent":[".ReactTable{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border:1px solid rgba(0,0,0,0.1);}.ReactTable *{box-sizing:border-box}.ReactTable .rt-table{-webkit-box-flex:1;-ms-flex:auto 1;flex:auto 1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%;border-collapse:collapse;overflow:auto}.ReactTable .rt-thead{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}.ReactTable .rt-thead.-headerGroups{background:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.05)}.ReactTable .rt-thead.-filters{border-bottom:1px solid rgba(0,0,0,0.05);}.ReactTable .rt-thead.-filters input,.ReactTable .rt-thead.-filters select{border:1px solid rgba(0,0,0,0.1);background:#fff;padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:normal;outline:none}.ReactTable .rt-thead.-filters .rt-th{border-right:1px solid rgba(0,0,0,0.02)}.ReactTable .rt-thead.-header{box-shadow:0 2px 15px 0 rgba(0,0,0,0.15)}.ReactTable .rt-thead .rt-tr{text-align:center}.ReactTable .rt-thead .rt-th,.ReactTable .rt-thead .rt-td{padding:5px 5px;line-height:normal;position:relative;border-right:1px solid rgba(0,0,0,0.05);transition:box-shadow .3s cubic-bezier(.175,.885,.32,1.275);box-shadow:inset 0 0 0 0 transparent;}.ReactTable .rt-thead .rt-th.-sort-asc,.ReactTable .rt-thead .rt-td.-sort-asc{box-shadow:inset 0 3px 0 0 rgba(0,0,0,0.6)}.ReactTable .rt-thead .rt-th.-sort-desc,.ReactTable .rt-thead .rt-td.-sort-desc{box-shadow:inset 0 -3px 0 0 rgba(0,0,0,0.6)}.ReactTable .rt-thead .rt-th.-cursor-pointer,.ReactTable .rt-thead .rt-td.-cursor-pointer{cursor:pointer}.ReactTable .rt-thead .rt-th:last-child,.ReactTable .rt-thead .rt-td:last-child{border-right:0}.ReactTable .rt-thead .rt-resizable-header{overflow:visible;}.ReactTable .rt-thead .rt-resizable-header:last-child{overflow:hidden}.ReactTable .rt-thead .rt-resizable-header-content{overflow:hidden;text-overflow:ellipsis}.ReactTable .rt-thead .rt-header-pivot{border-right-color:#f7f7f7}.ReactTable .rt-thead .rt-header-pivot:after,.ReactTable .rt-thead .rt-header-pivot:before{left:100%;top:50%;border:solid transparent;content:\" \";height:0;width:0;position:absolute;pointer-events:none}.ReactTable .rt-thead .rt-header-pivot:after{border-color:rgba(255,255,255,0);border-left-color:#fff;border-width:8px;margin-top:-8px}.ReactTable .rt-thead .rt-header-pivot:before{border-color:rgba(102,102,102,0);border-left-color:#f7f7f7;border-width:10px;margin-top:-10px}.ReactTable .rt-tbody{-webkit-box-flex:99999;-ms-flex:99999 1 auto;flex:99999 1 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;overflow:auto;}.ReactTable .rt-tbody .rt-tr-group{border-bottom:solid 1px rgba(0,0,0,0.05);}.ReactTable .rt-tbody .rt-tr-group:last-child{border-bottom:0}.ReactTable .rt-tbody .rt-td{border-right:1px solid rgba(0,0,0,0.02);}.ReactTable .rt-tbody .rt-td:last-child{border-right:0}.ReactTable .rt-tbody .rt-expandable{cursor:pointer;text-overflow:clip}.ReactTable .rt-tr-group{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.ReactTable .rt-tr{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.ReactTable .rt-th,.ReactTable .rt-td{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;white-space:nowrap;text-overflow:ellipsis;padding:7px 5px;overflow:hidden;transition:.3s ease;transition-property:width,min-width,padding,opacity;}.ReactTable .rt-th.-hidden,.ReactTable .rt-td.-hidden{width:0 !important;min-width:0 !important;padding:0 !important;border:0 !important;opacity:0 !important}.ReactTable .rt-expander{display:inline-block;position:relative;margin:0;color:transparent;margin:0 10px;}.ReactTable .rt-expander:after{content:'';position:absolute;width:0;height:0;top:50%;left:50%;-webkit-transform:translate(-50%,-50%) rotate(-90deg);transform:translate(-50%,-50%) rotate(-90deg);border-left:5.04px solid transparent;border-right:5.04px solid transparent;border-top:7px solid rgba(0,0,0,0.8);transition:all .3s cubic-bezier(.175,.885,.32,1.275);cursor:pointer}.ReactTable .rt-expander.-open:after{-webkit-transform:translate(-50%,-50%) rotate(0);transform:translate(-50%,-50%) rotate(0)}.ReactTable .rt-resizer{display:inline-block;position:absolute;width:36px;top:0;bottom:0;right:-18px;cursor:col-resize;z-index:10}.ReactTable .rt-tfoot{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;box-shadow:0 0 15px 0 rgba(0,0,0,0.15);}.ReactTable .rt-tfoot .rt-td{border-right:1px solid rgba(0,0,0,0.05);}.ReactTable .rt-tfoot .rt-td:last-child{border-right:0}.ReactTable.-striped .rt-tr.-odd{background:rgba(0,0,0,0.03)}.ReactTable.-highlight .rt-tbody .rt-tr:not(.-padRow):hover{background:rgba(0,0,0,0.05)}.ReactTable .-pagination{z-index:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:3px;box-shadow:0 0 15px 0 rgba(0,0,0,0.1);border-top:2px solid rgba(0,0,0,0.1);}.ReactTable .-pagination input,.ReactTable .-pagination select{border:1px solid rgba(0,0,0,0.1);background:#fff;padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:normal;outline:none}.ReactTable .-pagination .-btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:block;width:100%;height:100%;border:0;border-radius:3px;padding:6px;font-size:1em;color:rgba(0,0,0,0.6);background:rgba(0,0,0,0.1);transition:all .1s ease;cursor:pointer;outline:none;}.ReactTable .-pagination .-btn[disabled]{opacity:.5;cursor:default}.ReactTable .-pagination .-btn:not([disabled]):hover{background:rgba(0,0,0,0.3);color:#fff}.ReactTable .-pagination .-previous,.ReactTable .-pagination .-next{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center}.ReactTable .-pagination .-center{-webkit-box-flex:1.5;-ms-flex:1.5;flex:1.5;text-align:center;margin-bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around}.ReactTable .-pagination .-pageInfo{display:inline-block;margin:3px 10px;white-space:nowrap}.ReactTable .-pagination .-pageJump{display:inline-block;}.ReactTable .-pagination .-pageJump input{width:70px;text-align:center}.ReactTable .-pagination .-pageSizeOptions{margin:3px 10px}.ReactTable .rt-noData{display:block;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);background:rgba(255,255,255,0.8);transition:all .3s ease;z-index:1;pointer-events:none;padding:20px;color:rgba(0,0,0,0.5)}.ReactTable .-loading{display:block;position:absolute;left:0;right:0;top:0;bottom:0;background:rgba(255,255,255,0.8);transition:all .3s ease;z-index:-1;opacity:0;pointer-events:none;}.ReactTable .-loading > div{position:absolute;display:block;text-align:center;width:100%;top:50%;left:0;font-size:15px;color:rgba(0,0,0,0.6);-webkit-transform:translateY(-52%);transform:translateY(-52%);transition:all .3s cubic-bezier(.25,.46,.45,.94)}.ReactTable .-loading.-active{opacity:1;z-index:2;pointer-events:all;}.ReactTable .-loading.-active > div{-webkit-transform:translateY(50%);transform:translateY(50%)}.ReactTable .rt-resizing .rt-th,.ReactTable .rt-resizing .rt-td{transition:none !important;cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}"]} -------------------------------------------------------------------------------- /src/main/resources/static/static/css/main.546881c5.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:sans-serif}.App{text-align:center}.App-logo{-webkit-animation:App-logo-spin 20s linear infinite;animation:App-logo-spin 20s linear infinite;height:80px}.App-header{background-color:#222;height:150px;padding:20px;color:#fff}.App-title{font-size:1.5em}.App-intro{font-size:large}@-webkit-keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}body{background:#eee!important}.wrapper{margin-top:80px;margin-bottom:80px}.form-signin{max-width:380px;padding:15px 35px 45px;margin:0 auto;background-color:#fff;border:1px solid rgba(0,0,0,.1)}.form-signin .checkbox,.form-signin .form-signin-heading{margin-bottom:30px}.form-signin .checkbox{font-weight:400}.form-signin .form-control{position:relative;font-size:16px;height:auto;padding:10px;box-sizing:border-box}.form-signin .form-control:focus{z-index:2}.form-signin input[type=password],.form-signin input[type=text]{margin-bottom:20px;border-radius:10px} 2 | /*# sourceMappingURL=main.546881c5.chunk.css.map */ -------------------------------------------------------------------------------- /src/main/resources/static/static/css/main.546881c5.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.css","App.css","Login.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,SAAU,CACV,sBACF,CCJA,KACE,iBACF,CAEA,UACE,mDAA4C,CAA5C,2CAA4C,CAC5C,WACF,CAEA,YACE,qBAAsB,CACtB,YAAa,CACb,YAAa,CACb,UACF,CAEA,WACE,eACF,CAEA,WACE,eACF,CAEA,iCACE,GAAO,sBAAyB,CAChC,GAAK,uBAA2B,CAClC,CAHA,yBACE,GAAO,sBAAyB,CAChC,GAAK,uBAA2B,CAClC,CC3BA,KACI,yBACJ,CAEA,SACI,eAAgB,CAChB,kBACJ,CAEA,aACI,eAAgB,CAChB,sBAAuB,CACvB,aAAc,CACd,qBAAsB,CACtB,+BACJ,CACA,yDAEI,kBACJ,CACA,uBACI,eACJ,CACA,2BACI,iBAAkB,CAClB,cAAe,CACf,WAAY,CACZ,YAAa,CAGb,qBACJ,CACA,iCACI,SACJ,CAKA,gEACI,kBAAmB,CACnB,kBACJ","file":"main.546881c5.chunk.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: sans-serif;\n}\n",".App {\n text-align: center;\n}\n\n.App-logo {\n animation: App-logo-spin infinite 20s linear;\n height: 80px;\n}\n\n.App-header {\n background-color: #222;\n height: 150px;\n padding: 20px;\n color: white;\n}\n\n.App-title {\n font-size: 1.5em;\n}\n\n.App-intro {\n font-size: large;\n}\n\n@keyframes App-logo-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n}\n","body {\n background: #eee !important;\n}\n\n.wrapper {\n margin-top: 80px;\n margin-bottom: 80px;\n}\n\n.form-signin {\n max-width: 380px;\n padding: 15px 35px 45px;\n margin: 0 auto;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.1);\n}\n.form-signin .form-signin-heading,\n.form-signin .checkbox {\n margin-bottom: 30px;\n}\n.form-signin .checkbox {\n font-weight: normal;\n}\n.form-signin .form-control {\n position: relative;\n font-size: 16px;\n height: auto;\n padding: 10px;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n.form-signin .form-control:focus {\n z-index: 2;\n}\n.form-signin input[type=\"text\"] {\n margin-bottom: 20px;\n border-radius: 10px;\n}\n.form-signin input[type=\"password\"] {\n margin-bottom: 20px;\n border-radius: 10px;\n}\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/static/js/2.5772784c.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /** 14 | * @license 15 | * Lodash 16 | * Copyright OpenJS Foundation and other contributors 17 | * Released under MIT license 18 | * Based on Underscore.js 1.8.3 19 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 20 | */ 21 | 22 | /** @license React v0.19.1 23 | * scheduler.production.min.js 24 | * 25 | * Copyright (c) Facebook, Inc. and its affiliates. 26 | * 27 | * This source code is licensed under the MIT license found in the 28 | * LICENSE file in the root directory of this source tree. 29 | */ 30 | 31 | /** @license React v16.13.1 32 | * react-dom.production.min.js 33 | * 34 | * Copyright (c) Facebook, Inc. and its affiliates. 35 | * 36 | * This source code is licensed under the MIT license found in the 37 | * LICENSE file in the root directory of this source tree. 38 | */ 39 | 40 | /** @license React v16.13.1 41 | * react.production.min.js 42 | * 43 | * Copyright (c) Facebook, Inc. and its affiliates. 44 | * 45 | * This source code is licensed under the MIT license found in the 46 | * LICENSE file in the root directory of this source tree. 47 | */ 48 | -------------------------------------------------------------------------------- /src/main/resources/static/static/js/main.1025fbfc.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpfrontend=this.webpackJsonpfrontend||[]).push([[0],{44:function(e,t,r){e.exports=r(78)},52:function(e,t,r){},53:function(e,t,r){},77:function(e,t,r){},78:function(e,t,r){"use strict";r.r(t);var a={};r.r(a),r.d(a,"auth",(function(){return I})),r.d(a,"users",(function(){return G}));var n=r(1),s=r.n(n),o=r(38),u=r.n(o),c=r(23),i=r(14),l=r(80),p=(r(52),r(8)),d=r(9),h=r(18),m=r(17),f=(r(53),r(41)),v=r(11),g=r(81),b=r(5),w=r.n(b),O=r(10),k=r(19),y=r.n(k),E=new(function(){function e(){Object(p.a)(this,e)}return Object(d.a)(e,[{key:"login",value:function(){var e=Object(O.a)(w.a.mark((function e(t,r){var a,n;return w.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return(a=new FormData).append("j_username",encodeURI(t)),a.append("j_password",encodeURI(r)),e.prev=3,e.next=6,y()({url:"/auth",method:"post",headers:{"Content-Type":"application/x-www-form-urlencoded"},data:a,withCredentials:!0});case 6:if(200!==(n=e.sent).status){e.next=11;break}return e.abrupt("return",{loggedIn:!0,error:"",status:n.status});case 11:return e.abrupt("return",{loggedIn:!1,error:n.error,status:n.status});case 12:e.next=17;break;case 14:return e.prev=14,e.t0=e.catch(3),e.abrupt("return",{status:e.t0.response.status,error:e.t0.response.statusText});case 17:case"end":return e.stop()}}),e,null,[[3,14]])})));return function(t,r){return e.apply(this,arguments)}}()},{key:"logout",value:function(){var e=Object(O.a)(w.a.mark((function e(){var t;return w.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,y()({url:"/logout",method:"post",headers:{"Content-Type":"application/x-www-form-urlencoded"},withCredentials:!0});case 3:if(200!==(t=e.sent).status){e.next=8;break}return e.abrupt("return",{loggedIn:!0,error:"",status:t.status});case 8:return e.abrupt("return",{loggedIn:!1,error:"",status:t.status});case 9:e.next=14;break;case 11:return e.prev=11,e.t0=e.catch(0),e.abrupt("return",{status:e.t0.response.status,error:e.t0.response.statusText});case 14:case"end":return e.stop()}}),e,null,[[0,11]])})));return function(){return e.apply(this,arguments)}}()}]),e}()),j="auth.AUTH_ERROR",x="auth.SERVER_ERROR";var N={loggedIn:!1,username:void 0,error:""};function I(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:N,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(t.type){case"auth.LOGGED_IN":return{loggedIn:!0,username:t.username,error:""};case"auth.LOGGING_IN":case"auth.LOGGED_OUT":return{loggedIn:!1,username:void 0,error:""};case j:return{loggedIn:!1,username:void 0,error:"Authentication Error"};case x:return{loggedIn:!1,username:void 0,error:"Server Error"};default:return e}}function C(e){return{loggedIn:e.auth.loggedIn,username:e.auth.username,error:e.auth.error}}r(73);var S=new(function(){function e(){Object(p.a)(this,e)}return Object(d.a)(e,[{key:"getUsers",value:function(){var e=Object(O.a)(w.a.mark((function e(){var t;return w.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,y()({url:"/graphql",method:"post",data:{query:"\n query {\n allUsers {\n id\n firstName\n lastName\n totalSpent\n maximumSpent\n }\n } \n "},withCredentials:!0});case 3:if(t=e.sent,console.log(t.data.data.users),200!==t.status){e.next=9;break}return e.abrupt("return",{users:t.data.data.allUsers,status:t.status});case 9:return e.abrupt("return",{loggedIn:!1,error:t.error,status:t.status});case 10:e.next=15;break;case 12:return e.prev=12,e.t0=e.catch(0),e.abrupt("return",{status:e.t0.response.status,error:e.t0.response.statusText});case 15:case"end":return e.stop()}}),e,null,[[0,12]])})));return function(){return e.apply(this,arguments)}}()}]),e}()),L="users.LOADED";var _={users:[]};function G(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:_,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};switch(t.type){case"users.LOADING":return{users:[]};case L:return{users:t.users};default:return e}}function U(e){return{users:e.users.users}}var D=r(82),T=r(79),R=r(15),A=r(43),H=(r(75),function(e){Object(h.a)(r,e);var t=Object(m.a)(r);function r(e){var a;return Object(p.a)(this,r),(a=t.call(this,e)).handleLogout=a.handleLogout.bind(Object(v.a)(a)),a.props.dispatch(function(){var e=Object(O.a)(w.a.mark((function e(t,r){var a,n,s;return w.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t({type:"users.LOADING"}),e.next=3,S.getUsers();case 3:a=e.sent,n=a.users,s=a.status,e.t0=s,e.next=200===e.t0?9:401===e.t0?11:13;break;case 9:return t({type:L,users:n}),e.abrupt("break",14);case 11:return t({type:j,status:s}),e.abrupt("break",14);case 13:t({type:x,status:s});case 14:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}()),a}return Object(d.a)(r,[{key:"handleLogout",value:function(){this.props.dispatch(function(){var e=Object(O.a)(w.a.mark((function e(t,r){var a,n,s;return w.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,E.logout();case 2:a=e.sent,n=a.status,s=a.error,e.t0=n,e.next=200===e.t0?8:401===e.t0?10:12;break;case 8:return t({type:"auth.LOGGED_OUT",username:void 0}),e.abrupt("break",13);case 10:return t({type:j,status:n,error:s}),e.abrupt("break",13);case 12:t({type:x,status:n,error:s});case 13:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}())}},{key:"renderNavBar",value:function(){return s.a.createElement("nav",{className:"navbar navbar-expand-sm bg-dark navbar-dark sticky-top"},s.a.createElement("ul",{className:"navbar-nav"},s.a.createElement("li",{className:"nav-item"},s.a.createElement(g.a,{className:"nav-link",to:"/"},"Home"))),s.a.createElement("ul",{className:"navbar-nav ml-auto"},s.a.createElement("li",{className:"nav-item"},s.a.createElement("button",{className:"btn btn-sm btn-danger",onClick:this.handleLogout},"Logout"))))}},{key:"render",value:function(){return!1===this.props.loggedIn?s.a.createElement(D.a,{to:"/login",push:!0}):s.a.createElement("div",null,this.renderNavBar(),s.a.createElement("h2",null,"Users"),s.a.createElement("div",null,s.a.createElement(A.a,{data:this.props.users,columns:[{Header:"ID",accessor:"id",width:100},{Header:"First Name",accessor:"firstName"},{Header:"Last Name",accessor:"lastName"},{Header:"Total Spent",accessor:"totalSpent"},{Header:"Maximum",accessor:"maximumSpent"}],defaultPageSize:10,className:"-striped -highlight"})))}}]),r}(s.a.Component));var W=Object(T.a)(Object(R.a)((function(e){return{loggedIn:C(e).loggedIn,users:U(e).users}}))(H)),B=(r(77),function(e){Object(h.a)(r,e);var t=Object(m.a)(r);function r(e){var a;return Object(p.a)(this,r),(a=t.call(this,e)).state={username:"",password:"",submitted:!1},a.usernameChange=a.usernameChange.bind(Object(v.a)(a)),a.passwordChange=a.passwordChange.bind(Object(v.a)(a)),a.handleSubmit=a.handleSubmit.bind(Object(v.a)(a)),a}return Object(d.a)(r,[{key:"usernameChange",value:function(e){var t=e.target,r=(t.name,t.value);this.setState({username:r}),this.setState({submitted:!1})}},{key:"passwordChange",value:function(e){var t=e.target,r=(t.name,t.value);this.setState({password:r}),this.setState({submitted:!1})}},{key:"handleSubmit",value:function(e){e.preventDefault(),this.setState({submitted:!0});var t=this.state,r=t.username,a=t.password;this.props.dispatch(function(e,t){return function(){var r=Object(O.a)(w.a.mark((function r(a,n){var s,o,u;return w.a.wrap((function(r){for(;;)switch(r.prev=r.next){case 0:return a({type:"auth.LOGGING_IN"}),r.next=3,E.login(e,t);case 3:s=r.sent,o=s.status,u=s.error,r.t0=o,r.next=200===r.t0?9:401===r.t0?11:13;break;case 9:return a({type:"auth.LOGGED_IN",username:e}),r.abrupt("break",14);case 11:return a({type:j,status:o,error:u}),r.abrupt("break",14);case 13:a({type:x,status:o,error:u});case 14:case"end":return r.stop()}}),r)})));return function(e,t){return r.apply(this,arguments)}}()}(r,a))}},{key:"render",value:function(){return!0===this.props.loggedIn?s.a.createElement(D.a,{to:"/",push:!0}):s.a.createElement("div",{className:"wrapper"},s.a.createElement("form",{className:"form-signin",onSubmit:this.handleSubmit},s.a.createElement("h2",{className:"form-signin-heading"},"Please login"),s.a.createElement("input",{placeholder:"Use 'user1'",type:"text",name:"username",onChange:this.usernameChange,className:"form-control"}),s.a.createElement("input",{placeholder:"Use 'password123'",type:"password",name:"password",onChange:this.passwordChange,className:"form-control"}),s.a.createElement("hr",null),s.a.createElement("button",{className:"btn btn-lg btn-primary btn-block",type:"submit"},"Login")))}}]),r}(s.a.Component));var q=Object(T.a)(Object(R.a)((function(e){return C(e)}))(B)),P=function(e){Object(h.a)(r,e);var t=Object(m.a)(r);function r(){return Object(p.a)(this,r),t.apply(this,arguments)}return Object(d.a)(r,[{key:"render",value:function(){return s.a.createElement("div",{className:"App"},s.a.createElement(f.a,{exact:!0,path:"/",component:W}),s.a.createElement(f.a,{exact:!0,path:"/login",component:q}))}}]),r}(n.Component);var F=Object(T.a)(Object(c.b)((function(e){return{loggedIn:C(e).loggedIn}}))(P)),J=Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));function M(e){navigator.serviceWorker.register(e).then((function(e){e.onupdatefound=function(){var t=e.installing;t.onstatechange=function(){"installed"===t.state&&(navigator.serviceWorker.controller?console.log("New content is available; please refresh."):console.log("Content is cached for offline use."))}}})).catch((function(e){console.error("Error during service worker registration:",e)}))}var V=r(42),X=r.n(V),z=window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__||i.d,Q=Object(i.e)(Object(i.c)(a),z(Object(i.a)(X.a)));u.a.render(s.a.createElement(c.a,{store:Q},s.a.createElement(l.a,null,s.a.createElement(F,null))),document.getElementById("root")),function(){if("serviceWorker"in navigator){if(new URL("",window.location).origin!==window.location.origin)return;window.addEventListener("load",(function(){var e="".concat("","/service-worker.js");J?(!function(e){fetch(e).then((function(t){404===t.status||-1===t.headers.get("content-type").indexOf("javascript")?navigator.serviceWorker.ready.then((function(e){e.unregister().then((function(){window.location.reload()}))})):M(e)})).catch((function(){console.log("No internet connection found. App is running in offline mode.")}))}(e),navigator.serviceWorker.ready.then((function(){console.log("This web app is being served cache-first by a service worker. To learn more, visit https://goo.gl/SC7cgQ")}))):M(e)}))}}()}},[[44,1,2]]]); 2 | //# sourceMappingURL=main.1025fbfc.chunk.js.map -------------------------------------------------------------------------------- /src/main/resources/static/static/js/main.1025fbfc.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["services/authServices.js","store/auth/actionTypes.js","store/auth/reducer.js","services/userService.js","store/users/actionTypes.js","store/users/reducer.js","components/Home.js","store/users/actions.js","store/auth/actions.js","components/Login.js","App.js","registerServiceWorker.js","index.js"],"names":["username","password","fd","FormData","append","encodeURI","axios","url","method","headers","data","withCredentials","response","status","loggedIn","error","statusText","AUTH_ERROR","SERVER_ERROR","initialState","undefined","reduce","state","action","type","types","getLoginDetails","auth","query","console","log","users","allUsers","LOADED","reducer","actionTypes","getUsers","Home","props","handleLogout","bind","dispatch","getState","a","userService","authTypes","this","authService","logout","className","NavLink","to","onClick","Redirect","push","renderNavBar","columns","Header","accessor","width","defaultPageSize","React","Component","withRouter","connect","authSelectors","userSelectors","Login","submitted","usernameChange","passwordChange","handleSubmit","event","target","value","name","setState","preventDefault","login","authActiokns","onSubmit","placeholder","onChange","App","Route","exact","path","component","isLocalhost","Boolean","window","location","hostname","match","registerValidSW","swUrl","navigator","serviceWorker","register","then","registration","onupdatefound","installingWorker","installing","onstatechange","controller","catch","composeEnhancers","__REDUX_DEVTOOLS_EXTENSION_COMPOSE__","compose","store","createStore","combineReducers","reducers","applyMiddleware","thunk","ReactDOM","render","BrowserRouter","document","getElementById","URL","process","origin","addEventListener","fetch","get","indexOf","ready","unregister","reload","checkValidServiceWorker","registerServiceWorker"],"mappings":"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","file":"static/js/main.1025fbfc.chunk.js","sourcesContent":["import axios from 'axios'\n\nclass AuthService {\n\n async login(username, password) {\n var fd = new FormData()\n fd.append('j_username', encodeURI(username))\n fd.append('j_password', encodeURI(password))\n\n try {\n const response = await axios({\n url: '/auth',\n method: 'post',\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n data: fd,\n withCredentials: true\n });\n\n if (response.status === 200) {\n return { loggedIn: true, error:'', status: response.status }\n } else {\n return { loggedIn: false, error:response.error, status: response.status }\n }\n } catch (err) {\n return { status: err.response.status, error: err.response.statusText }\n }\n }\n\n async logout() {\n\n try {\n const response = await axios({\n url: '/logout',\n method: 'post',\n headers: {'Content-Type': 'application/x-www-form-urlencoded'},\n withCredentials: true\n });\n\n if (response.status === 200) {\n return { loggedIn: true, error:'', status: response.status }\n } else {\n return { loggedIn: false, error:'', status: response.status }\n }\n } catch (err) {\n return { status: err.response.status, error: err.response.statusText }\n }\n }\n}\n\nexport default new AuthService()","export const LOGGING_IN = \"auth.LOGGING_IN\"\nexport const LOGGED_IN = \"auth.LOGGED_IN\"\nexport const LOGGED_OUT = \"auth.LOGGED_OUT\"\nexport const AUTH_ERROR = \"auth.AUTH_ERROR\"\nexport const SERVER_ERROR = \"auth.SERVER_ERROR\"\n","\nimport * as types from './actionTypes'\n\nconst initialState = {\n loggedIn: false,\n username: undefined,\n error: ''\n};\n\nexport default function reduce(state=initialState, action={}) {\n switch (action.type) {\n case types.LOGGED_IN:\n return {loggedIn: true, username : action.username, error:''};\n case types.LOGGING_IN:\n case types.LOGGED_OUT:\n return {loggedIn: false, username : undefined, error:''};\n case types.AUTH_ERROR:\n return {loggedIn: false, username : undefined, error:'Authentication Error'};\n case types.SERVER_ERROR:\n return {loggedIn: false, username : undefined, error:'Server Error'};\n default:\n return state\n }\n}\n\nexport function getLoginDetails(state) {\n return {loggedIn: state.auth.loggedIn, username: state.auth.username, error: state.auth.error }\n}\n","import axios from 'axios'\nimport * as _ from 'lodash';\n\nclass UserService {\n\n async getUsers() {\n try {\n const response = await axios({\n url: '/graphql',\n method: 'post',\n data: {\n query: `\n query {\n allUsers {\n id\n firstName\n lastName\n totalSpent\n maximumSpent\n }\n } \n `\n },\n withCredentials: true\n });\n\n console.log(response.data.data.users)\n\n if (response.status === 200) {\n return { users: response.data.data.allUsers, status: response.status }\n } else {\n return { loggedIn: false, error:response.error, status: response.status }\n }\n } catch (err) {\n return { status: err.response.status, error: err.response.statusText }\n }\n }\n}\n\nexport default new UserService()\n","export const LOADING = \"users.LOADING\"\nexport const LOADED = \"users.LOADED\"\n","\nimport * as actionTypes from './actionTypes';\n\nconst initialState = {\n users: []\n};\n\nexport default function reducer(state=initialState, action={}) {\n switch (action.type) {\n case actionTypes.LOADING:\n return { users: [] }\n case actionTypes.LOADED:\n return { users: action.users }\n default:\n return state;\n }\n}\n\nexport function getUsers(state) {\n return { users : state.users.users }\n}\n","import React from 'react';\nimport { NavLink } from 'react-router-dom';\nimport * as authActions from '../store/auth/actions';\nimport * as authSelectors from '../store/auth/reducer';\nimport * as userActions from '../store/users/actions';\nimport * as userSelectors from '../store/users/reducer';\nimport { withRouter, Redirect } from \"react-router\";\nimport connect from \"react-redux/es/connect/connect\";\nimport ReactTable from 'react-table';\nimport 'react-table/react-table.css';\n\nclass Home extends React.Component {\n\n constructor(props) {\n super(props)\n this.handleLogout = this.handleLogout.bind(this)\n this.props.dispatch(userActions.getUsers())\n }\n\n handleLogout() {\n this.props.dispatch(authActions.logout())\n }\n\n renderNavBar() {\n return (\n )\n }\n\n render() {\n if (this.props.loggedIn === false) {\n return \n }\n\n return (\n
\n {this.renderNavBar()}\n

Users

\n
\n \n \n
\n
\n );\n }\n}\n\nfunction mapStateToProps(state) {\n return {\n loggedIn : authSelectors.getLoginDetails(state).loggedIn,\n users : userSelectors.getUsers(state).users\n }\n}\n\nexport default withRouter(connect(mapStateToProps)(Home));\n","import userService from '../../services/userService';\nimport * as types from './actionTypes';\nimport * as authTypes from '../auth/actionTypes';\n\nexport function getUsers() {\n return async (dispatch, getState) => {\n\n dispatch({type: types.LOADING});\n\n const { users, status } = await userService.getUsers();\n\n switch (status) {\n case 200:\n dispatch({type: types.LOADED, users });\n break;\n case 401:\n dispatch({type: authTypes.AUTH_ERROR, status });\n break;\n default:\n dispatch({type: authTypes.SERVER_ERROR, status });\n }\n }\n}\n","import authService from '../../services/authServices'\nimport * as types from './actionTypes'\n\nexport function authenticate(username, password) {\n return async(dispatch, getState) => {\n dispatch({type: types.LOGGING_IN});\n\n const { status, error } = await authService.login(username, password)\n\n switch (status) {\n case 200:\n dispatch({type: types.LOGGED_IN, username });\n break;\n case 401:\n dispatch({type: types.AUTH_ERROR, status, error });\n break;\n default:\n dispatch({type: types.SERVER_ERROR, status, error });\n }\n }\n}\n\nexport function logout() {\n return async (dispatch, getState) => {\n const { status, error } = await authService.logout();\n\n switch (status) {\n case 200:\n dispatch({type: types.LOGGED_OUT, username:undefined});\n break;\n case 401:\n dispatch({type: types.AUTH_ERROR, status, error });\n break;\n default:\n dispatch({type: types.SERVER_ERROR, status, error });\n }\n }\n}","import React from 'react'\nimport * as authSelectors from '../store/auth/reducer'\nimport * as authActiokns from '../store/auth/actions'\nimport { withRouter, Redirect } from \"react-router\";\nimport connect from \"react-redux/es/connect/connect\";\nimport './css/Login.css'\n\nclass Login extends React.Component {\n\n constructor(props) {\n super(props);\n this.state = {\n username: '',\n password: '',\n submitted: false\n };\n this.usernameChange = this.usernameChange.bind(this);\n this.passwordChange = this.passwordChange.bind(this);\n this.handleSubmit = this.handleSubmit.bind(this);\n }\n\n usernameChange(event) {\n const { name, value } = event.target;\n this.setState( {username: value});\n this.setState( {submitted: false});\n }\n\n passwordChange(event) {\n const { name, value } = event.target;\n this.setState( {password: value});\n this.setState( {submitted: false});\n }\n\n handleSubmit(event) {\n event.preventDefault();\n this.setState( {submitted: true});\n const { username, password } = this.state;\n this.props.dispatch(authActiokns.authenticate(username, password));\n }\n\n render() {\n if (this.props.loggedIn === true) {\n return \n }\n\n return (\n
\n
\n

Please login

\n \n \n
\n \n
\n
\n )\n }\n}\n\nfunction mapStateToProps(state) {\n return authSelectors.getLoginDetails(state)\n}\n\nexport default withRouter(connect(mapStateToProps)(Login));\n","import React, { Component } from 'react';\nimport './App.css';\nimport { Route } from 'react-router-dom'\nimport Home from './components/Home'\nimport Login from './components/Login'\nimport * as authSelectors from './store/auth/reducer'\nimport { connect } from 'react-redux'\nimport { withRouter } from 'react-router'\n\nclass App extends Component {\n render() {\n return (\n
\n \n \n
\n );\n }\n}\n\nfunction mapStateToProps(state) {\n return { loggedIn : authSelectors.getLoginDetails(state).loggedIn}\n}\n\nexport default withRouter(connect(mapStateToProps)(App));\n","// In production, we register a service worker to serve assets from local cache.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on the \"N+1\" visit to a page, since previously\n// cached resources are updated in the background.\n\n// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.\n// This link also includes instructions on opting out of this behavior.\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport default function register() {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Lets check if a service worker still exists or not.\n checkValidServiceWorker(swUrl);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://goo.gl/SC7cgQ'\n );\n });\n } else {\n // Is not local host. Just register service worker\n registerValidSW(swUrl);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the old content will have been purged and\n // the fresh content will have been added to the cache.\n // It's the perfect time to display a \"New content is\n // available; please refresh.\" message in your web app.\n console.log('New content is available; please refresh.');\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n if (\n response.status === 404 ||\n response.headers.get('content-type').indexOf('javascript') === -1\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Provider } from 'react-redux'\nimport { createStore, combineReducers, applyMiddleware, compose } from 'redux'\nimport { BrowserRouter } from 'react-router-dom'\nimport './index.css';\nimport App from './App';\nimport registerServiceWorker from './registerServiceWorker';\nimport thunk from 'redux-thunk'\nimport * as reducers from './store/reducers'\n\nconst composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\nconst store = createStore(combineReducers(reducers), composeEnhancers(applyMiddleware(thunk)))\n\nReactDOM.render(\n \n \n \n \n , document.getElementById('root'));\nregisterServiceWorker();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /src/main/resources/static/static/js/runtime-main.c8a21426.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,l=r[0],i=r[1],a=r[2],c=0,s=[];c