├── .DS_Store ├── .env.template ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── _config.yml ├── app.js ├── bank-app-backend ├── .gitignore ├── README.md ├── common │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── ibm │ │ └── codey │ │ └── bank │ │ ├── BaseResource.java │ │ ├── accounts │ │ └── json │ │ │ ├── UserRegistration.java │ │ │ └── UserRegistrationInfo.java │ │ ├── catalog │ │ ├── KnativeService.java │ │ ├── UserService.java │ │ └── json │ │ │ ├── CreateTransactionDefinition.java │ │ │ └── RewardTransactionDefinition.java │ │ └── interceptor │ │ ├── LoggingInterceptor.java │ │ ├── SecurityInterceptor.java │ │ └── binding │ │ └── RequiresAuthorization.java ├── integration-tests │ ├── pom.xml │ └── src │ │ └── test │ │ └── java │ │ └── it │ │ └── com │ │ └── ibm │ │ └── codey │ │ └── loyalty │ │ ├── EndpointTestBase.java │ │ ├── accounts │ │ ├── UserEndpointTest.java │ │ └── UserEventsEndpointTest.java │ │ ├── catalog │ │ └── EventsEndpointTest.java │ │ └── util │ │ └── TestSecurityHelper.java ├── pom.xml ├── transaction-service │ ├── Dockerfile │ ├── deployment.yaml │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── ibm │ │ │ └── codey │ │ │ └── bank │ │ │ ├── LivenessCheck.java │ │ │ ├── LoyaltyApplication.java │ │ │ ├── ReadinessCheck.java │ │ │ └── catalog │ │ │ ├── TransactionResource.java │ │ │ ├── dao │ │ │ └── TransactionDao.java │ │ │ └── models │ │ │ ├── Category.java │ │ │ ├── Transaction.java │ │ │ ├── TransactionListener.java │ │ │ └── TransactionPK.java │ │ ├── liberty │ │ └── config │ │ │ ├── bootstrap.properties │ │ │ ├── jvm.options │ │ │ └── server.xml │ │ ├── resources │ │ ├── META-INF │ │ │ ├── orm.xml │ │ │ └── persistence.xml │ │ └── security │ │ │ └── digicert-root-ca.jks │ │ └── webapp │ │ └── WEB-INF │ │ ├── beans.xml │ │ └── web.xml └── user-service │ ├── Dockerfile │ ├── deployment.yaml │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── com │ │ └── ibm │ │ └── codey │ │ └── bank │ │ ├── LivenessCheck.java │ │ ├── LoyaltyApplication.java │ │ ├── ReadinessCheck.java │ │ └── accounts │ │ ├── UserResource.java │ │ ├── dao │ │ └── UserDao.java │ │ └── models │ │ └── User.java │ ├── liberty │ └── config │ │ ├── bootstrap.properties │ │ ├── jvm.options │ │ └── server.xml │ ├── resources │ ├── META-INF │ │ ├── orm.xml │ │ └── persistence.xml │ └── security │ │ └── digicert-root-ca.jks │ └── webapp │ └── WEB-INF │ ├── beans.xml │ └── web.xml ├── bank-knative-service ├── Dockerfile ├── deployment.yaml ├── index.js └── package.json ├── bank-user-cleanup-utility ├── .gitignore ├── Dockerfile ├── README.md ├── job.yaml ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── ibm │ └── codey │ └── loyalty │ ├── AccountDeletionProcessor.java │ └── external │ ├── appid │ ├── AppIDService.java │ ├── AppIDServiceGetUserRoleResponse.java │ └── AppIDServiceGetUsersResponse.java │ └── iam │ ├── IAMTokenService.java │ └── IAMTokenServiceResponse.java ├── data_model ├── .gitignore ├── Dockerfile ├── README.md ├── cc_schema.sql ├── drop-schema │ ├── Dockerfile │ ├── drop.sql │ └── job.yaml └── job.yaml ├── deployment.yaml ├── design ├── icons.ai ├── iconset.svg └── stride.ai ├── images ├── add-application.png ├── allow-sign-in.png ├── appdiag1.png ├── create-role.png ├── disable-email.png ├── kiali.png ├── logdna.png ├── logdna_appid_select.png ├── loyalty-1.png ├── loyalty-bank.png ├── loyalty-phone.png ├── loyalty-profile.png ├── loyalty-spending.png ├── loyalty-transactions.png ├── loyalty-user-role-added.png ├── loyalty-user-role.png ├── loyalty-user-test.png ├── new-app.png ├── pattern-flow-diag.png ├── schema-1.png ├── sim1.png ├── sim2.png ├── sim3.png ├── sim4.png ├── simulator_checkin.png ├── simulator_events.png ├── simulator_katy.png ├── simulator_main.png ├── user-creation.png └── writer-credentials.png ├── manifest.yml ├── package-lock.json ├── package.json ├── pipelines ├── ACKNOWLEDGEMENT.md ├── bank-pipelinerun.yaml ├── bank-pvc.yaml ├── deployments │ ├── transaction.yaml │ ├── ui.yaml │ └── user.yaml ├── example-bank-pipeline.yaml ├── sonarqube.yaml └── tasks │ ├── mvn-task-settings.yaml │ ├── mvn-task.yaml │ ├── oc-task.yaml │ ├── task-s2i-java.yaml │ └── task-s2i-nodejs.yaml ├── public ├── .DS_Store ├── favicon.ico ├── images │ ├── account-deselected.svg │ ├── account-selected.svg │ ├── admin.svg │ ├── bank.svg │ ├── banklogo.svg │ ├── cafe.svg │ ├── cloud.svg │ ├── deploy-rules.svg │ ├── eventlist-deselected.svg │ ├── eventlist-selected.svg │ ├── flash.svg │ ├── fuel.svg │ ├── groceries.svg │ ├── istio.svg │ ├── lightbulb.svg │ ├── loop.svg │ ├── loyaltylogo.svg │ ├── newyork.svg │ ├── programming.svg │ ├── reservation-deselected.svg │ ├── reservation-selected.svg │ ├── restaurant.svg │ ├── rideshare.svg │ ├── secure.svg │ ├── statistics-deselected.svg │ ├── statistics-selected.svg │ ├── threat.svg │ ├── transactions-deselected.svg │ └── transactions-selected.svg ├── index.html ├── javascript │ ├── account.js │ ├── asset.js │ ├── clientHelpers │ │ ├── demoaccounts-devmode.js │ │ ├── demoaccounts.js │ │ ├── libertyclient-devmode.js │ │ ├── libertyclient.js │ │ └── localstoragehelper.js │ ├── home.js │ ├── login.js │ ├── loyalty.js │ ├── navigation.js │ ├── navigationbutton.js │ ├── phone.js │ ├── spinner.js │ ├── statistics.js │ ├── tile.js │ ├── transaction.js │ ├── transactions.js │ └── welcome.js ├── schedule.html ├── style │ └── loyalty.css └── transaction.html ├── routes └── createUser.js └── scripts ├── createappid.sh ├── createsecrets.sh ├── creditdb.yaml ├── deleteresources.sh ├── deploy-db.sh ├── installServerlessOperator.sh ├── mapAppIDtoSecrets.sh ├── pg_csv.yaml ├── pg_deploy.yaml ├── pg_operatorgroup.yaml └── pg_sub.yaml /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/.DS_Store -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | APP_ID_IAM_APIKEY= 2 | APP_ID_MANAGEMENT_URL= 3 | APP_ID_CLIENT_ID= 4 | APP_ID_CLIENT_SECRET= 5 | APP_ID_TOKEN_URL= 6 | PROXY_USER_MICROSERVICE=user-service:9080 7 | PROXY_TRANSACTION_MICROSERVICE=transaction-service:9080 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .~/ 4 | .DS_Store 5 | .vscode 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. 6 | 7 | In addition to the issue tracker, [#journeys on 8 | Slack](https://dwopen.slack.com) is the best way to get into contact with the 9 | project's maintainers. 10 | 11 | To contribute code, documentation, or tests, please submit a pull request to 12 | the GitHub repository. Generally, we expect two maintainers to review your pull 13 | request before it is approved for merging. For more details, see the 14 | [MAINTAINERS](MAINTAINERS.md) page. 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/node:12.16.1-alpine 2 | 3 | ENV NODE_ENV production 4 | ENV PORT 8080 5 | 6 | RUN mkdir /app 7 | COPY public /app/public 8 | COPY app.js /app/ 9 | COPY package.json /app/package.json 10 | COPY routes /app/routes 11 | WORKDIR /app 12 | RUN npm install 13 | 14 | CMD ["node", "app.js"] 15 | 16 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers Guide 2 | 3 | This guide is intended for maintainers - anybody with commit access to one or 4 | more Code Pattern repositories. 5 | 6 | ## Methodology 7 | 8 | This repository does not have a traditional release management cycle, but 9 | should instead be maintained as a useful, working, and polished reference at 10 | all times. While all work can therefore be focused on the master branch, the 11 | quality of this branch should never be compromised. 12 | 13 | The remainder of this document details how to merge pull requests to the 14 | repositories. 15 | 16 | ## Merge approval 17 | 18 | The project maintainers use LGTM (Looks Good To Me) in comments on the pull 19 | request to indicate acceptance prior to merging. A change requires LGTMs from 20 | two project maintainers. If the code is written by a maintainer, the change 21 | only requires one additional LGTM. 22 | 23 | ## Reviewing Pull Requests 24 | 25 | We recommend reviewing pull requests directly within GitHub. This allows a 26 | public commentary on changes, providing transparency for all users. When 27 | providing feedback be civil, courteous, and kind. Disagreement is fine, so long 28 | as the discourse is carried out politely. If we see a record of uncivil or 29 | abusive comments, we will revoke your commit privileges and invite you to leave 30 | the project. 31 | 32 | During your review, consider the following points: 33 | 34 | ### Does the change have positive impact? 35 | 36 | Some proposed changes may not represent a positive impact to the project. Ask 37 | whether or not the change will make understanding the code easier, or if it 38 | could simply be a personal preference on the part of the author (see 39 | [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)). 40 | 41 | Pull requests that do not have a clear positive impact should be closed without 42 | merging. 43 | 44 | ### Do the changes make sense? 45 | 46 | If you do not understand what the changes are or what they accomplish, ask the 47 | author for clarification. Ask the author to add comments and/or clarify test 48 | case names to make the intentions clear. 49 | 50 | At times, such clarification will reveal that the author may not be using the 51 | code correctly, or is unaware of features that accommodate their needs. If you 52 | feel this is the case, work up a code sample that would address the pull 53 | request for them, and feel free to close the pull request once they confirm. 54 | 55 | ### Does the change introduce a new feature? 56 | 57 | For any given pull request, ask yourself "is this a new feature?" If so, does 58 | the pull request (or associated issue) contain narrative indicating the need 59 | for the feature? If not, ask them to provide that information. 60 | 61 | Are new unit tests in place that test all new behaviors introduced? If not, do 62 | not merge the feature until they are! Is documentation in place for the new 63 | feature? (See the documentation guidelines). If not do not merge the feature 64 | until it is! Is the feature necessary for general use cases? Try and keep the 65 | scope of any given component narrow. If a proposed feature does not fit that 66 | scope, recommend to the user that they maintain the feature on their own, and 67 | close the request. You may also recommend that they see if the feature gains 68 | traction among other users, and suggest they re-submit when they can show such 69 | support. 70 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | markdown: kramdown 2 | kramdown: 3 | parse_block_html: true 4 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | 3 | //------------------------------------------------------------------------------ 4 | // node.js starter application for Bluemix 5 | //------------------------------------------------------------------------------ 6 | 7 | // This application uses express as its web server 8 | // for more info, see: http://expressjs.com 9 | var express = require('express'); 10 | 11 | // cfenv provides access to your Cloud Foundry environment 12 | // for more info, see: https://www.npmjs.com/package/cfenv 13 | var cfenv = require('cfenv'); 14 | 15 | // create a new express server 16 | var app = express(); 17 | 18 | var port = process.env.PORT || 8060; 19 | 20 | let DEVMODE = process.env.DEVMODE 21 | 22 | if (DEVMODE) { 23 | app.get('/javascript/clientHelpers/libertyclient.js', (req, res) => {res.sendFile('public/javascript/clientHelpers/libertyclient-devmode.js', {root: __dirname})}) 24 | app.get('/javascript/clientHelpers/demoaccounts.js', (req, res) => {res.sendFile('public/javascript/clientHelpers/demoaccounts-devmode.js', {root: __dirname})}) 25 | } 26 | // serve the files out of ./public as our main files 27 | app.use(express.static(__dirname + '/public')); 28 | 29 | // get the app environment from Cloud Foundry 30 | var appEnv = cfenv.getAppEnv(); 31 | var log4js = require('log4js'); 32 | var logger = log4js.getLogger(); 33 | 34 | logger.level = 'debug'; 35 | logger.debug("launching bank simulated UI"); 36 | 37 | app.use(require("body-parser").json()); 38 | app.use(require("body-parser").urlencoded({extended: false})); 39 | // use createUser route 40 | 41 | if (!DEVMODE) { 42 | app.use('/demo', require('./routes/createUser')) 43 | // proxy for testing locally 44 | let proxy = require('express-http-proxy') 45 | let USER_MICROSREVICE = process.env.PROXY_USER_MICROSERVICE 46 | let TRANSACTION_MICROSERVICE = process.env.PROXY_TRANSACTION_MICROSERVICE 47 | app.use('/proxy_user', proxy(USER_MICROSREVICE)) 48 | app.use('/proxy_transaction', proxy(TRANSACTION_MICROSERVICE)) 49 | } 50 | 51 | // start server on the specified port and binding host 52 | app.listen(port); 53 | logger.debug("Listening on port ", port); 54 | -------------------------------------------------------------------------------- /bank-app-backend/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | !.keep 3 | 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | 31 | 32 | ## Local configuration files 33 | /local/config/* 34 | 35 | *.swo 36 | *.swp 37 | *.~ 38 | -------------------------------------------------------------------------------- /bank-app-backend/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Building individual microservices 3 | 4 | ### User service 5 | 6 | ``` 7 | mvn -pl :user-service -am package 8 | docker build -t user-service:1.0-SNAPSHOT user-service 9 | ``` 10 | 11 | ### Transaction service 12 | ``` 13 | mvn -pl :transaction-service -am package 14 | docker build -t transaction-service:1.0-SNAPSHOT transaction-service 15 | ``` 16 | 17 | 18 | ## Configuration 19 | 20 | ### Secrets 21 | 22 | ``` 23 | kubectl create secret generic bank-db-secret --from-literal=DB_SERVERNAME= --from-literal=DB_PORTNUMBER= --from-literal=DB_DATABASENAME=ibmclouddb --from-literal=DB_USER= --from-literal=DB_PASSWORD= 24 | kubectl create secret generic bank-oidc-secret --from-literal=OIDC_JWKENDPOINTURL=/publickeys --from-literal=OIDC_ISSUERIDENTIFIER= --from-literal=OIDC_AUDIENCES= 25 | ``` 26 | 27 | 28 | ## Curl commands 29 | 30 | ### Users 31 | 32 | ``` 33 | curl -X POST -H "Authorization: Bearer " -H "Content-Type: application/json" -d "{\"consentGiven\": \"true\"}" -k https://localhost:9443/bank/v1/users 34 | 35 | curl -X GET "Authorization: Bearer " -k https://localhost:9443/bank/v1/users/self 36 | 37 | curl -X PUT "Authorization: Bearer " -H "Content-Type: application/json" -d "{\"consentGiven\": \"false\"}" -k https://localhost:9443/bank/v1/users/self 38 | 39 | curl -X DELETE "Authorization: Bearer " -k https://localhost:9443/bank/v1/users/self 40 | ``` 41 | 42 | 43 | ### User Events 44 | 45 | ``` 46 | curl -X POST "Authorization: Bearer " -H "Content-Type: application/json" -d "{\"eventId\": \"871859e4-9fca-4bcf-adb5-e7d063d0747e\"}" -k https://localhost:9443/bank/v1/userEvents 47 | 48 | curl -X GET "Authorization: Bearer " -k https://localhost:9443/bank/v1/userEvents/self 49 | 50 | curl -X GET "Authorization: Bearer " -k https://localhost:9443/bank/v1/userEvents/self/info 51 | ``` 52 | 53 | 54 | ### Events 55 | 56 | ``` 57 | curl -X POST "Authorization: Bearer " -H "Content-Type: application/json" -d "{\"eventName\": \"Event name\", \"pointValue\": 100}" -k https://localhost:9444/bank/v1/events 58 | 59 | curl -X GET "Authorization: Bearer " -k https://localhost:9444/bank/v1/events/{eventId} 60 | 61 | curl -X PUT "Authorization: Bearer " -H "Content-Type: application/json" -d "{\"eventName\": \"Event name\", \"pointValue\": 100}" -k https://localhost:9444/bank/v1/events/{eventId} 62 | 63 | curl -X GET "Authorization: Bearer " -k https://localhost:9444/bank/v1/events 64 | 65 | curl -X GET "Authorization: Bearer " -k "https://localhost:9444/bank/v1/events?id=&id=&id=" 66 | 67 | ``` 68 | 69 | ## Running the integration tests 70 | 71 | ### Set environment variables 72 | 73 | Base URL where users and events services are deployed 74 | ``` 75 | export USERS_BASEURL=http://: 76 | export EVENTS_BASEURL=http://: 77 | ``` 78 | 79 | Prefix for test user names and the password they should use. These users are created dynamically by the tests. 80 | ``` 81 | export TEST_USER_PREFIX= 82 | export TEST_PASSWORD= 83 | ``` 84 | 85 | Admin user name and password. This user name must exist in App Id prior to running the test and must have the admin role. 86 | ``` 87 | export TEST_ADMIN_USER= 88 | export TEST_ADMIN_PASSWORD= 89 | ``` 90 | 91 | App Id service URL. Change to correct URL for the region where your App Id instance is deployed. 92 | ``` 93 | export APPID_SERVICE_URL=https://us-south.appid.cloud.ibm.com 94 | ``` 95 | 96 | App Id tenant id, client id, and client password (secret) 97 | ``` 98 | export APPID_TENANTID= 99 | export OIDC_CLIENTID= 100 | export OIDC_CLIENTPASSWORD= 101 | export OIDC_ISSUERIDENTIFIER=%APPID_SERVICE_URL%/%APPID_TENANTID% 102 | ``` 103 | 104 | IAM API key (needed for authentication to App Id) 105 | ``` 106 | export IAM_APIKEY= 107 | export IAM_SERVICE_URL=https://iam.cloud.ibm.com/identity/token 108 | ``` 109 | 110 | 111 | ### Run the tests 112 | 113 | ``` 114 | mvn -pl :integration-tests -am verify 115 | ``` -------------------------------------------------------------------------------- /bank-app-backend/common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | com.ibm.codey.bank 10 | parent 11 | 1.0-SNAPSHOT 12 | 13 | 14 | com.ibm.codey.bank 15 | common 16 | 1.0-SNAPSHOT 17 | jar 18 | 19 | 20 | 21 | 22 | io.openliberty.features 23 | microProfile-3.0 24 | esa 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | 1.18.16 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/BaseResource.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.eclipse.microprofile.jwt.Claim; 6 | import org.eclipse.microprofile.jwt.Claims; 7 | 8 | public class BaseResource { 9 | 10 | @Inject 11 | @Claim("sub") 12 | private String subject; 13 | 14 | @Inject 15 | @Claim(standard = Claims.raw_token) 16 | private String rawToken; 17 | 18 | protected String getCallerSubject() { 19 | return subject; 20 | } 21 | 22 | protected String getCallerCredentials() { 23 | return "Bearer " + rawToken; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/accounts/json/UserRegistration.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.accounts.json; 2 | 3 | import javax.json.bind.annotation.JsonbProperty; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | @Getter @Setter @ToString 10 | public class UserRegistration { 11 | 12 | @JsonbProperty 13 | private boolean consentGiven; 14 | 15 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/accounts/json/UserRegistrationInfo.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.accounts.json; 2 | 3 | import javax.json.bind.annotation.JsonbProperty; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | @Getter @Setter @ToString 10 | public class UserRegistrationInfo { 11 | 12 | @JsonbProperty 13 | private String userId; 14 | 15 | @JsonbProperty 16 | private boolean consentGiven; 17 | 18 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/catalog/KnativeService.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.concurrent.CompletionStage; 6 | 7 | import javax.enterprise.context.Dependent; 8 | import javax.ws.rs.HeaderParam; 9 | import javax.ws.rs.POST; 10 | import javax.ws.rs.Path; 11 | import javax.ws.rs.QueryParam; 12 | 13 | import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; 14 | 15 | @Dependent 16 | @RegisterRestClient 17 | public interface KnativeService { 18 | 19 | @POST 20 | @Path("process") 21 | public CompletionStage processTransaction(@QueryParam("transactionId") String transactionId, @QueryParam("category") String category, @QueryParam("amount") String amount); 22 | 23 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/catalog/UserService.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import javax.enterprise.context.Dependent; 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.HeaderParam; 9 | import javax.ws.rs.PathParam; 10 | import javax.ws.rs.Path; 11 | import javax.ws.rs.Produces; 12 | import javax.ws.rs.QueryParam; 13 | import javax.ws.rs.core.MediaType; 14 | import javax.ws.rs.core.Response; 15 | 16 | import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; 17 | 18 | import com.ibm.codey.bank.accounts.json.UserRegistration; 19 | import com.ibm.codey.bank.accounts.json.UserRegistrationInfo; 20 | 21 | @Dependent 22 | @RegisterRestClient 23 | public interface UserService { 24 | 25 | @GET 26 | @Path("self") 27 | @Produces(MediaType.APPLICATION_JSON) 28 | public UserRegistrationInfo getUserConsent(@HeaderParam("Authorization") String authorizationHeader); 29 | 30 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/catalog/json/CreateTransactionDefinition.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.json; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import javax.json.bind.annotation.JsonbProperty; 6 | 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import lombok.ToString; 10 | 11 | @Getter @Setter @ToString 12 | public class CreateTransactionDefinition { 13 | 14 | @JsonbProperty 15 | private String transactionName; 16 | 17 | @JsonbProperty 18 | private String category; 19 | 20 | @JsonbProperty 21 | private BigDecimal amount; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/catalog/json/RewardTransactionDefinition.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.json; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import javax.json.bind.annotation.JsonbProperty; 6 | 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import lombok.ToString; 10 | 11 | @Getter @Setter @ToString 12 | public class RewardTransactionDefinition { 13 | 14 | @JsonbProperty 15 | private BigDecimal pointsEarned; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/interceptor/LoggingInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.interceptor; 2 | 3 | import java.util.Arrays; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | import javax.inject.Inject; 8 | import javax.interceptor.AroundInvoke; 9 | import javax.interceptor.InvocationContext; 10 | import javax.json.Json; 11 | import javax.json.JsonObjectBuilder; 12 | import javax.ws.rs.core.Response; 13 | 14 | import org.eclipse.microprofile.jwt.Claim; 15 | 16 | /* 17 | * This interceptor is used with the JAXRS resource classes to log any exception and return a 500 status code to the client. 18 | * This could have been accomplished with an ExceptionMapper as well but an interceptor lets us also log information about 19 | * the failing method and input parameters. 20 | */ 21 | public class LoggingInterceptor { 22 | 23 | private static final Logger log = Logger.getLogger(LoggingInterceptor.class.getName()); 24 | 25 | @Inject 26 | @Claim("sub") 27 | private String subject; 28 | 29 | @AroundInvoke 30 | public Object logInvocation(InvocationContext ctx) { 31 | try { 32 | Object result = ctx.proceed(); 33 | logRequestAndResult(ctx, result); 34 | return result; 35 | } catch(Throwable e) { 36 | String clz = ctx.getMethod().getDeclaringClass().getName(); 37 | String method = ctx.getMethod().getName(); 38 | Object[] params = ctx.getParameters(); 39 | if (params != null && params.length > 0) { 40 | log.log(Level.SEVERE, "***** Exception in " + clz + "." + method, params); 41 | } else { 42 | log.log(Level.SEVERE, "***** Exception in " + clz + "." + method); 43 | } 44 | e.printStackTrace(); 45 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); 46 | } 47 | } 48 | 49 | private void logRequestAndResult(InvocationContext ctx, Object result) { 50 | String methodName = ctx.getMethod().getName(); 51 | Object[] params = ctx.getParameters(); 52 | JsonObjectBuilder requestBuilder = Json.createObjectBuilder() 53 | .add("subject", subject) 54 | .add("action", methodName); 55 | if (params != null && params.length > 0) { 56 | requestBuilder.add("input", Arrays.toString(params)); 57 | } 58 | if (result instanceof Response) { 59 | Response response = (Response)result; 60 | requestBuilder.add("statuscode", response.getStatus()); 61 | } 62 | log.log(Level.INFO, "API REQUEST", requestBuilder.build()); 63 | } 64 | 65 | 66 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/interceptor/SecurityInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.interceptor; 2 | 3 | import javax.annotation.Priority; 4 | import javax.inject.Inject; 5 | import javax.interceptor.AroundInvoke; 6 | import javax.interceptor.Interceptor; 7 | import javax.interceptor.InvocationContext; 8 | import javax.ws.rs.core.Response; 9 | 10 | import org.eclipse.microprofile.jwt.Claim; 11 | 12 | import com.ibm.codey.bank.interceptor.binding.RequiresAuthorization; 13 | 14 | /* 15 | * This interceptor is used with the JAXRS resource classes to enforce a client scope for authorization purposes. 16 | */ 17 | @RequiresAuthorization @Interceptor 18 | @Priority(Interceptor.Priority.APPLICATION) 19 | public class SecurityInterceptor { 20 | 21 | @Inject 22 | @Claim("scope") 23 | private String scope; 24 | 25 | @AroundInvoke 26 | public Object checkScope(InvocationContext ctx) throws Exception { 27 | String[] scopeList = scope.split(" "); 28 | for(String hasScope : scopeList) { 29 | if (hasScope.equals("admin")) { 30 | Object result = ctx.proceed(); 31 | return result; 32 | } 33 | } 34 | return Response.status(Response.Status.FORBIDDEN).entity("admin permission required").build(); 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /bank-app-backend/common/src/main/java/com/ibm/codey/bank/interceptor/binding/RequiresAuthorization.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.interceptor.binding; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import java.lang.annotation.Inherited; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import javax.interceptor.InterceptorBinding; 12 | 13 | @Inherited 14 | @InterceptorBinding 15 | @Target({TYPE, METHOD}) 16 | @Retention(RUNTIME) 17 | public @interface RequiresAuthorization { 18 | } -------------------------------------------------------------------------------- /bank-app-backend/integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | com.ibm.codey.bank 10 | parent 11 | 1.0-SNAPSHOT 12 | 13 | 14 | com.ibm.codey.bank 15 | integration-tests 16 | 1.0-SNAPSHOT 17 | jar 18 | 19 | 20 | 21 | com.ibm.codey.bank 22 | common 23 | 1.0-SNAPSHOT 24 | jar 25 | 26 | 27 | org.apache.commons 28 | commons-lang3 29 | 3.9 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-failsafe-plugin 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /bank-app-backend/integration-tests/src/test/java/it/com/ibm/codey/loyalty/accounts/UserEndpointTest.java: -------------------------------------------------------------------------------- 1 | package it.com.ibm.codey.loyalty.accounts; 2 | 3 | import javax.ws.rs.core.Response; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import com.ibm.codey.loyalty.accounts.json.UserRegistration; 11 | 12 | import it.com.ibm.codey.loyalty.EndpointTestBase; 13 | import it.com.ibm.codey.loyalty.util.TestSecurityHelper; 14 | 15 | public class UserEndpointTest extends EndpointTestBase { 16 | 17 | @Before 18 | public void setup() { 19 | super.setup(); 20 | } 21 | 22 | @After 23 | public void teardown() { 24 | super.teardown(); 25 | } 26 | 27 | @Test 28 | public void testUserRegistrationAndDeletion() { 29 | try { 30 | setupUser(); 31 | // Use GET to get the user registration. 32 | UserRegistration checkUserRegistration = get(USERS_BASEURL, USERS_SELF_ENDPOINT, null, userAccessToken, Response.Status.OK, UserRegistration.class); 33 | assertEquals("Consent flag is incorrect", CONSENT_GIVEN, checkUserRegistration.isConsentGiven()); 34 | } finally { 35 | removeUser(); 36 | } 37 | } 38 | 39 | @Test 40 | public void testUserRegistrationModificationAndDeletion() { 41 | try { 42 | setupUser(); 43 | // Use PUT to change the user registration. 44 | UserRegistration userRegistration = new UserRegistration(); 45 | userRegistration.setConsentGiven(CONSENT_NOT_GIVEN); 46 | put(USERS_BASEURL, USERS_SELF_ENDPOINT, userRegistration, userAccessToken, Response.Status.NO_CONTENT, Void.class); 47 | // Use GET to get the user registration. 48 | UserRegistration checkUserRegistration = get(USERS_BASEURL, USERS_SELF_ENDPOINT, null, userAccessToken, Response.Status.OK, UserRegistration.class); 49 | assertEquals("Consent flag is incorrect", CONSENT_NOT_GIVEN, checkUserRegistration.isConsentGiven()); 50 | } finally { 51 | removeUser(); 52 | } 53 | } 54 | 55 | @Test 56 | public void testAuthenticationFailure() { 57 | // Make calls without an authentication header and verify they are rejected. 58 | UserRegistration userRegistration = new UserRegistration(); 59 | userRegistration.setConsentGiven(CONSENT_GIVEN); 60 | post(USERS_BASEURL, USERS_ENDPOINT, userRegistration, null, Response.Status.UNAUTHORIZED, Void.class); 61 | get(USERS_BASEURL, USERS_SELF_ENDPOINT, null, null, Response.Status.UNAUTHORIZED, Void.class); 62 | put(USERS_BASEURL, USERS_SELF_ENDPOINT, userRegistration, null, Response.Status.UNAUTHORIZED, Void.class); 63 | delete(USERS_BASEURL, USERS_SELF_ENDPOINT, null, Response.Status.UNAUTHORIZED); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /bank-app-backend/integration-tests/src/test/java/it/com/ibm/codey/loyalty/util/TestSecurityHelper.java: -------------------------------------------------------------------------------- 1 | package it.com.ibm.codey.loyalty.util; 2 | 3 | import java.net.URL; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Base64; 6 | 7 | import javax.json.Json; 8 | import javax.json.JsonObject; 9 | import javax.ws.rs.client.Client; 10 | import javax.ws.rs.client.ClientBuilder; 11 | import javax.ws.rs.client.Entity; 12 | import javax.ws.rs.core.Form; 13 | import javax.ws.rs.core.HttpHeaders; 14 | import javax.ws.rs.core.MediaType; 15 | import javax.ws.rs.core.Response; 16 | 17 | import org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider; 18 | 19 | public class TestSecurityHelper { 20 | 21 | private static String APPID_SERVICE_URL; 22 | 23 | private static String APPID_TENANTID; 24 | 25 | private static String IAM_APIKEY; 26 | 27 | private static String IAM_SERVICE_URL; 28 | 29 | private static String OIDC_ISSUERIDENTIFIER; 30 | 31 | private static String OIDC_CLIENTID; 32 | 33 | private static String OIDC_CLIENTPASSWORD; 34 | 35 | private static String iamAuthHeader; 36 | 37 | private static String oidcAuthHeader; 38 | 39 | static { 40 | APPID_SERVICE_URL = System.getenv("APPID_SERVICE_URL"); 41 | APPID_TENANTID = System.getenv("APPID_TENANTID"); 42 | IAM_APIKEY = System.getenv("IAM_APIKEY"); 43 | IAM_SERVICE_URL = System.getenv("IAM_SERVICE_URL"); 44 | OIDC_ISSUERIDENTIFIER = System.getenv("OIDC_ISSUERIDENTIFIER"); 45 | OIDC_CLIENTID = System.getenv("OIDC_CLIENTID"); 46 | OIDC_CLIENTPASSWORD = System.getenv("OIDC_CLIENTPASSWORD"); 47 | String oidcClientCredentials = OIDC_CLIENTID + ":" + OIDC_CLIENTPASSWORD; 48 | oidcAuthHeader = "Basic " + Base64.getEncoder().encodeToString(oidcClientCredentials.getBytes(StandardCharsets.UTF_8)); 49 | } 50 | 51 | public static void createUser(String user, String password) { 52 | Client client = ClientBuilder.newClient(); 53 | client.register(JsrJsonpProvider.class); 54 | // Get IAM bearer token when creating the first user. The token can be reused after that. 55 | if (iamAuthHeader == null) { 56 | Form form = new Form(); 57 | form.param("grant_type", "urn:ibm:params:oauth:grant-type:apikey"); 58 | form.param("apikey", IAM_APIKEY); 59 | String iamToken; 60 | try (Response response = client.target(IAM_SERVICE_URL).request(MediaType.APPLICATION_JSON).buildPost(Entity.form(form)).invoke()) { 61 | if (response.getStatus() != Response.Status.OK.getStatusCode()) { 62 | throw new RuntimeException("TEST CASE FAILURE. Cannot obtain IAM access token. Status code " + response.getStatus() + " Response =" + response.readEntity(JsonObject.class)); 63 | } 64 | JsonObject obj = response.readEntity(JsonObject.class); 65 | iamToken = obj.getString("access_token"); 66 | } 67 | iamAuthHeader = "Bearer " + iamToken; 68 | } 69 | // Create the user 70 | JsonObject request = Json.createObjectBuilder() 71 | .add("userName", user) 72 | .add("password", password) 73 | .add("active", true) 74 | .add("emails", Json.createArrayBuilder() 75 | .add(Json.createObjectBuilder() 76 | .add("value", "ibmtestloyalty@yopmail.com") 77 | .add("primary", true)) 78 | ).build(); 79 | String createUserURL = APPID_SERVICE_URL + "/management/v4/" + APPID_TENANTID + "/cloud_directory/Users"; 80 | try (Response response = client.target(createUserURL).request(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, iamAuthHeader).buildPost(Entity.json(request)).invoke()) { 81 | if (response.getStatus() != Response.Status.CREATED.getStatusCode()) { 82 | throw new RuntimeException("TEST CASE FAILURE. Cannot create user. Status code " + response.getStatus() + " Response =" + response.readEntity(JsonObject.class)); 83 | } 84 | } 85 | } 86 | 87 | public static String signOn(String user, String password) { 88 | String url = OIDC_ISSUERIDENTIFIER + "/token"; 89 | Form form = new Form(); 90 | form.param("grant_type", "password"); 91 | form.param("username", user); 92 | form.param("password", password); 93 | Client client = ClientBuilder.newClient(); 94 | client.register(JsrJsonpProvider.class); 95 | try (Response response = client.target(url).request(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, oidcAuthHeader).buildPost(Entity.form(form)).invoke()) { 96 | if (response.getStatus() != Response.Status.OK.getStatusCode()) { 97 | throw new RuntimeException("TEST CASE FAILURE. Cannot obtain access token. Status code " + response.getStatus() + " Response =" + response.readEntity(JsonObject.class)); 98 | } 99 | JsonObject obj = response.readEntity(JsonObject.class); 100 | return obj.getString("access_token"); 101 | } 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM open-liberty:19.0.0.12-kernel-java8-openj9 2 | 3 | USER root 4 | RUN apt-get update && apt-get upgrade -y e2fsprogs libgnutls30 libgcrypt20 libsasl2-2 5 | USER 1001 6 | 7 | COPY --chown=1001:0 src/main/liberty/config/ /config/ 8 | COPY --chown=1001:0 src/main/resources/security/ /config/resources/security/ 9 | COPY --chown=1001:0 target/*.war /config/apps/ 10 | COPY --chown=1001:0 target/jdbc/* /config/jdbc/ 11 | RUN configure.sh 12 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: transaction-service 5 | labels: 6 | app: transaction-service 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: transaction-service 12 | template: 13 | metadata: 14 | labels: 15 | app: transaction-service 16 | annotations: 17 | sidecar.istio.io/inject: "false" 18 | spec: 19 | containers: 20 | - name: transaction-service 21 | image: ykoyfman/bank-transaction-service:1.0 22 | imagePullPolicy: Always 23 | ports: 24 | - name: http-server 25 | containerPort: 9080 26 | envFrom: 27 | - secretRef: 28 | name: bank-db-secret 29 | - secretRef: 30 | name: bank-oidc-secret 31 | env: 32 | - name: USER_SERVICE_URL 33 | value: "http://user-service:9080/bank/v1/users" 34 | - name: KNATIVE_SERVICE_URL 35 | value: "http://process-transaction.example-bank.svc.cluster.local" 36 | - name: WLP_LOGGING_CONSOLE_LOGLEVEL 37 | value: INFO 38 | --- 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | name: transaction-service 43 | labels: 44 | app: transaction-service 45 | spec: 46 | ports: 47 | - port: 9080 48 | targetPort: 9080 49 | selector: 50 | app: transaction-service 51 | --- 52 | apiVersion: v1 53 | kind: Route 54 | metadata: 55 | name: transaction-service 56 | spec: 57 | to: 58 | kind: Service 59 | name: transaction-service 60 | 61 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | com.ibm.codey.bank 10 | parent 11 | 1.0-SNAPSHOT 12 | 13 | 14 | com.ibm.codey.bank 15 | transaction-service 16 | 1.0-SNAPSHOT 17 | war 18 | 19 | 20 | 21 | 22 | io.openliberty.features 23 | microProfile-3.0 24 | esa 25 | 26 | 27 | com.ibm.codey.bank 28 | common 29 | 1.0-SNAPSHOT 30 | jar 31 | 32 | 33 | 34 | 35 | ${project.artifactId} 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-war-plugin 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-dependency-plugin 45 | 3.0.0 46 | 47 | 48 | copy-jdbc-driver 49 | package 50 | 51 | copy 52 | 53 | 54 | 55 | 56 | org.postgresql 57 | postgresql 58 | 42.2.8 59 | ${project.build.directory}/jdbc 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-surefire-plugin 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-failsafe-plugin 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/LivenessCheck.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | 5 | import org.eclipse.microprofile.health.HealthCheck; 6 | import org.eclipse.microprofile.health.HealthCheckResponse; 7 | import org.eclipse.microprofile.health.Liveness; 8 | 9 | @Liveness 10 | @ApplicationScoped 11 | public class LivenessCheck implements HealthCheck { 12 | 13 | private boolean isAlive() { 14 | // perform health checks here 15 | 16 | return true; 17 | } 18 | 19 | @Override 20 | public HealthCheckResponse call() { 21 | boolean up = isAlive(); 22 | return HealthCheckResponse.named(this.getClass().getSimpleName()).state(up).build(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/LoyaltyApplication.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("/bank") 7 | public class LoyaltyApplication extends Application { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/ReadinessCheck.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | 5 | import org.eclipse.microprofile.health.HealthCheck; 6 | import org.eclipse.microprofile.health.HealthCheckResponse; 7 | import org.eclipse.microprofile.health.Readiness; 8 | 9 | @Readiness 10 | @ApplicationScoped 11 | public class ReadinessCheck implements HealthCheck { 12 | 13 | private boolean isReady() { 14 | // perform readiness checks, e.g. database connection, etc. 15 | 16 | return true; 17 | } 18 | 19 | @Override 20 | public HealthCheckResponse call() { 21 | boolean up = isReady(); 22 | return HealthCheckResponse.named(this.getClass().getSimpleName()).state(up).build(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/catalog/dao/TransactionDao.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.dao; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import javax.enterprise.context.RequestScoped; 8 | 9 | import javax.persistence.EntityManager; 10 | import javax.persistence.NoResultException; 11 | import javax.persistence.PersistenceContext; 12 | 13 | import com.ibm.codey.bank.catalog.models.Category; 14 | import com.ibm.codey.bank.catalog.models.Transaction; 15 | 16 | @RequestScoped 17 | public class TransactionDao { 18 | 19 | @PersistenceContext(name = "jpa-unit") 20 | private EntityManager em; 21 | 22 | public void createTransaction(Transaction transaction) { 23 | em.persist(transaction); 24 | } 25 | 26 | public void updateTransaction(Transaction transaction) { 27 | em.merge(transaction); 28 | } 29 | 30 | public List findTransactions() { 31 | return em.createNamedQuery("Transaction.findTransactions", Transaction.class) 32 | .getResultList(); 33 | } 34 | 35 | public List findTransactionsByUser(String userId) { 36 | return em.createNamedQuery("Transaction.findTransactionsByUser", Transaction.class) 37 | .setParameter("userId", userId) 38 | .getResultList(); 39 | } 40 | 41 | public Transaction findTransactionById(String transactionId) { 42 | try { 43 | return em.createNamedQuery("Transaction.findTransactionByIdOnly", Transaction.class) 44 | .setParameter("transactionId", transactionId) 45 | .getSingleResult(); 46 | } catch(NoResultException e) { 47 | return null; 48 | } 49 | } 50 | 51 | public Transaction findTransactionById(String transactionId, String userId) { 52 | try { 53 | return em.createNamedQuery("Transaction.findTransactionById", Transaction.class) 54 | .setParameter("transactionId", transactionId) 55 | .setParameter("userId", userId) 56 | .getSingleResult(); 57 | } catch(NoResultException e) { 58 | return null; 59 | } 60 | } 61 | 62 | public List groupCategoriesForUser(String userId) { 63 | try { 64 | List rows = em.createNamedQuery("Transaction.groupCategoriesForUser", Object[][].class) 65 | .setParameter("userId", userId) 66 | .getResultList(); 67 | List response = new ArrayList<>(); 68 | for (Object[] row: rows) { 69 | if (row.length == 2) { 70 | response.add(new Category(row[0].toString(), new BigDecimal(row[1].toString()))); 71 | } 72 | } 73 | 74 | return response; 75 | } catch(NoResultException e) { 76 | return null; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/catalog/models/Category.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.models; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Getter @Setter 9 | public class Category { 10 | 11 | private String category; 12 | private BigDecimal amount; 13 | 14 | public Category(String category, BigDecimal amount) { 15 | this.category = category; 16 | this.amount = amount; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/catalog/models/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.models; 2 | 3 | import java.io.Serializable; 4 | import java.math.BigDecimal; 5 | import java.time.OffsetDateTime; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.EntityListeners; 10 | import javax.persistence.Id; 11 | import javax.persistence.IdClass; 12 | import javax.persistence.NamedQueries; 13 | import javax.persistence.NamedQuery; 14 | import javax.persistence.Table; 15 | 16 | import lombok.Getter; 17 | import lombok.Setter; 18 | 19 | @Entity 20 | @Table(name = "transactions") 21 | @IdClass(TransactionPK.class) 22 | @NamedQueries({ 23 | @NamedQuery(name = "Transaction.findTransactions", query = "SELECT t FROM Transaction t"), 24 | @NamedQuery(name = "Transaction.findTransactionsByUser", query = "SELECT t FROM Transaction t WHERE t.userId = :userId"), 25 | @NamedQuery(name = "Transaction.findTransactionById", query = "SELECT t FROM Transaction t WHERE t.transactionId = :transactionId AND t.userId = :userId"), 26 | @NamedQuery(name = "Transaction.findTransactionByIdOnly", query = "SELECT t FROM Transaction t WHERE t.transactionId = :transactionId"), 27 | @NamedQuery(name = "Transaction.groupCategoriesForUser", query = "SELECT COALESCE(t.category, 'Uncategorized'), SUM (t.amount) FROM Transaction t WHERE t.userId = :userId GROUP BY t.category") 28 | }) 29 | @Getter @Setter 30 | @EntityListeners(TransactionListener.class) 31 | public class Transaction implements Serializable { 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | @Column(name = "transaction_id") 36 | @Id 37 | private String transactionId; 38 | 39 | @Id 40 | @Column(name = "usr") 41 | private String userId; 42 | 43 | @Column(name = "transaction_name") 44 | private String transactionName; 45 | 46 | @Column(name = "amount") 47 | private BigDecimal amount; 48 | 49 | @Column(name = "category") 50 | private String category; 51 | 52 | @Column(name = "points_earned") 53 | private BigDecimal pointsEarned; 54 | 55 | @Column(name = "processed") 56 | private boolean processed; 57 | 58 | @Column(name = "date") 59 | private OffsetDateTime date; 60 | 61 | public Transaction() { 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/catalog/models/TransactionListener.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.models; 2 | 3 | import java.net.URL; 4 | 5 | import javax.enterprise.context.RequestScoped; 6 | import javax.inject.Inject; 7 | import javax.persistence.PostPersist; 8 | import javax.ws.rs.WebApplicationException; 9 | import javax.ws.rs.core.Response; 10 | 11 | import org.eclipse.microprofile.config.inject.ConfigProperty; 12 | import org.eclipse.microprofile.rest.client.RestClientBuilder; 13 | 14 | import com.ibm.codey.bank.catalog.KnativeService; 15 | 16 | @RequestScoped 17 | public class TransactionListener { 18 | 19 | @Inject 20 | @ConfigProperty(name = "KNATIVE_SERVICE_URL") 21 | private URL knativeServiceURL; 22 | 23 | @PostPersist 24 | public void sendToProcessing(Transaction transaction) { 25 | KnativeService knativeService = RestClientBuilder.newBuilder().baseUrl(knativeServiceURL).build(KnativeService.class); 26 | 27 | try { 28 | knativeService.processTransaction(transaction.getTransactionId(), transaction.getCategory(), transaction.getAmount().toString()); 29 | } catch (WebApplicationException wae) { 30 | System.out.print("web app exception"); 31 | int status = wae.getResponse().getStatus(); 32 | if (status == Response.Status.NOT_FOUND.getStatusCode()) { 33 | // TODO: .. 34 | } else { 35 | wae.printStackTrace(); 36 | } 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/java/com/ibm/codey/bank/catalog/models/TransactionPK.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.catalog.models; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Getter @Setter 9 | public class TransactionPK implements Serializable { 10 | 11 | private String transactionId; 12 | 13 | private String userId; 14 | 15 | } -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/liberty/config/bootstrap.properties: -------------------------------------------------------------------------------- 1 | default.http.port=9080 2 | default.https.port=9443 -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/liberty/config/jvm.options: -------------------------------------------------------------------------------- 1 | # This option is needed when using an IBM JRE to avoid a handshake failure when making a secure JDBC connection. 2 | -Dcom.ibm.jsse2.overrideDefaultTLS=true 3 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/liberty/config/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jpa-2.2 5 | microProfile-3.0 6 | mpJwt-1.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/resources/META-INF/orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | bank 8 | 9 | 10 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | jdbc/AccountsDataSource 9 | NONE 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/resources/security/digicert-root-ca.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/bank-app-backend/transaction-service/src/main/resources/security/digicert-root-ca.jks -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /bank-app-backend/transaction-service/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | transaction-service 8 | 9 | 10 | authenticated 11 | 12 | 13 | 14 | Security Constraints 15 | 16 | ProtectedArea 17 | /* 18 | 19 | 20 | authenticated 21 | 22 | 23 | NONE 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM open-liberty:19.0.0.12-kernel-java8-openj9 2 | 3 | USER root 4 | RUN apt-get update && apt-get upgrade -y e2fsprogs libgnutls30 libgcrypt20 libsasl2-2 5 | USER 1001 6 | 7 | COPY --chown=1001:0 src/main/liberty/config/ /config/ 8 | COPY --chown=1001:0 src/main/resources/security/ /config/resources/security/ 9 | COPY --chown=1001:0 target/*.war /config/apps/ 10 | COPY --chown=1001:0 target/jdbc/* /config/jdbc/ 11 | RUN configure.sh 12 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: user-service 5 | labels: 6 | app: user-service 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: user-service 12 | template: 13 | metadata: 14 | labels: 15 | app: user-service 16 | spec: 17 | containers: 18 | - name: user-service 19 | image: anthonyamanse/user-service:example-bank-1.0 20 | imagePullPolicy: Always 21 | ports: 22 | - name: http-server 23 | containerPort: 9080 24 | envFrom: 25 | - secretRef: 26 | name: bank-db-secret 27 | - secretRef: 28 | name: bank-oidc-secret 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: user-service 34 | labels: 35 | app: user-service 36 | spec: 37 | ports: 38 | - port: 9080 39 | targetPort: 9080 40 | selector: 41 | app: user-service 42 | --- 43 | apiVersion: v1 44 | kind: Route 45 | metadata: 46 | name: user-service 47 | spec: 48 | to: 49 | kind: Service 50 | name: user-service 51 | 52 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | com.ibm.codey.bank 10 | parent 11 | 1.0-SNAPSHOT 12 | 13 | 14 | com.ibm.codey.bank 15 | user-service 16 | 1.0-SNAPSHOT 17 | war 18 | 19 | 20 | 21 | 22 | io.openliberty.features 23 | microProfile-3.0 24 | esa 25 | 26 | 27 | com.ibm.codey.bank 28 | common 29 | 1.0-SNAPSHOT 30 | jar 31 | 32 | 33 | 34 | 35 | ${project.artifactId} 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-war-plugin 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-dependency-plugin 45 | 3.0.0 46 | 47 | 48 | copy-jdbc-driver 49 | package 50 | 51 | copy 52 | 53 | 54 | 55 | 56 | org.postgresql 57 | postgresql 58 | 42.2.8 59 | ${project.build.directory}/jdbc 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-surefire-plugin 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-failsafe-plugin 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/java/com/ibm/codey/bank/LivenessCheck.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | 5 | import org.eclipse.microprofile.health.HealthCheck; 6 | import org.eclipse.microprofile.health.HealthCheckResponse; 7 | import org.eclipse.microprofile.health.Liveness; 8 | 9 | @Liveness 10 | @ApplicationScoped 11 | public class LivenessCheck implements HealthCheck { 12 | 13 | private boolean isAlive() { 14 | // perform health checks here 15 | 16 | return true; 17 | } 18 | 19 | @Override 20 | public HealthCheckResponse call() { 21 | boolean up = isAlive(); 22 | return HealthCheckResponse.named(this.getClass().getSimpleName()).state(up).build(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/java/com/ibm/codey/bank/LoyaltyApplication.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("/bank") 7 | public class LoyaltyApplication extends Application { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/java/com/ibm/codey/bank/ReadinessCheck.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | 5 | import org.eclipse.microprofile.health.HealthCheck; 6 | import org.eclipse.microprofile.health.HealthCheckResponse; 7 | import org.eclipse.microprofile.health.Readiness; 8 | 9 | @Readiness 10 | @ApplicationScoped 11 | public class ReadinessCheck implements HealthCheck { 12 | 13 | private boolean isReady() { 14 | // perform readiness checks, e.g. database connection, etc. 15 | 16 | return true; 17 | } 18 | 19 | @Override 20 | public HealthCheckResponse call() { 21 | boolean up = isReady(); 22 | return HealthCheckResponse.named(this.getClass().getSimpleName()).state(up).build(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/java/com/ibm/codey/bank/accounts/UserResource.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.accounts; 2 | 3 | import javax.enterprise.context.RequestScoped; 4 | import javax.inject.Inject; 5 | import javax.interceptor.Interceptors; 6 | import javax.transaction.Transactional; 7 | import javax.ws.rs.Consumes; 8 | import javax.ws.rs.GET; 9 | import javax.ws.rs.PUT; 10 | import javax.ws.rs.POST; 11 | import javax.ws.rs.DELETE; 12 | import javax.ws.rs.Path; 13 | import javax.ws.rs.Produces; 14 | import javax.ws.rs.core.MediaType; 15 | import javax.ws.rs.core.Response; 16 | 17 | import com.ibm.codey.bank.BaseResource; 18 | import com.ibm.codey.bank.accounts.dao.UserDao; 19 | import com.ibm.codey.bank.accounts.json.UserRegistration; 20 | import com.ibm.codey.bank.accounts.json.UserRegistrationInfo; 21 | import com.ibm.codey.bank.accounts.models.User; 22 | import com.ibm.codey.bank.interceptor.LoggingInterceptor; 23 | 24 | @RequestScoped 25 | @Interceptors(LoggingInterceptor.class) 26 | @Path("v1/users") 27 | public class UserResource extends BaseResource { 28 | 29 | @Inject 30 | private UserDao userDAO; 31 | 32 | /** 33 | * This method creates a new user. 34 | */ 35 | @POST 36 | @Consumes(MediaType.APPLICATION_JSON) 37 | @Transactional 38 | public Response registerUser(UserRegistration userRegistration) { 39 | String subject = this.getCallerSubject(); 40 | if (subject == null) { 41 | return Response.status(Response.Status.UNAUTHORIZED).entity("Missing subject").build(); 42 | } 43 | if (userDAO.findUserByRegistryId(subject) != null) { 44 | return Response.status(Response.Status.BAD_REQUEST).entity("User is already registered").build(); 45 | } 46 | User newUser = new User(); 47 | newUser.setSubject(subject); 48 | newUser.setConsentGiven(userRegistration.isConsentGiven()); 49 | userDAO.createUser(newUser); 50 | return Response.status(Response.Status.NO_CONTENT).build(); 51 | } 52 | 53 | /** 54 | * This method returns the user registration data for a user. 55 | */ 56 | @GET 57 | @Path("self") 58 | @Produces(MediaType.APPLICATION_JSON) 59 | @Transactional 60 | public Response getUser() { 61 | String subject = this.getCallerSubject(); 62 | if (subject == null) { 63 | return Response.status(Response.Status.UNAUTHORIZED).entity("Missing subject").build(); 64 | } 65 | User prevUser = userDAO.findUserByRegistryId(subject); 66 | if (prevUser == null) { 67 | return Response.status(Response.Status.NOT_FOUND).entity("User is not registered").build(); 68 | } 69 | UserRegistrationInfo userRegistration = new UserRegistrationInfo(); 70 | userRegistration.setUserId(prevUser.getUserId()); 71 | userRegistration.setConsentGiven(prevUser.isConsentGiven()); 72 | return Response.status(Response.Status.OK).entity(userRegistration).build(); 73 | } 74 | 75 | /** 76 | * This method updates the user registration data for a user. 77 | */ 78 | @PUT 79 | @Path("self") 80 | @Consumes(MediaType.APPLICATION_JSON) 81 | @Transactional 82 | public Response updateUser(UserRegistration userRegistration) { 83 | String subject = this.getCallerSubject(); 84 | if (subject == null) { 85 | return Response.status(Response.Status.UNAUTHORIZED).entity("Missing subject").build(); 86 | } 87 | User prevUser = userDAO.findUserByRegistryId(subject); 88 | if (prevUser == null) { 89 | return Response.status(Response.Status.NOT_FOUND).entity("User is not registered").build(); 90 | } 91 | if (prevUser.isDeleteRequested()) { 92 | return Response.status(Response.Status.CONFLICT).entity("User has requested deletion").build(); 93 | } 94 | prevUser.setConsentGiven(userRegistration.isConsentGiven()); 95 | userDAO.updateUser(prevUser); 96 | return Response.status(Response.Status.NO_CONTENT).build(); 97 | } 98 | 99 | /** 100 | * This method schedules an asynchronous process to remove the user from the system. 101 | */ 102 | @DELETE 103 | @Path("self") 104 | @Transactional 105 | public Response deleteUser() { 106 | String subject = this.getCallerSubject(); 107 | if (subject == null) { 108 | return Response.status(Response.Status.UNAUTHORIZED).entity("Missing subject").build(); 109 | } 110 | User prevUser = userDAO.findUserByRegistryId(subject); 111 | if (prevUser == null) { 112 | return Response.status(Response.Status.NOT_FOUND).entity("User is not registered").build(); 113 | } 114 | prevUser.setDeleteRequested(true); 115 | prevUser.setSubject(null); 116 | userDAO.updateUser(prevUser); 117 | return Response.status(Response.Status.NO_CONTENT).build(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/java/com/ibm/codey/bank/accounts/dao/UserDao.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.accounts.dao; 2 | 3 | import java.util.List; 4 | import javax.enterprise.context.RequestScoped; 5 | import javax.persistence.EntityManager; 6 | import javax.persistence.LockModeType; 7 | import javax.persistence.NoResultException; 8 | import javax.persistence.PersistenceContext; 9 | 10 | import com.ibm.codey.bank.accounts.models.User; 11 | 12 | @RequestScoped 13 | public class UserDao { 14 | 15 | @PersistenceContext(name = "jpa-unit") 16 | private EntityManager em; 17 | 18 | public void createUser(User user) { 19 | em.persist(user); 20 | } 21 | 22 | public void updateUser(User user) { 23 | em.merge(user); 24 | } 25 | 26 | public User findUserByRegistryId(String subject) { 27 | try { 28 | return em.createNamedQuery("User.findUserByRegistryId", User.class) 29 | .setParameter("subject", subject).getSingleResult(); 30 | } catch(NoResultException e) { 31 | return null; 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/java/com/ibm/codey/bank/accounts/models/User.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.bank.accounts.models; 2 | 3 | import java.io.Serializable; 4 | import java.util.UUID; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | import javax.persistence.NamedQueries; 10 | import javax.persistence.NamedQuery; 11 | import javax.persistence.Table; 12 | 13 | import lombok.AccessLevel; 14 | import lombok.Getter; 15 | import lombok.Setter; 16 | 17 | @Entity 18 | @Table(name = "users") 19 | @NamedQueries({ 20 | @NamedQuery(name = "User.findUserByRegistryId", query = "SELECT e FROM User e WHERE e.subject = :subject"), 21 | }) 22 | @Getter @Setter 23 | public class User implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | @Column(name = "user_id") 28 | @Id 29 | @Setter(AccessLevel.NONE) 30 | private String userId; 31 | 32 | @Column(name = "subject", unique=true) 33 | private String subject; 34 | 35 | @Column(name = "consent_given") 36 | private boolean consentGiven; 37 | 38 | @Column(name = "delete_requested") 39 | private boolean deleteRequested; 40 | 41 | public User() { 42 | this.userId = UUID.randomUUID().toString(); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/liberty/config/bootstrap.properties: -------------------------------------------------------------------------------- 1 | default.http.port=9080 2 | default.https.port=9443 -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/liberty/config/jvm.options: -------------------------------------------------------------------------------- 1 | # This option is needed when using an IBM JRE to avoid a handshake failure when making a secure JDBC connection. 2 | -Dcom.ibm.jsse2.overrideDefaultTLS=true 3 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/liberty/config/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jpa-2.2 5 | microProfile-3.0 6 | mpJwt-1.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/resources/META-INF/orm.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | bank 8 | 9 | 10 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | jdbc/AccountsDataSource 9 | NONE 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/resources/security/digicert-root-ca.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/bank-app-backend/user-service/src/main/resources/security/digicert-root-ca.jks -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /bank-app-backend/user-service/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | user-service 8 | 9 | 10 | authenticated 11 | 12 | 13 | 14 | Security Constraints 15 | 16 | ProtectedArea 17 | /* 18 | 19 | 20 | authenticated 21 | 22 | 23 | NONE 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /bank-knative-service/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official lightweight Node.js 12 image. 2 | # https://hub.docker.com/_/node 3 | FROM node:12-slim 4 | 5 | # Create and change to the app directory. 6 | WORKDIR /usr/src/app 7 | 8 | # Copy application dependency manifests to the container image. 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied. 10 | # Copying this separately prevents re-running npm install on every code change. 11 | COPY package*.json ./ 12 | 13 | # Install production dependencies. 14 | RUN npm install --only=production 15 | 16 | # Copy local code to the container image. 17 | COPY . ./ 18 | 19 | # Run the web service on container startup. 20 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /bank-knative-service/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: serving.knative.dev/v1 2 | kind: Service 3 | metadata: 4 | name: process-transaction 5 | # local to cluster only 6 | labels: 7 | serving.knative.dev/visibility: cluster-local 8 | spec: 9 | template: 10 | metadata: 11 | annotations: 12 | # Target 10 requests in-flight per pod. 13 | autoscaling.knative.dev/target: "10" 14 | # Disable scale to zero with a minScale of 1. 15 | # autoscaling.knative.dev/minScale: "1" 16 | # Limit scaling to 50 pods. 17 | # autoscaling.knative.dev/maxScale: "50" 18 | spec: 19 | containers: 20 | - image: anthonyamanse/knative-transaction-process:with-auth 21 | envFrom: 22 | - secretRef: 23 | name: bank-oidc-adminuser 24 | - secretRef: 25 | name: mobile-simulator-secrets 26 | env: 27 | - name: TRANSACTION_SERVICE_URL 28 | value: "http://transaction-service:9080/bank/v1/transactions" -------------------------------------------------------------------------------- /bank-knative-service/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const axios = require('axios'); 4 | const qs = require('qs'); 5 | const jwt_decode = require('jwt-decode') 6 | 7 | let transactionServiceUrl = process.env.TRANSACTION_SERVICE_URL 8 | let appIdTokenUrl = process.env.APP_ID_TOKEN_URL 9 | let appIdClientId = process.env.APP_ID_CLIENT_ID 10 | let appIdClientSecret = process.env.APP_ID_CLIENT_SECRET 11 | let appIdAdminUser = process.env.APP_ID_ADMIN_USER 12 | let appIdAdminPassword = process.env.APP_ID_ADMIN_PASSWORD 13 | 14 | let appIdResult; 15 | 16 | app.post('/process', (req, res) => { 17 | console.log('received request') 18 | console.log(req.query) 19 | if (!appIdResult) { 20 | getAppIdToken(appIdAdminUser, appIdAdminPassword) 21 | .then(function (response) { 22 | appIdResult = response.data 23 | sendToRewardEndpoint(req, res, appIdResult.access_token) 24 | }) 25 | .catch(function (error) { 26 | console.log(error) 27 | res.status('404').send('Error getting admin token') 28 | }) 29 | } else { 30 | console.log('found app id result in global variable') 31 | // check if token is expired 32 | if (isAccessTokenExpired(appIdResult.access_token)) { 33 | console.log('token found is expired. getting new one...') 34 | getAppIdToken(appIdAdminUser, appIdAdminPassword) 35 | .then(function (response) { 36 | appIdResult = response.data 37 | sendToRewardEndpoint(req, res, appIdResult.access_token) 38 | }) 39 | .catch(function (error) { 40 | console.log(error) 41 | res.status('404').send('Error getting admin token') 42 | }) 43 | } else { 44 | sendToRewardEndpoint(req, res, appIdResult.access_token) 45 | } 46 | } 47 | }); 48 | 49 | function sendToRewardEndpoint(req, res, authToken) { 50 | if (req.query.transactionId && req.query.category && req.query.amount) { 51 | let pointsEarned = computeReward(req.query.category, req.query.amount); 52 | axios({ 53 | headers: { 54 | 'Authorization': 'Bearer ' + authToken 55 | }, 56 | method: 'put', 57 | url: transactionServiceUrl + '/reward/' + req.query.transactionId, 58 | data: { 59 | pointsEarned 60 | } 61 | }) 62 | .then(function (response) { 63 | if (response.status == '204') { 64 | res.status('200').send('OK') 65 | } else { 66 | console.log({status: error.response.status, data: error.response.data}) 67 | res.status('404').send({result: 'Failed to post to transaction API', response }) 68 | } 69 | }).catch(function (error) { 70 | console.log("Error in PUT /transactions/reward/{transactionId}") 71 | console.log({status: error.response.status, data: error.response.data}) 72 | res.status('404').send({error}) 73 | }) 74 | } else { 75 | res.status('404').send('transactionId, category, and amount must be present in query parameters.') 76 | } 77 | } 78 | 79 | function computeReward(category, amount) { 80 | return amount; 81 | } 82 | 83 | function getAppIdToken(username, password) { 84 | let data = { 85 | username, 86 | password, 87 | grant_type: 'password' 88 | } 89 | return axios({ 90 | method: 'post', 91 | url: appIdTokenUrl + '/token', 92 | headers: { 93 | 'Authorization': 'Basic ' + Buffer.from(appIdClientId + ":" + appIdClientSecret).toString('base64'), 94 | 'Content-Type' : 'application/x-www-form-urlencoded' 95 | }, 96 | data: qs.stringify(data) 97 | }) 98 | } 99 | 100 | function isAccessTokenExpired(access_token) { 101 | if (new Date().getTime() - (jwt_decode(access_token).exp * 1000) >= 0) { 102 | return true 103 | } else { 104 | return false 105 | } 106 | } 107 | 108 | 109 | const port = process.env.PORT || 8080; 110 | app.listen(port, () => { 111 | console.log('Hello world listening on port', port); 112 | }); -------------------------------------------------------------------------------- /bank-knative-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bank-knative-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^0.19.2", 14 | "express": "^4.17.1", 15 | "jwt-decode": "^2.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bank-user-cleanup-utility/.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | !.keep 3 | 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | 31 | 32 | ## Local configuration files 33 | /local/config/* 34 | 35 | *.swo 36 | *.swp 37 | -------------------------------------------------------------------------------- /bank-user-cleanup-utility/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk:8-jre-openj9 2 | 3 | USER root 4 | RUN apt-get update && apt-get upgrade -y e2fsprogs libgnutls30 libgcrypt20 libsasl2-2 5 | RUN mkdir -p /opt/app/lib 6 | USER 1001 7 | 8 | COPY target/user-cleanup-utility-1.0-SNAPSHOT.jar /opt/app 9 | COPY target/lib/* /opt/app/lib/ 10 | CMD ["java", "-jar", "/opt/app/user-cleanup-utility-1.0-SNAPSHOT.jar"] -------------------------------------------------------------------------------- /bank-user-cleanup-utility/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Build 3 | 4 | ``` 5 | mvn package 6 | docker build -t bank-user-cleanup-utility:1.0-SNAPSHOT . 7 | ``` 8 | 9 | ### Secrets 10 | 11 | ``` 12 | kubectl create secret generic bank-db-secret --from-literal=DB_SERVERNAME=48f106c1-94cb-4133-b99f-20991c91cb1a.bn2a2vgd01r3l0hfmvc0.databases.appdomain.cloud --from-literal=DB_PORTNUMBER=30389 --from-literal=DB_DATABASENAME=ibmclouddb --from-literal=DB_USER=ibm_cloud_0637cd24_8ac9_4dc7_b2d4_ebd080633f7f --from-literal=DB_PASSWORD= 13 | kubectl create secret generic bank-iam-secret --from-literal=IAM_APIKEY= --from-literal=IAM_SERVICE_URL=https://iam.cloud.ibm.com/identity/token 14 | kubectl create secret generic bank-appid-secret --from-literal=APPID_TENANTID=3d17f53d-4600-4f32-bb2c-207f4e2f6060 --from-literal=APPID_SERVICE_URL=https://us-south.appid.cloud.ibm.com 15 | ``` -------------------------------------------------------------------------------- /bank-user-cleanup-utility/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1beta1 2 | kind: CronJob 3 | metadata: 4 | name: bank-user-cleanup-utility 5 | labels: 6 | app: bank-user-cleanup-utility 7 | spec: 8 | schedule: "@hourly" 9 | jobTemplate: 10 | spec: 11 | template: 12 | spec: 13 | restartPolicy: Never 14 | containers: 15 | - name: bank-user-cleanup-utility 16 | image: ykoyfman/bank-cleanup:1.0 17 | imagePullPolicy: Always 18 | envFrom: 19 | - secretRef: 20 | name: bank-db-secret 21 | - secretRef: 22 | name: bank-iam-secret 23 | - secretRef: 24 | name: bank-appid-secret 25 | env: 26 | - name: LAST_LOGIN_HOURS 27 | value: "24" 28 | backoffLimit: 0 29 | -------------------------------------------------------------------------------- /bank-user-cleanup-utility/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.ibm.codey.loyalty 9 | user-cleanup-utility 10 | 1.0-SNAPSHOT 11 | jar 12 | 13 | 14 | UTF-8 15 | UTF-8 16 | 1.8 17 | 1.8 18 | 19 | 20 | 21 | 22 | org.postgresql 23 | postgresql 24 | 42.2.10 25 | 26 | 27 | 28 | jakarta.json.bind 29 | jakarta.json.bind-api 30 | 1.0.2 31 | 32 | 33 | 34 | org.jboss.resteasy 35 | resteasy-json-binding-provider 36 | 4.4.2.Final 37 | 38 | 39 | 40 | org.eclipse.microprofile.rest.client 41 | microprofile-rest-client-api 42 | 1.3.3 43 | 44 | 45 | 46 | org.jboss.resteasy 47 | resteasy-client-microprofile 48 | 4.4.2.Final 49 | 50 | 51 | 52 | org.projectlombok 53 | lombok 54 | 1.18.16 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-dependency-plugin 63 | 3.1.1 64 | 65 | 66 | compile 67 | 68 | copy-dependencies 69 | 70 | 71 | ${project.build.directory}/lib 72 | runtime 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-jar-plugin 80 | 3.0.2 81 | 82 | 83 | 84 | true 85 | lib/ 86 | com.ibm.codey.loyalty.AccountDeletionProcessor 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /bank-user-cleanup-utility/src/main/java/com/ibm/codey/loyalty/external/appid/AppIDService.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.loyalty.external.appid; 2 | 3 | import javax.ws.rs.HeaderParam; 4 | import javax.ws.rs.DELETE; 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | import javax.ws.rs.PathParam; 8 | import javax.ws.rs.QueryParam; 9 | import javax.ws.rs.Produces; 10 | import javax.ws.rs.core.MediaType; 11 | 12 | public interface AppIDService extends AutoCloseable { 13 | 14 | public static String DATASCOPE_FULL = "full"; 15 | 16 | @GET 17 | @Path("/management/v4/{tenantId}/users") 18 | @Produces({MediaType.APPLICATION_JSON}) 19 | public AppIDServiceGetUsersResponse getUsers( 20 | @HeaderParam("Authorization") String authorizationHeader, 21 | @PathParam("tenantId") String tenantId, 22 | @QueryParam("dataScope") String dataScope, 23 | @QueryParam("startIndex") int startIndex, 24 | @QueryParam("count") int count 25 | ); 26 | 27 | @GET 28 | @Path("/management/v4/{tenantId}/users/{id}/roles") 29 | @Produces({MediaType.APPLICATION_JSON}) 30 | public AppIDServiceGetUserRoleResponse getUserRoles( 31 | @HeaderParam("Authorization") String authorizationHeader, 32 | @PathParam("tenantId") String tenantId, 33 | @PathParam("id") String profileId 34 | ); 35 | 36 | @DELETE 37 | @Path("/management/v4/{tenantId}/cloud_directory/remove/{userId}") 38 | public void removeUser( 39 | @HeaderParam("Authorization") String authorizationHeader, 40 | @PathParam("tenantId") String tenantId, 41 | @PathParam("userId") String userId 42 | ); 43 | 44 | } -------------------------------------------------------------------------------- /bank-user-cleanup-utility/src/main/java/com/ibm/codey/loyalty/external/appid/AppIDServiceGetUserRoleResponse.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.loyalty.external.appid; 2 | 3 | import javax.json.bind.annotation.JsonbProperty; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Getter @Setter 9 | public class AppIDServiceGetUserRoleResponse { 10 | 11 | @JsonbProperty("roles") 12 | private Role[] roles; 13 | 14 | @Getter @Setter 15 | public static class Role { 16 | 17 | @JsonbProperty("name") 18 | private String name; 19 | 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /bank-user-cleanup-utility/src/main/java/com/ibm/codey/loyalty/external/appid/AppIDServiceGetUsersResponse.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.loyalty.external.appid; 2 | 3 | import javax.json.bind.annotation.JsonbProperty; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Getter @Setter 9 | public class AppIDServiceGetUsersResponse { 10 | 11 | @JsonbProperty("totalResults") 12 | private int totalResults; 13 | 14 | @JsonbProperty("itemsPerPage") 15 | private int itemsPerPage; 16 | 17 | @JsonbProperty("users") 18 | private User[] users; 19 | 20 | @Getter @Setter 21 | public static class User { 22 | 23 | @JsonbProperty("id") 24 | private String profileId; 25 | 26 | @JsonbProperty("identities") 27 | private Identity[] identities; 28 | 29 | } 30 | 31 | @Getter @Setter 32 | public static class Identity { 33 | 34 | @JsonbProperty("provider") 35 | private String provider; 36 | 37 | @JsonbProperty("id") 38 | private String providerId; 39 | 40 | @JsonbProperty("idpUserInfo") 41 | private IdpUserInfo idpUserInfo; 42 | 43 | } 44 | 45 | @Getter @Setter 46 | public static class IdpUserInfo { 47 | 48 | @JsonbProperty("meta") 49 | private Meta meta; 50 | 51 | } 52 | 53 | @Getter @Setter 54 | public static class Meta { 55 | 56 | @JsonbProperty("lastModified") 57 | private String lastModified; 58 | 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /bank-user-cleanup-utility/src/main/java/com/ibm/codey/loyalty/external/iam/IAMTokenService.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.loyalty.external.iam; 2 | 3 | import javax.ws.rs.Consumes; 4 | import javax.ws.rs.FormParam; 5 | import javax.ws.rs.POST; 6 | import javax.ws.rs.Produces; 7 | import javax.ws.rs.core.MediaType; 8 | 9 | public interface IAMTokenService extends AutoCloseable { 10 | 11 | public final static String GRANT_TYPE_APIKEY = "urn:ibm:params:oauth:grant-type:apikey"; 12 | 13 | @POST 14 | @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) 15 | @Produces({MediaType.APPLICATION_JSON}) 16 | public IAMTokenServiceResponse getIAMTokenFromAPIKey( 17 | @FormParam("grant_type") String grantType, 18 | @FormParam("apikey") String apiKey 19 | ); 20 | 21 | } -------------------------------------------------------------------------------- /bank-user-cleanup-utility/src/main/java/com/ibm/codey/loyalty/external/iam/IAMTokenServiceResponse.java: -------------------------------------------------------------------------------- 1 | package com.ibm.codey.loyalty.external.iam; 2 | 3 | import javax.json.bind.annotation.JsonbProperty; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Getter @Setter 9 | public class IAMTokenServiceResponse { 10 | 11 | @JsonbProperty("access_token") 12 | public String accessToken; 13 | 14 | @JsonbProperty 15 | public long expiration; 16 | 17 | } -------------------------------------------------------------------------------- /data_model/.gitignore: -------------------------------------------------------------------------------- 1 | .~/ 2 | -------------------------------------------------------------------------------- /data_model/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | COPY cc_schema.sql /tmp 3 | CMD /usr/bin/psql postgres://$DB_USER:$DB_PASSWORD@$DB_SERVERNAME:$DB_PORTNUMBER -f /tmp/cc_schema.sql 4 | -------------------------------------------------------------------------------- /data_model/README.md: -------------------------------------------------------------------------------- 1 | ### Secrets 2 | 3 | ``` 4 | kubectl create secret generic bank-db-secret --from-literal=DB_SERVERNAME=48f106c1-94cb-4133-b99f-20991c91cb1a.bn2a2vgd01r3l0hfmvc0.databases.appdomain.cloud --from-literal=DB_PORTNUMBER=30389 --from-literal=DB_DATABASENAME=ibmclouddb --from-literal=DB_USER=ibm_cloud_0637cd24_8ac9_4dc7_b2d4_ebd080633f7f --from-literal=DB_PASSWORD= 5 | ``` 6 | -------------------------------------------------------------------------------- /data_model/cc_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 2 | CREATE DATABASE example; 3 | \connect example; 4 | CREATE SCHEMA IF NOT EXISTS bank; 5 | set search_path to bank; 6 | 7 | CREATE TABLE IF NOT EXISTS users ( 8 | user_id VARCHAR, 9 | subject VARCHAR UNIQUE, 10 | consent_given BOOLEAN NOT NULL, 11 | delete_requested BOOLEAN NOT NULL, 12 | PRIMARY KEY (user_id) 13 | ); 14 | 15 | CREATE TABLE IF NOT EXISTS events ( 16 | event_id VARCHAR, 17 | event_name VARCHAR NOT NULL, 18 | point_value INTEGER, 19 | location VARCHAR, 20 | start_time TIMESTAMP, 21 | end_time TIMESTAMP, 22 | description VARCHAR, 23 | PRIMARY KEY (event_id) 24 | ); 25 | 26 | -- Events attended by users 27 | CREATE TABLE IF NOT EXISTS user_event ( 28 | usr VARCHAR NOT NULL, 29 | event VARCHAR NOT NULL, 30 | PRIMARY KEY (usr, event), 31 | FOREIGN KEY (usr) REFERENCES users(user_id) ON UPDATE CASCADE, 32 | FOREIGN KEY (event) REFERENCES events(event_id) ON UPDATE CASCADE 33 | ); 34 | 35 | -- Transactions 36 | CREATE TABLE IF NOT EXISTS transactions ( 37 | transaction_id VARCHAR UNIQUE, 38 | usr VARCHAR NOT NULL, 39 | transaction_name VARCHAR, 40 | amount NUMERIC(15,2), 41 | category VARCHAR, 42 | points_earned REAL, 43 | processed BOOLEAN NOT NULL, 44 | date TIMESTAMP, 45 | PRIMARY KEY (transaction_id, usr), 46 | FOREIGN KEY (usr) REFERENCES users(user_id) ON UPDATE CASCADE 47 | ); 48 | 49 | -------------------------------------------------------------------------------- /data_model/drop-schema/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | COPY drop.sql /tmp 3 | CMD /usr/bin/psql postgres://$DB_USER:$DB_PASSWORD@$DB_SERVERNAME:$DB_PORTNUMBER -f /tmp/drop.sql 4 | -------------------------------------------------------------------------------- /data_model/drop-schema/drop.sql: -------------------------------------------------------------------------------- 1 | DROP EXTENSION "uuid-ossp"; 2 | DROP DATABASE example; 3 | -------------------------------------------------------------------------------- /data_model/drop-schema/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: cc-schema-unload 5 | labels: 6 | app: cc-schema-unload 7 | spec: 8 | template: 9 | spec: 10 | restartPolicy: Never 11 | containers: 12 | - name: cc-schema-unload 13 | image: ykoyfman/drop-schema 14 | imagePullPolicy: Always 15 | envFrom: 16 | - secretRef: 17 | name: bank-db-secret 18 | -------------------------------------------------------------------------------- /data_model/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: cc-schema-load 5 | labels: 6 | app: cc-schema-load 7 | spec: 8 | template: 9 | spec: 10 | restartPolicy: Never 11 | containers: 12 | - name: cc-schema-load 13 | image: ykoyfman/bank-schema:1.0 14 | imagePullPolicy: Always 15 | envFrom: 16 | - secretRef: 17 | name: bank-db-secret 18 | -------------------------------------------------------------------------------- /deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: mobile-simulator-service 6 | labels: 7 | app: mobile-simulator 8 | spec: 9 | ports: 10 | - port: 80 11 | protocol: TCP 12 | targetPort: 8080 13 | type: LoadBalancer 14 | selector: 15 | app: mobile-simulator 16 | --- 17 | apiVersion: apps/v1 18 | kind: Deployment 19 | metadata: 20 | name: mobile-simulator-deployment 21 | labels: 22 | app: mobile-simulator 23 | spec: 24 | selector: 25 | matchLabels: 26 | app: mobile-simulator 27 | strategy: 28 | type: Recreate 29 | template: 30 | metadata: 31 | labels: 32 | app: mobile-simulator 33 | spec: 34 | containers: 35 | - image: ykoyfman/mobile-simulator:css-fix 36 | imagePullPolicy: Always 37 | name: mobile-simulator 38 | envFrom: 39 | - secretRef: 40 | name: mobile-simulator-secrets 41 | env: 42 | - name: PORT 43 | value: '8080' 44 | ports: 45 | - containerPort: 8080 46 | 47 | --- 48 | apiVersion: v1 49 | kind: Route 50 | metadata: 51 | name: mobile-simulator-service 52 | spec: 53 | to: 54 | kind: Service 55 | name: mobile-simulator-service 56 | 57 | -------------------------------------------------------------------------------- /design/icons.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/design/icons.ai -------------------------------------------------------------------------------- /design/stride.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/design/stride.ai -------------------------------------------------------------------------------- /images/add-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/add-application.png -------------------------------------------------------------------------------- /images/allow-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/allow-sign-in.png -------------------------------------------------------------------------------- /images/appdiag1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/appdiag1.png -------------------------------------------------------------------------------- /images/create-role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/create-role.png -------------------------------------------------------------------------------- /images/disable-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/disable-email.png -------------------------------------------------------------------------------- /images/kiali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/kiali.png -------------------------------------------------------------------------------- /images/logdna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/logdna.png -------------------------------------------------------------------------------- /images/logdna_appid_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/logdna_appid_select.png -------------------------------------------------------------------------------- /images/loyalty-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-1.png -------------------------------------------------------------------------------- /images/loyalty-bank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-bank.png -------------------------------------------------------------------------------- /images/loyalty-phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-phone.png -------------------------------------------------------------------------------- /images/loyalty-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-profile.png -------------------------------------------------------------------------------- /images/loyalty-spending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-spending.png -------------------------------------------------------------------------------- /images/loyalty-transactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-transactions.png -------------------------------------------------------------------------------- /images/loyalty-user-role-added.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-user-role-added.png -------------------------------------------------------------------------------- /images/loyalty-user-role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-user-role.png -------------------------------------------------------------------------------- /images/loyalty-user-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/loyalty-user-test.png -------------------------------------------------------------------------------- /images/new-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/new-app.png -------------------------------------------------------------------------------- /images/pattern-flow-diag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/pattern-flow-diag.png -------------------------------------------------------------------------------- /images/schema-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/schema-1.png -------------------------------------------------------------------------------- /images/sim1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/sim1.png -------------------------------------------------------------------------------- /images/sim2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/sim2.png -------------------------------------------------------------------------------- /images/sim3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/sim3.png -------------------------------------------------------------------------------- /images/sim4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/sim4.png -------------------------------------------------------------------------------- /images/simulator_checkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/simulator_checkin.png -------------------------------------------------------------------------------- /images/simulator_events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/simulator_events.png -------------------------------------------------------------------------------- /images/simulator_katy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/simulator_katy.png -------------------------------------------------------------------------------- /images/simulator_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/simulator_main.png -------------------------------------------------------------------------------- /images/user-creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/user-creation.png -------------------------------------------------------------------------------- /images/writer-credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/images/writer-credentials.png -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | - path: . 3 | memory: 256M 4 | instances: 1 5 | domain: mybluemix.net 6 | name: bank 7 | host: bank 8 | disk_quota: 1024M 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NodejsStarterApp", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js", 7 | "dev": "DEVMODE=true node app.js" 8 | }, 9 | "dependencies": { 10 | "body-parser": "^1.19.0", 11 | "cfenv": "^1.2.2", 12 | "dotenv": "^8.2.0", 13 | "express": "^4.16.4", 14 | "express-http-proxy": "^1.6.0", 15 | "ibmcloud-appid": "^6.1.0", 16 | "log4js": "^6.1.0", 17 | "node-random-name": "^1.0.1", 18 | "request": "^2.88.2" 19 | }, 20 | "repository": {}, 21 | "engines": { 22 | "node": "8.x" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pipelines/ACKNOWLEDGEMENT.md: -------------------------------------------------------------------------------- 1 | Parts of this pipeline related to Sonarqube were borrowed from Siamak Sadeghianfar and his [work](https://github.com/siamaksade/tekton-cd-demo) on Tekton. 2 | -------------------------------------------------------------------------------- /pipelines/bank-pipelinerun.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: tekton.dev/v1alpha1 3 | kind: PipelineRun 4 | metadata: 5 | generateName: bank-run- 6 | spec: 7 | pipelineRef: 8 | name: example-bank 9 | resources: 10 | - name: bank-git 11 | resourceSpec: 12 | type: git 13 | params: 14 | - name: url 15 | value: https://github.com/IBM/example-bank.git 16 | - name: revision 17 | value: main 18 | - name: transaction-image 19 | resourceSpec: 20 | type: image 21 | params: 22 | - name: url 23 | value: image-registry.openshift-image-registry.svc:5000/bank-infra/transaction 24 | - name: user-image 25 | resourceSpec: 26 | type: image 27 | params: 28 | - name: url 29 | value: image-registry.openshift-image-registry.svc:5000/bank-infra/user 30 | - name: ui-image 31 | resourceSpec: 32 | type: image 33 | params: 34 | - name: url 35 | value: image-registry.openshift-image-registry.svc:5000/bank-infra/ui 36 | workspaces: 37 | - name: local-maven-repo 38 | persistentVolumeClaim: 39 | claimName: maven-repo-pvc 40 | serviceAccountName: pipeline 41 | -------------------------------------------------------------------------------- /pipelines/bank-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: maven-repo-pvc 5 | spec: 6 | resources: 7 | requests: 8 | storage: 5Gi 9 | volumeMode: Filesystem 10 | accessModes: 11 | - ReadWriteOnce 12 | persistentVolumeReclaimPolicy: Retain -------------------------------------------------------------------------------- /pipelines/deployments/transaction.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: transaction-service 5 | labels: 6 | app: transaction-service 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: transaction-service 12 | template: 13 | metadata: 14 | labels: 15 | app: transaction-service 16 | annotations: 17 | sidecar.istio.io/inject: "false" 18 | spec: 19 | containers: 20 | - name: transaction-service 21 | image: ykoyfman/bank-transaction-service:1.0 22 | imagePullPolicy: Always 23 | ports: 24 | - name: http-server 25 | containerPort: 9080 26 | envFrom: 27 | - secretRef: 28 | name: bank-db-secret 29 | - secretRef: 30 | name: bank-oidc-secret 31 | env: 32 | - name: USER_SERVICE_URL 33 | value: "http://user-service:9080/bank/v1/users" 34 | - name: KNATIVE_SERVICE_URL 35 | value: "http://process-transaction.bank-renamed-no-mesh.svc.cluster.local" 36 | - name: WLP_LOGGING_CONSOLE_LOGLEVEL 37 | value: INFO 38 | --- 39 | apiVersion: v1 40 | kind: Service 41 | metadata: 42 | name: transaction-service 43 | labels: 44 | app: transaction-service 45 | spec: 46 | ports: 47 | - port: 9080 48 | targetPort: 9080 49 | selector: 50 | app: transaction-service 51 | --- 52 | apiVersion: v1 53 | kind: Route 54 | metadata: 55 | name: transaction-service 56 | spec: 57 | to: 58 | kind: Service 59 | name: transaction-service 60 | 61 | -------------------------------------------------------------------------------- /pipelines/deployments/ui.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: mobile-simulator-service 6 | labels: 7 | app: mobile-simulator 8 | spec: 9 | ports: 10 | - port: 80 11 | protocol: TCP 12 | targetPort: 8080 13 | type: LoadBalancer 14 | selector: 15 | app: mobile-simulator 16 | --- 17 | apiVersion: extensions/v1beta1 18 | kind: Deployment 19 | metadata: 20 | name: mobile-simulator-deployment 21 | labels: 22 | app: mobile-simulator 23 | spec: 24 | strategy: 25 | type: Recreate 26 | template: 27 | metadata: 28 | labels: 29 | app: mobile-simulator 30 | spec: 31 | containers: 32 | - image: anthonyamanse/mobile-simulator:example-bank-1.0 33 | imagePullPolicy: Always 34 | name: mobile-simulator 35 | envFrom: 36 | - secretRef: 37 | name: mobile-simulator-secrets 38 | env: 39 | - name: PORT 40 | value: '8080' 41 | ports: 42 | - containerPort: 8080 43 | 44 | --- 45 | apiVersion: v1 46 | kind: Route 47 | metadata: 48 | name: mobile-simulator-service 49 | spec: 50 | to: 51 | kind: Service 52 | name: mobile-simulator-service 53 | 54 | -------------------------------------------------------------------------------- /pipelines/deployments/user.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: user-service 5 | labels: 6 | app: user-service 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: user-service 12 | template: 13 | metadata: 14 | labels: 15 | app: user-service 16 | spec: 17 | containers: 18 | - name: user-service 19 | image: anthonyamanse/user-service:example-bank-1.0 20 | imagePullPolicy: Always 21 | ports: 22 | - name: http-server 23 | containerPort: 9080 24 | envFrom: 25 | - secretRef: 26 | name: bank-db-secret 27 | - secretRef: 28 | name: bank-oidc-secret 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: user-service 34 | labels: 35 | app: user-service 36 | spec: 37 | ports: 38 | - port: 9080 39 | targetPort: 9080 40 | selector: 41 | app: user-service 42 | --- 43 | apiVersion: v1 44 | kind: Route 45 | metadata: 46 | name: user-service 47 | spec: 48 | to: 49 | kind: Service 50 | name: user-service 51 | 52 | -------------------------------------------------------------------------------- /pipelines/example-bank-pipeline.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1alpha1 2 | kind: Pipeline 3 | metadata: 4 | name: example-bank 5 | spec: 6 | resources: 7 | - name: bank-git 8 | type: git 9 | - name: transaction-image 10 | type: image 11 | - name: user-image 12 | type: image 13 | - name: ui-image 14 | type: image 15 | tasks: 16 | - name: code-analysis 17 | params: 18 | - name: GOALS 19 | value: 20 | - install 21 | - 'sonar:sonar' 22 | - '-Dsonar.host.url=http://sonarqube:9000' 23 | - '-Dsonar.userHome=/tmp/sonar' 24 | - '-DskipITs' 25 | - '-Darguments=-DskipITs' 26 | resources: 27 | inputs: 28 | - name: source 29 | resource: bank-git 30 | taskRef: 31 | kind: Task 32 | name: maven 33 | workspaces: 34 | - name: maven-repo 35 | workspace: local-maven-repo 36 | - name: build-transaction 37 | params: 38 | - name: TLSVERIFY 39 | value: 'false' 40 | - name: MAVEN_ARGS_APPEND 41 | value: '-pl :transaction-service -am package' 42 | - name: PATH_CONTEXT 43 | value: bank-app-backend/ 44 | resources: 45 | inputs: 46 | - name: source 47 | resource: bank-git 48 | outputs: 49 | - name: image 50 | resource: transaction-image 51 | runAfter: 52 | - code-analysis 53 | taskRef: 54 | kind: Task 55 | name: s2i-java-8 56 | - name: build-user 57 | params: 58 | - name: TLSVERIFY 59 | value: 'false' 60 | - name: MAVEN_ARGS_APPEND 61 | value: '-pl :user-service -am package' 62 | - name: PATH_CONTEXT 63 | value: bank-app-backend/ 64 | resources: 65 | inputs: 66 | - name: source 67 | resource: bank-git 68 | outputs: 69 | - name: image 70 | resource: user-image 71 | runAfter: 72 | - code-analysis 73 | taskRef: 74 | kind: Task 75 | name: s2i-java-8 76 | - name: build-ui 77 | params: 78 | - name: TLSVERIFY 79 | value: 'false' 80 | resources: 81 | inputs: 82 | - name: source 83 | resource: bank-git 84 | outputs: 85 | - name: image 86 | resource: ui-image 87 | runAfter: 88 | - code-analysis 89 | taskRef: 90 | kind: Task 91 | name: s2i-nodejs 92 | - name: deploy-transaction 93 | params: 94 | - name: COMMANDS 95 | value: > 96 | oc apply -f 97 | https://raw.githubusercontent.com/IBM/example-bank/main/bank-app-backend/transaction-service/deployment.yaml 98 | -n example-bank 99 | runAfter: 100 | - build-transaction 101 | taskRef: 102 | kind: Task 103 | name: openshift-client 104 | - name: deploy-user 105 | params: 106 | - name: COMMANDS 107 | value: > 108 | oc apply -f 109 | https://raw.githubusercontent.com/IBM/example-bank/main/bank-app-backend/user-service/deployment.yaml 110 | -n example-bank 111 | runAfter: 112 | - build-user 113 | taskRef: 114 | kind: Task 115 | name: openshift-client 116 | - name: deploy-ui 117 | params: 118 | - name: COMMANDS 119 | value: > 120 | oc apply -f 121 | https://raw.githubusercontent.com/IBM/example-bank/main/deployment.yaml 122 | -n example-bank 123 | runAfter: 124 | - build-transaction 125 | taskRef: 126 | kind: Task 127 | name: openshift-client 128 | workspaces: 129 | - name: local-maven-repo 130 | -------------------------------------------------------------------------------- /pipelines/sonarqube.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: sonarqube 6 | labels: 7 | app: sonarqube 8 | app.kubernetes.io/component: sonarqube 9 | app.kubernetes.io/instance: sonarqube 10 | app.kubernetes.io/name: sonarqube 11 | app.kubernetes.io/part-of: sonarqube 12 | spec: 13 | replicas: 1 14 | selector: 15 | matchLabels: 16 | app: sonarqube 17 | name: sonarqube 18 | template: 19 | metadata: 20 | labels: 21 | app: sonarqube 22 | name: sonarqube 23 | spec: 24 | containers: 25 | - name: sonarqube 26 | imagePullPolicy: Always 27 | image: docker.io/sonarqube:8-community-beta 28 | ports: 29 | - containerPort: 9000 30 | protocol: TCP 31 | volumeMounts: 32 | - mountPath: /opt/sq/temp 33 | name: sonarqube-temp 34 | - mountPath: /opt/sq/conf 35 | name: sonarqube-conf 36 | - mountPath: /opt/sq/data 37 | name: sonarqube-data 38 | - mountPath: /opt/sq/extensions 39 | name: sonarqube-extensions 40 | - mountPath: /opt/sq/logs 41 | name: sonarqube-logs 42 | livenessProbe: 43 | failureThreshold: 10 44 | httpGet: 45 | path: / 46 | port: 9000 47 | scheme: HTTP 48 | initialDelaySeconds: 45 49 | periodSeconds: 10 50 | successThreshold: 1 51 | timeoutSeconds: 1 52 | readinessProbe: 53 | failureThreshold: 10 54 | httpGet: 55 | path: / 56 | port: 9000 57 | scheme: HTTP 58 | initialDelaySeconds: 10 59 | periodSeconds: 10 60 | successThreshold: 1 61 | timeoutSeconds: 1 62 | resources: 63 | limits: 64 | cpu: "1" 65 | memory: 4Gi 66 | requests: 67 | cpu: 200m 68 | memory: 512Mi 69 | volumes: 70 | - name: sonarqube-temp 71 | emptyDir: {} 72 | - name: sonarqube-conf 73 | emptyDir: {} 74 | - name: sonarqube-data 75 | emptyDir: {} 76 | - name: sonarqube-extensions 77 | emptyDir: {} 78 | - name: sonarqube-logs 79 | emptyDir: {} 80 | --- 81 | apiVersion: v1 82 | kind: Route 83 | metadata: 84 | labels: 85 | app: sonarqube 86 | name: sonarqube 87 | spec: 88 | port: 89 | targetPort: 9000-tcp 90 | tls: 91 | termination: edge 92 | to: 93 | kind: Service 94 | name: sonarqube 95 | weight: 100 96 | wildcardPolicy: None 97 | --- 98 | apiVersion: v1 99 | kind: Service 100 | metadata: 101 | labels: 102 | app: sonarqube 103 | name: sonarqube 104 | spec: 105 | ports: 106 | - name: 9000-tcp 107 | port: 9000 108 | protocol: TCP 109 | targetPort: 9000 110 | selector: 111 | app: sonarqube 112 | name: sonarqube 113 | type: ClusterIP -------------------------------------------------------------------------------- /pipelines/tasks/mvn-task.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: tekton.dev/v1alpha1 3 | kind: Task 4 | metadata: 5 | name: maven 6 | spec: 7 | workspaces: 8 | - name: maven-repo 9 | inputs: 10 | params: 11 | - name: GOALS 12 | description: The Maven goals to run 13 | type: array 14 | default: ["package"] 15 | - name: MAVEN_SETTINGS_CONFIGMAP 16 | description: The configmap containing Maven settings.xml 17 | type: string 18 | default: maven-settings 19 | resources: 20 | - name: source 21 | type: git 22 | steps: 23 | - name: mvn 24 | image: gcr.io/cloud-builders/mvn 25 | workingDir: /workspace/source/bank-app-backend 26 | command: ["/usr/bin/mvn"] 27 | args: 28 | - -DskipITs 29 | - -Dmaven.test.skip=true 30 | - -Dmaven.repo.local=$(workspaces.maven-repo.path) 31 | - -s 32 | - /var/config/settings.xml 33 | - "$(inputs.params.GOALS)" 34 | volumeMounts: 35 | - name: maven-settings 36 | mountPath: /var/config 37 | volumes: 38 | - name: maven-settings 39 | configMap: 40 | name: $(inputs.params.MAVEN_SETTINGS_CONFIGMAP) 41 | -------------------------------------------------------------------------------- /pipelines/tasks/oc-task.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1alpha1 2 | kind: Task 3 | metadata: 4 | name: openshift-client 5 | spec: 6 | inputs: 7 | params: 8 | - name: COMMANDS 9 | type: string 10 | steps: 11 | - name: run-commands 12 | image: quay.io/openshift/origin-cli:latest 13 | script: | 14 | #!/usr/bin/env bash 15 | $(inputs.params.COMMANDS) 16 | -------------------------------------------------------------------------------- /pipelines/tasks/task-s2i-java.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1alpha1 2 | kind: Task 3 | metadata: 4 | name: s2i-java-8 5 | spec: 6 | inputs: 7 | params: 8 | - default: . 9 | description: The location of the path to run s2i from 10 | name: PATH_CONTEXT 11 | type: string 12 | - default: 'true' 13 | description: >- 14 | Verify the TLS on the registry endpoint (for push/pull to a non-TLS 15 | registry) 16 | name: TLSVERIFY 17 | type: string 18 | - default: '' 19 | description: Additional Maven arguments 20 | name: MAVEN_ARGS_APPEND 21 | type: string 22 | - default: 'false' 23 | description: Remove the Maven repository after the artifact is built 24 | name: MAVEN_CLEAR_REPO 25 | type: string 26 | - default: '' 27 | description: The base URL of a mirror used for retrieving artifacts 28 | name: MAVEN_MIRROR_URL 29 | type: string 30 | resources: 31 | - name: source 32 | type: git 33 | outputs: 34 | resources: 35 | - name: image 36 | type: image 37 | steps: 38 | - args: 39 | - |- 40 | echo "MAVEN_CLEAR_REPO=$(inputs.params.MAVEN_CLEAR_REPO)" > env-file 41 | 42 | [[ '$(inputs.params.MAVEN_ARGS_APPEND)' != "" ]] && 43 | echo "MAVEN_ARGS_APPEND=$(inputs.params.MAVEN_ARGS_APPEND)" >> env-file 44 | 45 | [[ '$(inputs.params.MAVEN_MIRROR_URL)' != "" ]] && 46 | echo "MAVEN_MIRROR_URL=$(inputs.params.MAVEN_MIRROR_URL)" >> env-file 47 | 48 | echo "Generated Env file" 49 | echo "------------------------------" 50 | cat env-file 51 | echo "------------------------------" 52 | command: 53 | - /bin/sh 54 | - '-c' 55 | image: quay.io/openshift-pipeline/s2i 56 | name: gen-env-file 57 | resources: {} 58 | volumeMounts: 59 | - mountPath: /env-params 60 | name: envparams 61 | workingDir: /env-params 62 | - command: 63 | - s2i 64 | - build 65 | - $(inputs.params.PATH_CONTEXT) 66 | - registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift 67 | - '--image-scripts-url' 68 | - 'image:///usr/local/s2i' 69 | - '--as-dockerfile' 70 | - /gen-source/Dockerfile.gen 71 | - '--environment-file' 72 | - /env-params/env-file 73 | image: quay.io/openshift-pipeline/s2i 74 | name: generate 75 | resources: {} 76 | volumeMounts: 77 | - mountPath: /gen-source 78 | name: gen-source 79 | - mountPath: /env-params 80 | name: envparams 81 | workingDir: /workspace/source 82 | - command: 83 | - buildah 84 | - bud 85 | - '--tls-verify=$(inputs.params.TLSVERIFY)' 86 | - '--layers' 87 | - '-f' 88 | - /gen-source/Dockerfile.gen 89 | - '-t' 90 | - $(outputs.resources.image.url) 91 | - . 92 | image: 'quay.io/buildah/stable:v1.11.4' 93 | name: build 94 | resources: {} 95 | securityContext: 96 | privileged: true 97 | volumeMounts: 98 | - mountPath: /var/lib/containers 99 | name: varlibcontainers 100 | - mountPath: /gen-source 101 | name: gen-source 102 | workingDir: /gen-source 103 | - command: 104 | - buildah 105 | - push 106 | - '--tls-verify=$(inputs.params.TLSVERIFY)' 107 | - $(outputs.resources.image.url) 108 | - 'docker://$(outputs.resources.image.url)' 109 | image: 'quay.io/buildah/stable:v1.11.4' 110 | name: push 111 | resources: {} 112 | securityContext: 113 | privileged: true 114 | volumeMounts: 115 | - mountPath: /var/lib/containers 116 | name: varlibcontainers 117 | volumes: 118 | - emptyDir: {} 119 | name: varlibcontainers 120 | - emptyDir: {} 121 | name: gen-source 122 | - emptyDir: {} 123 | name: envparams 124 | -------------------------------------------------------------------------------- /pipelines/tasks/task-s2i-nodejs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1alpha1 2 | kind: Task 3 | metadata: 4 | name: s2i-nodejs 5 | spec: 6 | inputs: 7 | params: 8 | - default: '8' 9 | description: The version of the nodejs 10 | name: VERSION 11 | type: string 12 | - default: . 13 | description: The location of the path to run s2i from. 14 | name: PATH_CONTEXT 15 | type: string 16 | - default: 'true' 17 | description: >- 18 | Verify the TLS on the registry endpoint (for push/pull to a non-TLS 19 | registry) 20 | name: TLSVERIFY 21 | type: string 22 | resources: 23 | - name: source 24 | type: git 25 | outputs: 26 | resources: 27 | - name: image 28 | type: image 29 | steps: 30 | - command: 31 | - s2i 32 | - build 33 | - $(inputs.params.PATH_CONTEXT) 34 | - registry.access.redhat.com/rhscl/nodejs-$(inputs.params.VERSION)-rhel7 35 | - '--as-dockerfile' 36 | - /gen-source/Dockerfile.gen 37 | image: quay.io/openshift-pipeline/s2i 38 | name: generate 39 | resources: {} 40 | volumeMounts: 41 | - mountPath: /gen-source 42 | name: gen-source 43 | workingDir: /workspace/source 44 | - command: 45 | - buildah 46 | - bud 47 | - '--tls-verify=$(inputs.params.TLSVERIFY)' 48 | - '--layers' 49 | - '-f' 50 | - /gen-source/Dockerfile.gen 51 | - '-t' 52 | - $(outputs.resources.image.url) 53 | - . 54 | image: 'quay.io/buildah/stable:v1.11.4' 55 | name: build 56 | resources: {} 57 | securityContext: 58 | privileged: true 59 | volumeMounts: 60 | - mountPath: /var/lib/containers 61 | name: varlibcontainers 62 | - mountPath: /gen-source 63 | name: gen-source 64 | workingDir: /gen-source 65 | - command: 66 | - buildah 67 | - push 68 | - '--tls-verify=$(inputs.params.TLSVERIFY)' 69 | - $(outputs.resources.image.url) 70 | - 'docker://$(outputs.resources.image.url)' 71 | image: 'quay.io/buildah/stable:v1.11.4' 72 | name: push 73 | resources: {} 74 | securityContext: 75 | privileged: true 76 | volumeMounts: 77 | - mountPath: /var/lib/containers 78 | name: varlibcontainers 79 | volumes: 80 | - emptyDir: {} 81 | name: varlibcontainers 82 | - emptyDir: {} 83 | name: gen-source 84 | -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/public/.DS_Store -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/example-bank/0f32d796d7c5b7cf5919022fbb0ad80c476533d6/public/favicon.ico -------------------------------------------------------------------------------- /public/images/account-deselected.svg: -------------------------------------------------------------------------------- 1 | account-deselected -------------------------------------------------------------------------------- /public/images/account-selected.svg: -------------------------------------------------------------------------------- 1 | account-selected -------------------------------------------------------------------------------- /public/images/admin.svg: -------------------------------------------------------------------------------- 1 | admin -------------------------------------------------------------------------------- /public/images/bank.svg: -------------------------------------------------------------------------------- 1 | bankBANK -------------------------------------------------------------------------------- /public/images/banklogo.svg: -------------------------------------------------------------------------------- 1 | banklogo -------------------------------------------------------------------------------- /public/images/cafe.svg: -------------------------------------------------------------------------------- 1 | cafeCAFE -------------------------------------------------------------------------------- /public/images/cloud.svg: -------------------------------------------------------------------------------- 1 | cloud -------------------------------------------------------------------------------- /public/images/deploy-rules.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | Deploy rules 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/eventlist-deselected.svg: -------------------------------------------------------------------------------- 1 | eventlist-deselected -------------------------------------------------------------------------------- /public/images/eventlist-selected.svg: -------------------------------------------------------------------------------- 1 | eventlist-selected -------------------------------------------------------------------------------- /public/images/flash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | Flash 13 | 14 | -------------------------------------------------------------------------------- /public/images/fuel.svg: -------------------------------------------------------------------------------- 1 | fuelFUEL -------------------------------------------------------------------------------- /public/images/groceries.svg: -------------------------------------------------------------------------------- 1 | groceriesGROCERIES -------------------------------------------------------------------------------- /public/images/istio.svg: -------------------------------------------------------------------------------- 1 | istio -------------------------------------------------------------------------------- /public/images/lightbulb.svg: -------------------------------------------------------------------------------- 1 | lightbulb -------------------------------------------------------------------------------- /public/images/loop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | Loop 10 | -------------------------------------------------------------------------------- /public/images/loyaltylogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | loyaltylogo 11 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/images/newyork.svg: -------------------------------------------------------------------------------- 1 | newyork -------------------------------------------------------------------------------- /public/images/programming.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 11 | 14 | Development 15 | 16 | -------------------------------------------------------------------------------- /public/images/reservation-deselected.svg: -------------------------------------------------------------------------------- 1 | reservation-deselected -------------------------------------------------------------------------------- /public/images/reservation-selected.svg: -------------------------------------------------------------------------------- 1 | reservation-selected -------------------------------------------------------------------------------- /public/images/restaurant.svg: -------------------------------------------------------------------------------- 1 | restaurantRESTAURANT -------------------------------------------------------------------------------- /public/images/rideshare.svg: -------------------------------------------------------------------------------- 1 | rideshareRIDE SHARE -------------------------------------------------------------------------------- /public/images/secure.svg: -------------------------------------------------------------------------------- 1 | secure -------------------------------------------------------------------------------- /public/images/statistics-deselected.svg: -------------------------------------------------------------------------------- 1 | statsicongrey -------------------------------------------------------------------------------- /public/images/statistics-selected.svg: -------------------------------------------------------------------------------- 1 | statsiconred -------------------------------------------------------------------------------- /public/images/threat.svg: -------------------------------------------------------------------------------- 1 | threat -------------------------------------------------------------------------------- /public/images/transactions-deselected.svg: -------------------------------------------------------------------------------- 1 | crediticongrey -------------------------------------------------------------------------------- /public/images/transactions-selected.svg: -------------------------------------------------------------------------------- 1 | crediticonred -------------------------------------------------------------------------------- /public/javascript/account.js: -------------------------------------------------------------------------------- 1 | class Account extends HTMLElement { 2 | 3 | events = "" 4 | points = "" 5 | name = "" 6 | 7 | constructor() { 8 | // Always call super first in constructor 9 | super(); 10 | 11 | console.log('INITIALIZED ACCOUNT VIEW'); 12 | var customElement = this; 13 | 14 | let template = document.getElementById('accountview'); 15 | let templateContent = template.content; 16 | 17 | 18 | 19 | const shadow = this.attachShadow({ 20 | mode: 'open' 21 | }) 22 | .appendChild(templateContent.cloneNode(true)); 23 | 24 | let sr = this.shadowRoot; 25 | let logoutButton = sr.getElementById("logoutAccountButton") 26 | logoutButton.addEventListener("click", e => { 27 | this.logout(); 28 | }) 29 | 30 | 31 | let deleteButton = sr.getElementById("deleteAccountButton") 32 | deleteButton.addEventListener("click", e => { 33 | this.delete(); 34 | }) 35 | } 36 | 37 | logout() { 38 | // clear cookies of tokens 39 | document.cookie = "access_token=; Max-Age=0'" 40 | document.cookie = "id_token=; Max-Age=0'" 41 | 42 | // clear local storage 43 | localStorage.clear() 44 | 45 | var phoneview = document.getElementById("phoneview"); 46 | var mobileview = phoneview.getMobileView(); 47 | mobileview.innerHTML = ""; 48 | var welcome = document.createElement('welcome-element') 49 | welcome.setAttribute('mode','INTEGRATED') 50 | mobileview.appendChild(welcome) 51 | 52 | phoneview.hideNavigation(); 53 | } 54 | 55 | delete() { 56 | let phoneview = document.getElementById("phoneview"); 57 | let mobileview = phoneview.getMobileView(); 58 | mobileview.innerHTML = ""; 59 | let element = document.createElement('loading-spinner-element'); 60 | element.setAttribute("status", "Deleting account...") 61 | mobileview.appendChild(element) 62 | 63 | setTimeout(() => { 64 | deleteUserProfile(loyalty.getCookie('access_token'), success => { 65 | if (success) { 66 | element.setAttribute("status", "Successfully deleted account. Logging out...") 67 | setTimeout(() => { 68 | this.logout(); 69 | }, 2500) 70 | } 71 | }) 72 | }, 1500) 73 | } 74 | 75 | connectedCallback(){ 76 | var customElement = this; 77 | var sr = this.shadowRoot; 78 | 79 | /* where to make a data call for points/events */ 80 | 81 | this.mode = customElement.getAttribute('mode'); 82 | this.events = customElement.getAttribute('events'); 83 | this.points = customElement.getAttribute('points'); 84 | 85 | if (this.events == null) { 86 | this.events = '-' 87 | } 88 | if (this.points == null) { 89 | this.points = '-' 90 | } 91 | 92 | this.name = customElement.getAttribute('name') || localStorage.getItem("loyaltyname") 93 | 94 | if(this.name == null){ 95 | this.name = ""; 96 | }else{ 97 | console.log('SETTING NAME') 98 | } 99 | 100 | 101 | this.nameelement = sr.getElementById('name'); 102 | this.nameelement.innerHTML = this.name; 103 | } 104 | } 105 | 106 | try { 107 | customElements.define('account-element', Account); 108 | } catch (err) { 109 | const h3 = document.createElement('h3') 110 | h3.innerHTML = err 111 | document.body.appendChild(h3) 112 | } 113 | -------------------------------------------------------------------------------- /public/javascript/asset.js: -------------------------------------------------------------------------------- 1 | class Asset extends HTMLElement { 2 | 3 | static get observedAttributes() { 4 | return ['assetimage', 'text', 'link']; 5 | } 6 | 7 | constructor(details) { 8 | // Always call super first in constructor 9 | super(); 10 | 11 | let template = document.getElementById('assetlink'); 12 | let templateContent = template.content; 13 | 14 | this.details = details; 15 | 16 | const shadow = this.attachShadow({ 17 | mode: 'open' 18 | }) 19 | .appendChild(templateContent.cloneNode(true)); 20 | } 21 | 22 | connectedCallback(){ 23 | var customElement = this; 24 | var sr = this.shadowRoot; 25 | this.assetimage = sr.getElementById('assetimage'); 26 | this.assetimage.src = customElement.getAttribute('assetimage'); 27 | this.assettext = sr.getElementById('text'); 28 | this.assettext.innerHTML = customElement.getAttribute('text'); 29 | 30 | let link = customElement.getAttribute('link') 31 | if (link) { 32 | this.assettext.addEventListener("click", e => { 33 | window.location = link 34 | }) 35 | } 36 | } 37 | } 38 | 39 | try { 40 | customElements.define('asset-element', Asset); 41 | } catch (err) { 42 | const h3 = document.createElement('h3') 43 | h3.innerHTML = err 44 | document.body.appendChild(h3) 45 | } -------------------------------------------------------------------------------- /public/javascript/clientHelpers/demoaccounts-devmode.js: -------------------------------------------------------------------------------- 1 | function loginWithAppId(username, password, callback) { 2 | let jsonBody = { 3 | id_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLTY4ZDI1ZDQ2LThmZGItNDhlMy1iODNkLTJhYzY2YzI5MTA2NC0yMDIwLTAxLTMxVDAwOjI5OjI4Ljg1NiIsInZlciI6NH0.eyJpc3MiOiJodHRwczovL3VzLXNvdXRoLmFwcGlkLmNsb3VkLmlibS5jb20vb2F1dGgvdjQvMTIzIiwiYXVkIjpbIjEyMyJdLCJleHAiOjAsInRlbmFudCI6IjY4ZDI1ZDQ2LThmZGItNDhlMy1iODNkLTJhYzY2YzI5MTA2NCIsImlhdCI6MCwiZW1haWwiOiJKb2huQFNtaXRoLm9yZyIsIm5hbWUiOiJKb2huIFNtaXRoIiwic3ViIjoiMTIzIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiTGl0YUNhdnJhayIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJTbWl0aCIsImlkZW50aXRpZXMiOlt7InByb3ZpZGVyIjoiY2xvdWRfZGlyZWN0b3J5IiwiaWQiOiIxMjMifV0sImFtciI6WyJjbG91ZF9kaXJlY3RvcnkiXX0.ABC", 4 | access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFwcElkLTY4ZDI1ZDQ2LThmZGItNDhlMy1iODNkLTJhYzY2YzI5MTA2NC0yMDIwLTAxLTMxVDAwOjI5OjI4Ljg1NiIsInZlciI6NH0.eyJpc3MiOiJodHRwczovL3VzLXNvdXRoLmFwcGlkLmNsb3VkLmlibS5jb20vb2F1dGgvdjQvMTIzIiwiZXhwIjowLCJhdWQiOlsiMTIzIl0sInN1YiI6IjEyMyIsImFtciI6WyJjbG91ZF9kaXJlY3RvcnkiXSwiaWF0IjowLCJ0ZW5hbnQiOiIxMjMiLCJzY29wZSI6Im9wZW5pZCBhcHBpZF9kZWZhdWx0IGFwcGlkX3JlYWR1c2VyYXR0ciBhcHBpZF9yZWFkcHJvZmlsZSBhcHBpZF93cml0ZXVzZXJhdHRyIGFwcGlkX2F1dGhlbnRpY2F0ZWQifQ.ABC" 5 | } 6 | 7 | document.cookie = 'access_token=' + jsonBody.access_token + ';' 8 | document.cookie = 'id_token=' + jsonBody.id_token+ ';' 9 | callback(jsonBody) 10 | } 11 | 12 | function getRandomUser(callback) { 13 | let text = "John Smith" 14 | let name = text.split(' ') 15 | let firstname = name[0] 16 | let surname = name[1] 17 | let password = name[0] + name[1] 18 | let email = name[0] + "@" + name[1] + ".org" 19 | callback(firstname, surname, password, email) 20 | } 21 | 22 | function createAccountAppId(firstname, lastname, password, email, callback) { 23 | let json = {} 24 | json.status = "user created successfully" 25 | callback(json) 26 | } 27 | 28 | function getAllUsers(callback) { 29 | callback(['JohnSmith']) 30 | } 31 | -------------------------------------------------------------------------------- /public/javascript/clientHelpers/demoaccounts.js: -------------------------------------------------------------------------------- 1 | function loginWithAppId(username, password, callback) { 2 | let jsonBody = {username, password} 3 | 4 | fetch("/demo/login", { 5 | method: 'POST', 6 | headers: { 7 | 'Content-type': 'application/json', 8 | }, 9 | body: JSON.stringify(jsonBody) 10 | }).then((response) => { 11 | console.log(response) 12 | return response.json(); 13 | }).then((json) => { 14 | console.log(json) 15 | callback(json) 16 | }).catch((error) => { 17 | callback(null) 18 | }) 19 | } 20 | 21 | function getRandomUser(callback) { 22 | fetch("/demo/random_user") 23 | .then((response) => { 24 | return response.text() 25 | }) 26 | .then((text) => { 27 | let name = text.split(' ') 28 | let firstname = name[0] 29 | let surname = name[1] 30 | let password = name[0] + name[1] 31 | let email = name[0] + "@" + name[1] + ".org" 32 | callback(firstname, surname, password, email) 33 | }) 34 | } 35 | 36 | function createAccountAppId(firstname, lastname, password, email, callback) { 37 | let jsonRequestBody = {} 38 | jsonRequestBody.firstName = firstname 39 | jsonRequestBody.lastName = lastname 40 | jsonRequestBody.password = password 41 | jsonRequestBody.email = email 42 | 43 | fetch('/demo/create_account', { 44 | method: 'POST', 45 | headers: { 46 | 'Content-type': 'application/json' 47 | }, 48 | body: JSON.stringify(jsonRequestBody) 49 | }).then((response) => { 50 | console.log(response) 51 | return response.json() 52 | }).then((json) => { 53 | callback(json) 54 | }) 55 | } 56 | 57 | function getAllUsers(callback) { 58 | fetch('/demo/get_all_users') 59 | .then((response) => { 60 | return response.json() 61 | }).then((users) => { 62 | callback(users) 63 | }) 64 | } 65 | 66 | // sample appid account 67 | // loginWithAppId("RolandeColla", "RolandeColla") 68 | -------------------------------------------------------------------------------- /public/javascript/clientHelpers/libertyclient-devmode.js: -------------------------------------------------------------------------------- 1 | let SECURE_USER_BACKEND_URL='/proxy_user' 2 | let SECURE_EVENT_BACKEND_URL='/proxy_transaction' 3 | // DEVMODE 4 | let mode = 'INTEGRATED' 5 | function createProfile(access_token, callback) { 6 | callback(true) 7 | } 8 | 9 | function deleteUserProfile(access_token, callback) { 10 | callback(true) 11 | } 12 | 13 | function getTransactions(access_token, callback) { 14 | let testdata = [{ 15 | "amount": 20, 16 | "category": "Cafe", 17 | "date": "2020-04-27T22:09:39.183Z", 18 | "pointsEarned": 20, 19 | "processed": true, 20 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 21 | "transactionName": "Starbucks", 22 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 23 | }, 24 | { 25 | "amount": 15, 26 | "category": "Carshare", 27 | "date": "2020-04-27T22:09:39.183Z", 28 | "pointsEarned":15, 29 | "processed": true, 30 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 31 | "transactionName": "Uber", 32 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 33 | }, 34 | { 35 | "amount": 70, 36 | "category": "Gas", 37 | "date": "2020-04-27T22:09:39.183Z", 38 | "pointsEarned": 100, 39 | "processed": true, 40 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 41 | "transactionName": "Esso", 42 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 43 | }, 44 | { 45 | "amount": 20, 46 | "category": "Meals", 47 | "date": "2020-04-27T22:09:39.183Z", 48 | "pointsEarned":20, 49 | "processed": true, 50 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 51 | "transactionName": "Sweetgreen", 52 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 53 | },, 54 | { 55 | "amount": 127, 56 | "category": "Groceries", 57 | "date": "2020-04-27T22:09:39.183Z", 58 | "pointsEarned": 200, 59 | "processed": true, 60 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 61 | "transactionName": "Whole Foods", 62 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 63 | }, 64 | { 65 | "amount": 34, 66 | "category": "Meals", 67 | "date": "2020-04-17T22:09:39.183Z", 68 | "pointsEarned":34, 69 | "processed": true, 70 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 71 | "transactionName": "Shake Shack", 72 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 73 | }, 74 | , 75 | { 76 | "amount": 20, 77 | "category": "Meals", 78 | "date": "2020-04-18T22:09:39.183Z", 79 | "pointsEarned":20, 80 | "processed": true, 81 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 82 | "transactionName": "Sweetgreen", 83 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 84 | },, 85 | { 86 | "amount": 127, 87 | "category": "Groceries", 88 | "date": "2020-04-27T22:09:39.183Z", 89 | "pointsEarned": 200, 90 | "processed": true, 91 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 92 | "transactionName": "Whole Foods", 93 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 94 | }, 95 | { 96 | "amount": 5.75, 97 | "category": "Cafe", 98 | "date": "2020-04-28T22:09:39.183Z", 99 | "pointsEarned":34, 100 | "processed": true, 101 | "transactionId": "c2eb0fb9-2af0-43a3-820d-fa210203f698", 102 | "transactionName": "Starbucks", 103 | "userId": "60e67c81-1a27-4423-a890-db653941822a" 104 | } 105 | ] 106 | callback(null, testdata) 107 | } 108 | 109 | function getSpending(access_token, callback) { 110 | var data = [ 111 | { 112 | "category": "Cafe", 113 | "amount": 45 114 | }, 115 | { 116 | "category": "Groceries", 117 | "amount": 239 118 | }, 119 | { 120 | "category": "Fuel", 121 | "amount": 75 122 | }, 123 | { 124 | "category": "Ride Share", 125 | "amount": 35 126 | }, 127 | { 128 | "category": "Restaurant", 129 | "amount": 90 130 | } 131 | ]; 132 | callback(null, data) 133 | } 134 | 135 | function createTransaction(access_token, transactionName, category, amount, callback) { 136 | callback(true) 137 | } -------------------------------------------------------------------------------- /public/javascript/clientHelpers/localstoragehelper.js: -------------------------------------------------------------------------------- 1 | function attendEvent(userSubject, event) { 2 | let localStorageId = userSubject + '-events' 3 | let userEvents = localStorage.getItem(localStorageId) 4 | 5 | if (userEvents == null || userEvents == "") { 6 | let events = [] 7 | events.push(event) 8 | localStorage.setItem(userSubject + '-events', JSON.stringify(events)) 9 | } else { 10 | let arrayOfEvents = JSON.parse(userEvents) 11 | if (arrayOfEvents.filter(e => e.eventId === event.eventId).length > 0) { 12 | console.log('event exists in local storage') 13 | } else { 14 | arrayOfEvents.push(event) 15 | } 16 | localStorage.setItem(localStorageId, JSON.stringify(arrayOfEvents)) 17 | } 18 | } 19 | 20 | function getStoredEvents(userSubject) { 21 | let storedEventsString = localStorage.getItem(userSubject + '-events') 22 | if (storedEventsString == null || storedEventsString == "") return null 23 | return JSON.parse(storedEventsString) 24 | } 25 | 26 | function removeStoredEvent(userSubject, eventId) { 27 | let localStorageId = userSubject + '-events' 28 | let storedEvents = getStoredEvents(userSubject) 29 | storedEvents = storedEvents.filter(e => e.eventId != eventId) 30 | localStorage.setItem(localStorageId, JSON.stringify(storedEvents)) 31 | } 32 | -------------------------------------------------------------------------------- /public/javascript/home.js: -------------------------------------------------------------------------------- 1 | class Home extends HTMLElement { 2 | 3 | constructor() { 4 | super(); 5 | 6 | console.log('INITIALIZING HOMESCREEN'); 7 | 8 | let template = document.getElementById('homescreen'); 9 | let templateContent = template.content; 10 | const shadow = this.attachShadow({ 11 | mode: 'open' 12 | }) 13 | .appendChild(templateContent.cloneNode(true)); 14 | } 15 | 16 | generateTransaction(access_token, shadowRoot, tile){ 17 | var limit = tile.detail.eventData.limit * 100; 18 | var base = tile.detail.eventData.base * 100; 19 | 20 | var charge = Math.floor(Math.random() * (limit - base + 1)) + base; 21 | charge=charge/100; 22 | charge=charge.toFixed(2); 23 | 24 | var entity = tile.detail.eventData.name.toUpperCase() 25 | 26 | console.log('CREATING A CREDIT CARD CHARGE OF $' + charge + ' ON ' + entity ); 27 | 28 | createTransaction(access_token, entity, entity, charge, 29 | (success) => { 30 | if (success) { 31 | let text = 'CREDIT CARD $' + charge + ' ON ' + entity ; 32 | this.showNotification(shadowRoot, text) 33 | } else { 34 | this.showNotification(shadowRoot, "Failed creating transaction. Please check logs") 35 | } 36 | }) 37 | } 38 | 39 | showNotification(shadowRoot, notificationText) { 40 | var notifcationArea = shadowRoot.getElementById('notificationarea'); 41 | notifcationArea.innerHTML = ''; 42 | 43 | var message = document.createElement('div'); 44 | message.innerHTML = notificationText 45 | message.className = 'notification'; 46 | notifcationArea.appendChild(message); 47 | 48 | setTimeout(function(){ 49 | message.remove() 50 | }, 2000); 51 | } 52 | 53 | connectedCallback() { 54 | 55 | var sr = this.shadowRoot; 56 | 57 | var tiles = sr.getElementById('APPTILES'); 58 | 59 | var homescreen = this; 60 | 61 | tiles.addEventListener('APPTILE', e => { 62 | console.log('HOMESCREEN RECIEVED EVENT FROM TILE: ' + e.detail.eventData.name.toLocaleUpperCase()); 63 | 64 | switch(e.detail.eventData.name){ 65 | 66 | case 'bank': 67 | sr.host.parentElement.innerHTML = ''; 68 | break; 69 | 70 | default: 71 | let access_token = loyalty.getCookie('access_token') 72 | if (access_token != "") { 73 | homescreen.generateTransaction(access_token, sr, e) 74 | } else { 75 | homescreen.showNotification(sr, 'Please log in using the Bank app.') 76 | } 77 | break; 78 | } 79 | }); 80 | } 81 | } 82 | 83 | try { 84 | customElements.define('homescreen-element', Home); 85 | } catch (err) { 86 | const h3 = document.createElement('h3') 87 | h3.innerHTML = err 88 | document.body.appendChild(h3) 89 | } 90 | -------------------------------------------------------------------------------- /public/javascript/loyalty.js: -------------------------------------------------------------------------------- 1 | class Loyalty { 2 | 3 | mobileview; 4 | 5 | constructor() { 6 | 7 | console.log('INITIALIZING LOYALTY APP'); 8 | 9 | var phoneview = document.getElementById("phoneview"); 10 | this.mobileview = phoneview.getMobileView(); 11 | 12 | // if cookie exists - then user is logged in 13 | // navigate to account section 14 | 15 | // if(this.mode=='INTEGRATED'){ 16 | // if (this.getCookie('access_token') != "" && this.getCookie('id_token') != "") { 17 | // let id_object = this.parseJwt(this.getCookie('id_token')) 18 | // console.log(id_object) 19 | 20 | // var accountinfo = { 21 | // firstname: id_object.given_name, 22 | // surname: id_object.family_name 23 | // } 24 | 25 | // var fullname = accountinfo.firstname + ' ' + accountinfo.surname 26 | 27 | // this.mobileview.innerHTML = ""; 28 | 29 | // let element = document.createElement('transactions-element') 30 | // element.setAttribute('name', fullname); 31 | // element.setAttribute('mode', this.mode); 32 | // this.mobileview.appendChild(element); 33 | 34 | // localStorage.setItem("loyaltyname", fullname); 35 | // console.log(phoneview) 36 | // phoneview.showNavigation(); 37 | // } 38 | // } 39 | } 40 | 41 | signup() { 42 | console.log('loyalty.signup'); 43 | 44 | // var phoneview = document.getElementById("phoneview"); 45 | // var mobileview = phoneview.getMobileView(); 46 | this.mobileview.innerHTML = ""; 47 | 48 | var element = document.createElement('login-element'); 49 | getRandomUser((firstname, surname, password, email) => { 50 | element.setAttribute('firstname', firstname); 51 | element.setAttribute('surname', surname); 52 | element.setAttribute('password', password); 53 | element.setAttribute('email', email); 54 | element.setAttribute('username', firstname + surname); 55 | 56 | this.mobileview.appendChild(element); 57 | }) 58 | 59 | /* same as mobileview.innerHTML = 60 | '' */ 61 | } 62 | 63 | parseJwt (token) { 64 | var base64Url = token.split('.')[1]; 65 | var base64 = base64Url.replace('/-/g', '+').replace('/_/g', '/'); 66 | var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { 67 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 68 | }).join('')); 69 | 70 | return JSON.parse(jsonPayload); 71 | }; 72 | 73 | getCookie(cname) { 74 | var name = cname + "="; 75 | var decodedCookie = decodeURIComponent(document.cookie); 76 | var ca = decodedCookie.split(';'); 77 | for(var i = 0; i { 45 | 46 | console.log(e) 47 | 48 | var id = e.detail.eventData.id; 49 | 50 | // console.log('HOMESCREEN RECIEVED EVENT FROM NAV BUTTON: ' + id.toLocaleUpperCase()); 51 | 52 | this.setAllButtonsDisabled(); 53 | 54 | var button = sr.getElementById(id); 55 | button.setEnabled(); 56 | navelement.activeview = id; 57 | 58 | var mobileview = this.getMobileView(); 59 | mobileview.innerHTML = "<" + id + "-element>"; 60 | }); 61 | } 62 | } 63 | 64 | try { 65 | customElements.define('navigation-element', Navigation); 66 | } catch (err) { 67 | const h3 = document.createElement('h3') 68 | h3.innerHTML = err 69 | document.body.appendChild(h3) 70 | } 71 | -------------------------------------------------------------------------------- /public/javascript/navigationbutton.js: -------------------------------------------------------------------------------- 1 | class NavigationButton extends HTMLElement { 2 | 3 | 4 | SELECTEDSUFFIX = '-selected.svg'; 5 | DESELECTEDSUFFIX = '-deselected.svg' 6 | 7 | static get observedAttributes() { 8 | return ['imagename','viewname','mode']; 9 | } 10 | 11 | constructor() { 12 | super(); 13 | 14 | let template = document.getElementById('navigationbutton'); 15 | let templateContent = template.content; 16 | 17 | const shadow = this.attachShadow({ 18 | mode: 'open' 19 | }) 20 | .appendChild(templateContent.cloneNode(true)); 21 | } 22 | 23 | setMode(mode){ 24 | this.mode = mode; 25 | 26 | var imagestring = this.imagename; 27 | 28 | if(this.mode=='active'){ 29 | imagestring = imagestring + this.SELECTEDSUFFIX; 30 | }else{ 31 | imagestring = imagestring + this.DESELECTEDSUFFIX; 32 | } 33 | 34 | this.buttonimage.src = './images/' + imagestring; 35 | } 36 | 37 | setEnabled(){ 38 | this.setMode('active'); 39 | } 40 | 41 | setDisabled(){ 42 | this.setMode('inactive'); 43 | } 44 | 45 | connectedCallback(){ 46 | var customElement = this; 47 | var sr = this.shadowRoot; 48 | this.buttonimage = sr.getElementById('navbuttonimage'); 49 | this.button = sr.getElementById('navbutton'); 50 | 51 | // this.mode = customElement.getAttribute('mode'); 52 | this.viewname = customElement.getAttribute('viewname'); 53 | this.imagename = customElement.getAttribute('imagename') 54 | 55 | this.setMode(customElement.getAttribute('mode')); 56 | 57 | this.button.onclick = function () { 58 | console.log('CLICKING NAV BUTTON: ' + customElement.viewname.toLocaleUpperCase()); 59 | var customEvent = new CustomEvent( 'NAV', { 60 | detail: { 61 | eventData: {"id":customElement.viewname} 62 | }, 63 | bubbles: true 64 | }); 65 | customElement.dispatchEvent(customEvent); 66 | } 67 | 68 | console.log('ADDING NAVIGATION BUTTON : ' + this.viewname.toLocaleUpperCase()); 69 | } 70 | } 71 | 72 | 73 | try { 74 | customElements.define('navigationbutton-element', NavigationButton); 75 | } catch (err) { 76 | const h3 = document.createElement('h3') 77 | h3.innerHTML = err 78 | document.body.appendChild(h3) 79 | } -------------------------------------------------------------------------------- /public/javascript/phone.js: -------------------------------------------------------------------------------- 1 | class Phone extends HTMLElement { 2 | 3 | constructor() { 4 | super(); 5 | 6 | console.log('INITIALIZING MOBILE PHONE'); 7 | 8 | let template = document.getElementById('phone'); 9 | let templateContent = template.content; 10 | const shadow = this.attachShadow({ 11 | mode: 'open' 12 | }) 13 | .appendChild(templateContent.cloneNode(true)); 14 | } 15 | 16 | getMobileView(){ 17 | var sr = this.shadowRoot; 18 | var mobileview = sr.getElementById('mobileview'); 19 | return mobileview; 20 | } 21 | 22 | showNavigation(){ 23 | var sr = this.shadowRoot; 24 | var nav = sr.getElementById("mobilenavigation"); 25 | nav.style.display = "flex"; 26 | } 27 | 28 | hideNavigation(){ 29 | var sr = this.shadowRoot; 30 | var nav = sr.getElementById("mobilenavigation"); 31 | nav.style.display = "none"; 32 | } 33 | 34 | connectedCallback() { 35 | var sr = this.shadowRoot; 36 | var phone = this; 37 | var basebutton = sr.getElementById('basebutton'); 38 | var mobileview = sr.getElementById('mobileview'); 39 | var navigation = sr.getElementById('mobilenavigation'); 40 | var apptiles = sr.getElementById('APPTILES'); 41 | basebutton.addEventListener('click', e => { 42 | mobileview.innerHTML = ''; 43 | phone.hideNavigation(); 44 | }); 45 | } 46 | } 47 | 48 | try { 49 | customElements.define('phone-element', Phone); 50 | } catch (err) { 51 | const h3 = document.createElement('h3') 52 | h3.innerHTML = err 53 | document.body.appendChild(h3) 54 | } 55 | -------------------------------------------------------------------------------- /public/javascript/spinner.js: -------------------------------------------------------------------------------- 1 | class LoadingSpinner extends HTMLElement { 2 | 3 | static get observedAttributes() { return ['status']; } 4 | 5 | constructor() { 6 | // Always call super first in constructor 7 | super(); 8 | 9 | let template = document.getElementById('loadingspinner'); 10 | let templateContent = template.content; 11 | 12 | console.log('INITIALIZING SPINNER') 13 | 14 | const shadow = this.attachShadow({ 15 | mode: 'open' 16 | }) 17 | .appendChild(templateContent.cloneNode(true)); 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | // if status attribute is set, change to custom status 22 | console.log("ATTRIBUTE CHANGED") 23 | if (name == "status") { 24 | this.shadowRoot.getElementById("status").innerHTML = newValue 25 | } 26 | } 27 | } 28 | 29 | try { 30 | customElements.define('loading-spinner-element', LoadingSpinner); 31 | } catch (err) { 32 | const h3 = document.createElement('h3') 33 | h3.innerHTML = err 34 | document.body.appendChild(h3) 35 | } 36 | -------------------------------------------------------------------------------- /public/javascript/statistics.js: -------------------------------------------------------------------------------- 1 | class Statistics extends HTMLElement { 2 | 3 | events = "" 4 | points = "" 5 | name = "" 6 | 7 | constructor() { 8 | // Always call super first in constructor 9 | super(); 10 | 11 | console.log('INITIALIZED ACCOUNT VIEW'); 12 | var customElement = this; 13 | 14 | let template = document.getElementById('statistics'); 15 | let templateContent = template.content; 16 | 17 | const shadow = this.attachShadow({ 18 | mode: 'open' 19 | }) 20 | .appendChild(templateContent.cloneNode(true)); 21 | } 22 | 23 | connectedCallback(){ 24 | 25 | console.log('INITIALIZING ANALYSIS'); 26 | 27 | var sr = this.shadowRoot; 28 | var ctx = sr.getElementById('myChart'); 29 | getSpending(loyalty.getCookie('access_token'), (err, spending) => { 30 | if (err == null) { 31 | console.log(spending) 32 | let labels = [] 33 | let values = [] 34 | 35 | spending.forEach(entry => { 36 | labels.push(entry.category) 37 | values.push(entry.amount) 38 | }) 39 | 40 | let data = { 41 | labels: labels, 42 | datasets: [{ 43 | label: 'Spending Breakdown', 44 | data: values, 45 | backgroundColor: [ 46 | 'rgba(178, 35, 60, 1.0)', 47 | 'rgba(229, 45, 78, 1.0)', 48 | 'rgba(236, 108, 131, 1.0)', 49 | 'rgba(244, 171, 184, 1.0)', 50 | 'rgba(252, 234, 237, 1.0)', 51 | 'rgba(102, 20, 34, 1.0)' 52 | ] 53 | }] 54 | }; 55 | 56 | let myDoughnutChart = new Chart(ctx, { 57 | type: 'doughnut', 58 | data: data 59 | }); 60 | } 61 | 62 | }) 63 | } 64 | } 65 | 66 | try { 67 | customElements.define('statistics-element', Statistics); 68 | } catch (err) { 69 | const h3 = document.createElement('h3') 70 | h3.innerHTML = err 71 | document.body.appendChild(h3) 72 | } 73 | -------------------------------------------------------------------------------- /public/javascript/tile.js: -------------------------------------------------------------------------------- 1 | class Tile extends HTMLElement { 2 | 3 | static get observedAttributes() { 4 | return ['tileimage', 'tiletext', 'limit', 'base']; 5 | } 6 | 7 | constructor() { 8 | super(); 9 | 10 | let template = document.getElementById('tile'); 11 | let templateContent = template.content; 12 | 13 | const shadow = this.attachShadow({ 14 | mode: 'open' 15 | }) 16 | .appendChild(templateContent.cloneNode(true)); 17 | } 18 | 19 | connectedCallback(){ 20 | var customElement = this; 21 | var sr = this.shadowRoot; 22 | this.tileimage = sr.getElementById('buttonImage'); 23 | this.tileimage.src = customElement.getAttribute('tileimage'); 24 | this.tiletext = customElement.getAttribute('tiletext'); 25 | this.limit=customElement.getAttribute('ceiling'); 26 | this.base=customElement.getAttribute('base'); 27 | this.button = sr.getElementById('tileButton'); 28 | this.button.onclick = function () { 29 | console.log('CLICKING TILE: ' + customElement.tiletext.toLocaleUpperCase()); 30 | var customEvent = new CustomEvent( 'APPTILE', { 31 | detail: { 32 | eventData: {"name":customElement.tiletext,"limit":customElement.limit,"base":customElement.base} 33 | }, 34 | bubbles: true 35 | }); 36 | customElement.dispatchEvent(customEvent); 37 | 38 | } 39 | 40 | console.log('ADDING TILE : ' + this.tiletext.toLocaleUpperCase()); 41 | } 42 | } 43 | 44 | try { 45 | customElements.define('tile-element', Tile); 46 | } catch (err) { 47 | const h3 = document.createElement('h3') 48 | h3.innerHTML = err 49 | document.body.appendChild(h3) 50 | } -------------------------------------------------------------------------------- /public/javascript/transaction.js: -------------------------------------------------------------------------------- 1 | class Transaction extends HTMLElement { 2 | 3 | observables = ['vendor', 'date', 'amount', 'points']; 4 | 5 | static get observedAttributes() { 6 | return observables; 7 | } 8 | 9 | constructor() { 10 | super(); 11 | 12 | let template = document.getElementById('transaction'); 13 | let templateContent = template.content; 14 | 15 | const shadow = this.attachShadow({ 16 | mode: 'open' 17 | }) 18 | .appendChild(templateContent.cloneNode(true)); 19 | } 20 | 21 | connectedCallback() { 22 | 23 | var sr = this.shadowRoot; 24 | 25 | var transactionComponent = this; 26 | 27 | console.log('LOADING TRANSACTION DATA'); 28 | 29 | this.observables.forEach(function(id){ 30 | var element = sr.getElementById(id); 31 | element.innerHTML = transactionComponent.getAttribute(id); 32 | }) 33 | 34 | 35 | 36 | // eventscomponent.addEventListener(eventid, e => { 37 | // console.log(e.detail) 38 | // let id_object = loyalty.parseJwt(loyalty.getCookie('id_token')) 39 | // attendEvent(id_object.sub, e.detail.eventData) 40 | // // re-attach this component 41 | // let container = this.parentElement 42 | // let content = container.innerHTML 43 | // container.innerHTML = content 44 | // }); 45 | } 46 | } 47 | 48 | try { 49 | customElements.define('transaction-element', Transaction); 50 | } catch (err) { 51 | const h3 = document.createElement('h3') 52 | h3.innerHTML = err 53 | document.body.appendChild(h3) 54 | } 55 | -------------------------------------------------------------------------------- /public/javascript/transactions.js: -------------------------------------------------------------------------------- 1 | class Transactions extends HTMLElement { 2 | 3 | balance = 0; 4 | points = 0; 5 | 6 | constructor() { 7 | super(); 8 | 9 | let template = document.getElementById('transactions'); 10 | let templateContent = template.content; 11 | 12 | console.log('INITIALIZING TRANSACTIONS VIEW') 13 | 14 | const shadow = this.attachShadow({ 15 | mode: 'open' 16 | }) 17 | .appendChild(templateContent.cloneNode(true)); 18 | } 19 | 20 | createTransaction(vendor, date, amount, points){ 21 | var transaction = document.createElement('transaction-element'); 22 | transaction.setAttribute('vendor', vendor); 23 | transaction.setAttribute('date', date); 24 | if (amount != '-') amount = amount.toFixed(2); 25 | if (points != '-') points = points.toFixed(2); 26 | transaction.setAttribute('amount', amount); 27 | transaction.setAttribute('points',points); 28 | return transaction; 29 | } 30 | 31 | connectedCallback() { 32 | var sr = this.shadowRoot; 33 | var transactionlist = sr.getElementById('TRANSACTIONLIST'); 34 | var balance = sr.getElementById('BALANCE'); 35 | var points = sr.getElementById('POINTS'); 36 | var transactionComponent = this; 37 | 38 | getTransactions(loyalty.getCookie('access_token'), (err, _transactions) => { 39 | console.log(_transactions) 40 | if (err == null) { 41 | let transactions = _transactions.sort((a,b) => new Date(b.date) -new Date(a.date)) 42 | transactions.forEach(transaction => { 43 | const date = new Date(transaction.date) 44 | const year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date) 45 | const month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date) 46 | const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date) 47 | if (transaction.amount != null || transaction.amount != undefined) transactionComponent.balance += transaction.amount 48 | if (transaction.pointsEarned != null || transaction.pointsEarned != undefined) transactionComponent.points += transaction.pointsEarned 49 | if (transaction.amount == null || transaction.amount == undefined) transaction.amount = '-' 50 | if (transaction.pointsEarned == null || transaction.pointsEarned == undefined) transaction.pointsEarned = '-' 51 | let transactionElement = transactionComponent.createTransaction(transaction.transactionName, month + " " + day + " " + year, transaction.amount, transaction.pointsEarned) 52 | transactionlist.appendChild(transactionElement) 53 | }) 54 | 55 | balance.innerHTML = '$' + transactionComponent.balance.toFixed(2); 56 | points.innerHTML = transactionComponent.points.toFixed(2); 57 | } else if (err == 'User not registered') { 58 | let phoneview = document.getElementById("phoneview"); 59 | let mobileview = phoneview.getMobileView(); 60 | mobileview.innerHTML = ""; 61 | let element = document.createElement('loading-spinner-element'); 62 | element.setAttribute("status", "User is marked for deletion...") 63 | mobileview.appendChild(element) 64 | phoneview.hideNavigation(); 65 | 66 | setTimeout(() => { 67 | element.setAttribute("status", "Logging out...") 68 | setTimeout(() => { 69 | // clear cookies of tokens 70 | document.cookie = "access_token=; Max-Age=0'" 71 | document.cookie = "id_token=; Max-Age=0'" 72 | 73 | // clear local storage 74 | localStorage.clear() 75 | 76 | mobileview.innerHTML = ""; 77 | var welcome = document.createElement('welcome-element') 78 | welcome.setAttribute('mode','INTEGRATED') 79 | mobileview.appendChild(welcome) 80 | 81 | }, 2500) 82 | }, 2000) 83 | } 84 | }) 85 | } 86 | } 87 | 88 | try { 89 | customElements.define('transactions-element', Transactions); 90 | } catch (err) { 91 | const h3 = document.createElement('h3') 92 | h3.innerHTML = err 93 | document.body.appendChild(h3) 94 | } 95 | -------------------------------------------------------------------------------- /public/javascript/welcome.js: -------------------------------------------------------------------------------- 1 | class Welcome extends HTMLElement { 2 | 3 | constructor() { 4 | 5 | super(); 6 | 7 | console.log('INITIALIZING WELCOME VIEW'); 8 | 9 | let template = document.getElementById('welcomeview'); 10 | let templateContent = template.content; 11 | 12 | const shadow = this.attachShadow({mode: 'open'}) 13 | .appendChild(templateContent.cloneNode(true)); 14 | 15 | } 16 | 17 | connectedCallback() { 18 | 19 | let sr = this.shadowRoot; 20 | 21 | let selectUserInput = sr.getElementById("usernameselect") 22 | let signinButton = sr.getElementById("signin") 23 | 24 | var phoneview = document.getElementById("phoneview"); 25 | var mobileview = phoneview.getMobileView(); 26 | 27 | if (loyalty.getCookie('access_token') != "" && loyalty.getCookie('id_token') != "") { 28 | let id_object = loyalty.parseJwt(loyalty.getCookie('id_token')) 29 | console.log(id_object) 30 | 31 | var accountinfo = { 32 | firstname: id_object.given_name, 33 | surname: id_object.family_name 34 | } 35 | 36 | var fullname = accountinfo.firstname + ' ' + accountinfo.surname 37 | 38 | mobileview.innerHTML = ""; 39 | 40 | let element = document.createElement('transactions-element') 41 | element.setAttribute('name', fullname); 42 | mobileview.appendChild(element); 43 | 44 | localStorage.setItem("loyaltyname", fullname); 45 | 46 | phoneview.showNavigation(); 47 | } else { 48 | getAllUsers((users) => { 49 | users.forEach(user => { 50 | var option = document.createElement("option"); 51 | option.text = user 52 | selectUserInput.add(option) 53 | }); 54 | }) 55 | } 56 | 57 | signinButton.addEventListener("click", e => { 58 | this.signin(selectUserInput.value, selectUserInput.value) 59 | }) 60 | } 61 | 62 | 63 | signin(username, password) { 64 | let sr = this.shadowRoot; 65 | 66 | var mobileview = sr.host.parentElement; 67 | mobileview.innerHTML = ""; 68 | 69 | // create loading spinner first 70 | var element = document.createElement('loading-spinner-element'); 71 | element.setAttribute("status", "Logging in...") 72 | mobileview.appendChild(element) 73 | 74 | loginWithAppId(username, password, (jsonWebToken) => { 75 | // when login complete, 76 | // re-initialize app? 77 | new Loyalty(this.mode); 78 | let id_object = loyalty.parseJwt(jsonWebToken.id_token) 79 | console.log(id_object) 80 | 81 | var accountinfo = { 82 | firstname: id_object.given_name, 83 | surname: id_object.family_name 84 | } 85 | 86 | var fullname = accountinfo.firstname + ' ' + accountinfo.surname 87 | 88 | mobileview.innerHTML = ""; 89 | 90 | let element = document.createElement('transactions-element') 91 | element.setAttribute('name', fullname); 92 | mobileview.appendChild(element); 93 | 94 | localStorage.setItem("loyaltyname", fullname); 95 | 96 | phoneview.showNavigation(); 97 | // edge case when unable to sign in 98 | }) 99 | } 100 | } 101 | 102 | try { 103 | customElements.define('welcome-element', Welcome); 104 | } catch (err) { 105 | const h3 = document.createElement('h3') 106 | h3.innerHTML = err 107 | document.body.appendChild(h3) 108 | } 109 | -------------------------------------------------------------------------------- /public/schedule.html: -------------------------------------------------------------------------------- 1 | View the Cloud Dev Conf schedule & directory. -------------------------------------------------------------------------------- /public/style/loyalty.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --loyalty-background-color: white; 3 | --loyalty-app-color: white; 4 | --loyalty-text-color: #494A4B; 5 | --loyalty-button-color: #FF7E00; 6 | --loyalty-highlight-color: #FF002E; 7 | --loyalty-phone-color: #39687E; 8 | --loyalty-sensor-color: #88a4b1; 9 | --loyalty-divide-color: aliceblue; 10 | --loyalty-highlight-color: #fff2e5; 11 | } 12 | 13 | .loyalty { 14 | padding: 0; 15 | margin: 0; 16 | background-color: var(--loyalty-background-color); 17 | /* #F5F8FA; #33729b; #9f805e; */ 18 | /* font-family: 'Open Sans', sans-serif;*/ 19 | font-family: 'IBM Plex Sans', sans-serif; 20 | /* font-family: 'Questrial', sans-serif; */ 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | display: flex; 24 | flex-direction: row; 25 | justify-content: center; 26 | color: var(--loyalty-text-color); 27 | } 28 | 29 | .container { 30 | width: 900px; 31 | height: 600px; 32 | margin: 70px; 33 | background: var(--loyalty-app-color); 34 | display: flex; 35 | flex-direction: row; 36 | } 37 | 38 | .navigation { 39 | display: flex; 40 | /* min-height: 15%; 41 | max-height:15%; */ 42 | flex-direction: column; 43 | width: 50%; 44 | padding-right: 50px; 45 | } 46 | 47 | .app { 48 | display: flex; 49 | flex-direction: column; 50 | justify-content: center; 51 | align-content: center; 52 | align-items: center; 53 | width: 50%; 54 | /* background: aliceblue; */ 55 | } 56 | 57 | .titlebar { 58 | display: flex; 59 | flex-direction: row; 60 | } 61 | 62 | .logo { 63 | display: flex; 64 | flex-direction: row; 65 | justify-content: center; 66 | align-content: center; 67 | } 68 | 69 | .icon { 70 | width: 40px; 71 | } 72 | 73 | .title { 74 | display: flex; 75 | flex-direction: row; 76 | justify-content: center; 77 | align-content: center; 78 | align-items: center; 79 | margin: 10px; 80 | text-transform: uppercase; 81 | font-weight: bold; 82 | } 83 | 84 | .about { 85 | display: flex; 86 | flex-direction: row; 87 | justify-content: left; 88 | align-content: center; 89 | align-items: center; 90 | margin: 10px; 91 | margin-left: 5px; 92 | text-transform: uppercase; 93 | font-weight: normal; 94 | font-size: 12px; 95 | line-height: 18px; 96 | word-spacing: 2px; 97 | padding-bottom: 20px; 98 | } 99 | 100 | .instruction { 101 | display: flex; 102 | flex-direction: row; 103 | justify-content: left; 104 | align-content: center; 105 | align-items: center; 106 | margin: 10px; 107 | margin-left: 5px; 108 | text-transform: uppercase; 109 | font-weight: bold; 110 | font-size: 16px; 111 | line-height: 20px; 112 | word-spacing: 2px; 113 | color: var(--loyalty-text-color); 114 | } 115 | 116 | .linkbar { 117 | display: flex; 118 | flex-direction: row; 119 | margin-top: 20px; 120 | } 121 | 122 | .linktext { 123 | display: flex; 124 | flex-direction: row; 125 | justify-content: center; 126 | align-content: center; 127 | align-items: center; 128 | margin: 10px; 129 | text-transform: uppercase; 130 | font-weight: bold; 131 | font-size: 14px; 132 | } -------------------------------------------------------------------------------- /public/transaction.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/createsecrets.sh: -------------------------------------------------------------------------------- 1 | MGMTEP=$1 2 | APIKEY=$2 3 | 4 | response=$(curl -k -v -X POST -w "\n%{http_code}" \ 5 | -H "Content-Type: application/x-www-form-urlencoded" \ 6 | -H "Accept: application/json" \ 7 | --data-urlencode "grant_type=urn:ibm:params:oauth:grant-type:apikey" \ 8 | --data-urlencode "apikey=$APIKEY" \ 9 | "https://iam.cloud.ibm.com/identity/token") 10 | 11 | echo $response 12 | 13 | code=$(echo "${response}" | tail -n1) 14 | [ "$code" -ne "200" ] && exit 1 15 | 16 | accesstoken=$(echo "${response}" | head -n1 | jq -j '.access_token') 17 | 18 | response=$(curl -v -X GET -w "\n%{http_code}" \ 19 | -H "Content-Type: application/json" \ 20 | -H "Authorization: Bearer $accesstoken" \ 21 | $MGMTEP/applications) 22 | 23 | echo $response 24 | 25 | code=$(echo "${response}" | tail -n1) 26 | [ "$code" -ne "200" ] && exit 1 27 | 28 | tenantid=$(echo "${response}"| head -n1 | jq -j '.applications[0].tenantId') 29 | clientid=$(echo "${response}"| head -n1 | jq -j '.applications[0].clientId') 30 | secret=$(echo "${response}"| head -n1 | jq -j '.applications[0].secret') 31 | oauthserverurl=$(echo "${response}"| head -n1 | jq -j '.applications[0].oAuthServerUrl') 32 | appidhost=$(echo "${oauthserverurl}" | awk -F/ '{print $3}') 33 | 34 | oc create secret generic bank-oidc-secret --from-literal=OIDC_JWKENDPOINTURL=$oauthserverurl/publickeys --from-literal=OIDC_ISSUERIDENTIFIER=$oauthserverurl --from-literal=OIDC_AUDIENCES=$clientid 35 | 36 | oc create secret generic bank-appid-secret --from-literal=APPID_TENANTID=$tenantid --from-literal=APPID_SERVICE_URL=https://$appidhost 37 | 38 | oc create secret generic bank-iam-secret --from-literal=IAM_APIKEY=$APIKEY --from-literal=IAM_SERVICE_URL=https://iam.cloud.ibm.com/identity/token 39 | 40 | oc create secret generic mobile-simulator-secrets \ 41 | --from-literal=APP_ID_IAM_APIKEY=$APIKEY \ 42 | --from-literal=APP_ID_MANAGEMENT_URL=$MGMTEP \ 43 | --from-literal=APP_ID_CLIENT_ID=$clientid \ 44 | --from-literal=APP_ID_CLIENT_SECRET=$secret \ 45 | --from-literal=APP_ID_TOKEN_URL=$oauthserverurl \ 46 | --from-literal=PROXY_USER_MICROSERVICE=user-service:9080 \ 47 | --from-literal=PROXY_TRANSACTION_MICROSERVICE=transaction-service:9080 48 | 49 | oc create secret generic bank-oidc-adminuser --from-literal=APP_ID_ADMIN_USER=bankadmin --from-literal=APP_ID_ADMIN_PASSWORD=password 50 | 51 | oc create secret generic bank-db-secret --from-literal=DB_SERVERNAME=creditdb --from-literal=DB_PORTNUMBER=5432 --from-literal=DB_DATABASENAME=example --from-literal=DB_USER=postgres --from-literal=DB_PASSWORD=postgres 52 | -------------------------------------------------------------------------------- /scripts/creditdb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: postgresql.dev4devs.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | generation: 1 5 | name: creditdb 6 | namespace: example-bank 7 | spec: 8 | databaseCpu: 30m 9 | databaseCpuLimit: 60m 10 | databaseMemoryLimit: 512Mi 11 | databaseMemoryRequest: 128Mi 12 | databaseName: example 13 | databaseNameKeyEnvVar: POSTGRESQL_DATABASE 14 | databasePassword: postgres 15 | databasePasswordKeyEnvVar: POSTGRESQL_PASSWORD 16 | databaseStorageRequest: 1Gi 17 | databaseUser: postgres 18 | databaseUserKeyEnvVar: POSTGRESQL_USER 19 | image: centos/postgresql-96-centos7 20 | size: 1 21 | -------------------------------------------------------------------------------- /scripts/deleteresources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | 4 | 5 | # Delete resources before removing namespace 6 | oc delete all --all -n example-bank 7 | sleep 2 8 | 9 | ## begin delete openshift serverless 10 | # delete knative-serving component 11 | oc delete knativeservings.operator.knative.dev knative-serving -n knative-serving 12 | oc delete namespace knative-serving 13 | oc delete knativeeventings.operator.knative.dev knative-eventing -n knative-eventing 14 | oc delete namespace knative-eventing 15 | 16 | # delete subscription and clusterserviceversion of openshift serverless operator 17 | oc delete subscription serverless-operator -n openshift-operators 18 | oc delete csv $(oc get csv -n openshift-operators -o=jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep serverless-operator) -n openshift-operators 19 | 20 | ## end of delete openshift serverless 21 | 22 | 23 | # Service mesh cleanup 24 | 25 | oc delete smcp --all -n istio-system 26 | oc delete smmr --all -n istio-system 27 | 28 | 29 | oc delete validatingwebhookconfiguration/openshift-operators.servicemesh-resources.maistra.io 30 | oc delete mutatingwebhookconfigurations/openshift-operators.servicemesh-resources.maistra.io 31 | oc delete -n openshift-operators daemonset/istio-node 32 | oc delete clusterrole/istio-admin clusterrole/istio-cni clusterrolebinding/istio-cni 33 | oc delete subs --all --all-namespaces 34 | oc get crds -o name | grep '.*\.istio\.io' | xargs -r -n 1 oc delete 35 | oc get crds -o name | grep '.*\.maistra\.io' | xargs -r -n 1 oc delete 36 | oc get crds -o name | grep '.*\.kiali\.io' | xargs -r -n 1 oc delete 37 | 38 | oc delete all --all -n istio-system 39 | sleep 2 40 | 41 | ## Delete projects 42 | 43 | oc delete project example-bank 44 | oc delete project istio-system 45 | 46 | ## Delete jaeger,elasticsearch,kialia,servicemesh operators 47 | 48 | oc delete csv -n openshift-operators $(oc get csv -n openshift-operators -o=jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep 'elasticsearch-operator\|jaeger-operator\|kiali-operator\|servicemeshoperator') -------------------------------------------------------------------------------- /scripts/deploy-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Deploying PostgreSQL 1/3" 3 | kubectl apply -f pg_csv.yaml -f pg_sub.yaml -f pg_operatorgroup.yaml 4 | echo "Deploying PostgreSQL 2/3" 5 | sleep 5 6 | kubectl apply -f pg_deploy.yaml 7 | echo "Deploying PostgreSQL 2/3" 8 | sleep 3 9 | kubectl apply -f creditdb.yaml 10 | -------------------------------------------------------------------------------- /scripts/installServerlessOperator.sh: -------------------------------------------------------------------------------- 1 | echo "Creating OpenShift Serverless operator" 2 | 3 | cat <