Personal website / portfolio https://www.ironoc.net/
- Java 25 (LTS), Spring Boot 4, ReactJs 19, Maven 4, HTML5+CSS, Docker / Bash, AWS, minikube, & kubectl.
Note: iRonoc is an active user of the GitHub API & conorheffron is a proud participant in the GitHub Developer Program.
- The
iRonocPM tool is designed to streamline project management by automating project navigation & issue tracking. - By leveraging the GitHub API, the aim is to enhance the developer experience and contribute to the vibrant GitHub ecosystem.
- As part of the GitHub Developer Program, it is important to commit to adhering to best practices & continuously improving the integration to provide value to users.
For a comprehensive architectural blueprint, including C4 System Design Blueprints, Comprehensive UX Journey Diagrams, granular backend service breakdowns, and data flow sequence mapping for dynamic features (such as Subscriptions and Caching), please refer to the:
π Architecture Documentation & Data Flows (ARCHITECTURE.md)
See ironoc-frontend README here
- Backend requests to
/api/**and/graphqlnow pass through an in-memory Bucket4j interceptor. GETrequests are limited to60requests per minute per client IP.POSTandPUTrequests are limited to10requests per minute per client IP and return429 Too Many Requestswith aRetry-Afterheader when the bucket is empty.- Frontend follow-up options researched for later hardening: debounce repeat user actions, cache repeated reads before re-requesting
/api/**, and add client-side handling for429responses with a short backoff/retry message. - Backend alternatives researched for a future production rollout: Spring Cloud Gateway/Redis for distributed limits, or edge/CDN controls such as WAF/CDN rate limiting in front of the app.
src
βββ main
βΒ Β βββ java
βΒ Β βΒ Β βββ net
βΒ Β βΒ Β βββ ironoc
βΒ Β βΒ Β βββ portfolio
βΒ Β βΒ Β βββ App.java
βΒ Β βΒ Β βββ aws
βΒ Β βΒ Β βΒ Β βββ AwsSecretManager.java
βΒ Β βΒ Β βΒ Β βββ SecretManager.java
βΒ Β βΒ Β βββ client
βΒ Β βΒ Β βΒ Β βββ Client.java
βΒ Β βΒ Β βΒ Β βββ GitClient.java
βΒ Β βΒ Β βββ config
βΒ Β βΒ Β βΒ Β βββ GraphiQlConfiguration.java
βΒ Β βΒ Β βΒ Β βββ IronocConfiguration.java
βΒ Β βΒ Β βΒ Β βββ Properties.java
βΒ Β βΒ Β βΒ Β βββ PropertyConfig.java
βΒ Β βΒ Β βΒ Β βββ PropertyConfigI.java
βΒ Β βΒ Β βΒ Β βββ PropertyKey.java
βΒ Β βΒ Β βΒ Β βββ PropertyKeyI.java
βΒ Β βΒ Β βββ controller
βΒ Β βΒ Β βΒ Β βββ BrewGraphqlController.java
βΒ Β βΒ Β βΒ Β βββ CoffeeController.java
βΒ Β βΒ Β βΒ Β βββ DonateGraphqlController.java
βΒ Β βΒ Β βΒ Β βββ DonateRestController.java
βΒ Β βΒ Β βΒ Β βββ GitProjectsController.java
βΒ Β βΒ Β βΒ Β βββ PortfolioController.java
βΒ Β βΒ Β βΒ Β βββ VersionController.java
βΒ Β βΒ Β βββ domain
βΒ Β βΒ Β βΒ Β βββ CoffeeDomain.java
βΒ Β βΒ Β βΒ Β βββ IngredientsDeserializer.java
βΒ Β βΒ Β βΒ Β βββ RepositoryDetailDomain.java
βΒ Β βΒ Β βΒ Β βββ RepositoryIssueDomain.java
βΒ Β βΒ Β βββ dto
βΒ Β βΒ Β βΒ Β βββ Brew.java
βΒ Β βΒ Β βΒ Β βββ Donate.java
βΒ Β βΒ Β βΒ Β βββ DonateItemOrder.java
βΒ Β βΒ Β βΒ Β βββ LabelDto.java
βΒ Β βΒ Β βΒ Β βββ OwnerDto.java
βΒ Β βΒ Β βΒ Β βββ RepositoryDetailDto.java
βΒ Β βΒ Β βΒ Β βββ RepositoryIssueDto.java
βΒ Β βΒ Β βββ enums
βΒ Β βΒ Β βΒ Β βββ SortingOrder.java
βΒ Β βΒ Β βββ exception
βΒ Β βΒ Β βΒ Β βββ IronocJsonException.java
βΒ Β βΒ Β βββ filter
βΒ Β βΒ Β βΒ Β βββ RequestRateLimitingInterceptor.java
βΒ Β βΒ Β βββ graph
βΒ Β βΒ Β βΒ Β βββ BrewsResolver.java
βΒ Β βΒ Β βΒ Β βββ DonateItemsResolver.java
βΒ Β βΒ Β βββ job
βΒ Β βΒ Β βΒ Β βββ GitDetailsJob.java
βΒ Β βΒ Β βΒ Β βββ GitDetailsRunnable.java
βΒ Β βΒ Β βββ logger
βΒ Β βΒ Β βΒ Β βββ AbstractLogger.java
βΒ Β βΒ Β βΒ Β βββ LoggerI.java
βΒ Β βΒ Β βββ resolver
βΒ Β βΒ Β βΒ Β βββ PushStateResourceResolver.java
βΒ Β βΒ Β βββ service
βΒ Β βΒ Β βΒ Β βββ AbstractGitCache.java
βΒ Β βΒ Β βΒ Β βββ CoffeeCacheService.java
βΒ Β βΒ Β βΒ Β βββ Coffees.java
βΒ Β βΒ Β βΒ Β βββ CoffeesCache.java
βΒ Β βΒ Β βΒ Β βββ CoffeesService.java
βΒ Β βΒ Β βΒ Β βββ GitDetails.java
βΒ Β βΒ Β βΒ Β βββ GitDetailsService.java
βΒ Β βΒ Β βΒ Β βββ GitProjectCache.java
βΒ Β βΒ Β βΒ Β βββ GitProjectCacheService.java
βΒ Β βΒ Β βΒ Β βββ GitRepoCache.java
βΒ Β βΒ Β βΒ Β βββ GitRepoCacheService.java
βΒ Β βΒ Β βΒ Β βββ GraphQLClient.java
βΒ Β βΒ Β βΒ Β βββ GraphQLClientService.java
βΒ Β βΒ Β βΒ Β βββ PortfolioItemsResolver.java
βΒ Β βΒ Β βββ utils
βΒ Β βΒ Β βββ UrlUtils.java
βΒ Β βββ resources
βΒ Β βββ application.yml
βΒ Β βββ graphiql
βΒ Β βΒ Β βββ index.html
βΒ Β βββ graphql
βΒ Β βΒ Β βββ charities.txt
βΒ Β βΒ Β βββ coffeesQuery.graphqls
βΒ Β βΒ Β βββ ironocGraphSchema.graphql
βΒ Β βββ json
βΒ Β βΒ Β βββ brews.json
βΒ Β βΒ Β βββ donate-items.json
βΒ Β βΒ Β βββ portfolio-items.json
βΒ Β βββ static
βΒ Β βββ ironoc-banner.txt
βΒ Β βββ swagger-ui
βββ test
βββ java
βΒ Β βββ net
βΒ Β βββ ironoc
βΒ Β βββ portfolio
βΒ Β βββ AppTest.java
βΒ Β βββ RemoteBrowserBasedIntTest.java
βΒ Β βββ SeleniumConfig.java
βΒ Β βββ aws
βΒ Β βΒ Β βββ AwsSecretManagerTest.java
βΒ Β βββ client
βΒ Β βΒ Β βββ GitClientTest.java
βΒ Β βββ config
βΒ Β βΒ Β βββ IronocConfigurationTest.java
βΒ Β βΒ Β βββ PropertyConfigTest.java
βΒ Β βΒ Β βββ PropertyKeyTest.java
βΒ Β βΒ Β βββ TestIronocConfiguration.java
βΒ Β βββ controller
βΒ Β βΒ Β βββ BaseControllerIntegrationTest.java
βΒ Β βΒ Β βββ BrewGraphqlControllerTest.java
βΒ Β βΒ Β βββ CoffeeControllerIntegrationTest.java
βΒ Β βΒ Β βββ CoffeeControllerTest.java
βΒ Β βΒ Β βββ DonateGraphqlControllerTest.java
βΒ Β βΒ Β βββ DonateRestControllerTest.java
βΒ Β βΒ Β βββ GitProjectsControllerIntegrationTest.java
βΒ Β βΒ Β βββ GitProjectsControllerTest.java
βΒ Β βΒ Β βββ PortfolioControllerTest.java
βΒ Β βΒ Β βββ VersionControllerTest.java
βΒ Β βββ graph
βΒ Β βΒ Β βββ BrewsResolverTest.java
βΒ Β βΒ Β βββ DonateItemsResolverTest.java
βΒ Β βββ job
βΒ Β βΒ Β βββ GitDetailsJobTest.java
βΒ Β βΒ Β βββ GitDetailsRunnableTest.java
βΒ Β βββ resolver
βΒ Β βΒ Β βββ PushStateResourceResolverTest.java
βΒ Β βββ service
βΒ Β βΒ Β βββ CoffeesServiceTest.java
βΒ Β βΒ Β βββ GitDetailsServiceTest.java
βΒ Β βΒ Β βββ GitRepoCacheServiceTest.java
βΒ Β βΒ Β βββ GraphQLClientServiceTest.java
βΒ Β βΒ Β βββ PortfolioItemsResolverTest.java
βΒ Β βββ utils
βΒ Β βΒ Β βββ TestRequestResponseUtils.java
βΒ Β βΒ Β βββ UrlUtilsTest.java
βΒ Β βββ web
βΒ Β βββ page
βΒ Β βββ AboutPage.java
βΒ Β βββ BrewsPage.java
βΒ Β βββ DonatePage.java
βΒ Β βββ HomePage.java
βΒ Β βββ PortfolioPage.java
βββ resources
βββ graphql
βΒ Β βββ charities.txt
βΒ Β βββ iRonocQuery.graphqls.SAMPLE
βΒ Β βββ query.graphql
βββ json
βββ donate-items.json
βββ test_coffees_expected_response.json
βββ test_coffees_input_response.json
βββ test_issues_response.json
βββ test_parse_null_response.json
βββ test_repo_detail_response.json
βββ test_response.jsondocker run -d --restart=always -p 8080:8080 conorheffron/ironoc
aws configure
aws iam get-user
aws sts get-session-token./mvnw clean package -UView in browser at http://localhost:63342/ironoc/target/site/jacoco/index.html?_ijt=s0hqlj2p4s6554gldritju13qr&_ij_reload=RELOAD_ON_SAVE
Note: Change port as needed/identifier as this is URL generated during build process & opened via IDE window.
cd frontend
npm install [--force or --legacy-peer-deps]
npm run build
rpm run start
npm run test
npm run test:coverage./mvnw -DAWS_ACCESS_KEY_ID="<val1>" \
-DAWS_REGION=<val2> \
-DAWS_SECRET_ACCESS_KEY="<val3>" \
-DAWS_SESSION_TOKEN="<val4>" \
spring-boot:rundocker image build -t ironoc .
docker compose up -d
docker logs ironoc-portfolio-1 -f
docker compose down
MiniKube Install Notes for mac users
brew install kubectl
brew install virtualbox
brew install / upgrade minikube
Oracle VirtualBox Manager v7.1.4
% kubectl version
Client Version: v1.30.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.31.0
% minikube version
minikube version: v1.34.0
commit: 210b148df93a80eb872ecbeb7e35281b3c582c61
% docker version
Client:
Version: 27.3.1
API version: 1.47
Go version: go1.22.7
Git commit: ce12230
Built: Fri Sep 20 11:38:18 2024
OS/Arch: darwin/amd64
Context: desktop-linux- Open terminal
% minikube start --driver=docker
π minikube v1.35.0 on Darwin 15.3.1
π Kubernetes 1.32.0 is now available. If you would like to upgrade, specify: --kubernetes-version=v1.32.0
β¨ Using the docker driver based on existing profile
π Starting "minikube" primary control-plane node in "minikube" cluster
π Pulling base image v0.0.46 ...
π Updating the running docker "minikube" container ...
β Image was not built for the current minikube version. To resolve this you can delete and recreate your minikube cluster using the latest images. Expected minikube version: v1.34.0 -> Actual minikube version: v1.35.0
π³ Preparing Kubernetes v1.31.0 on Docker 27.2.0 ...
π Configuring bridge CNI (Container Networking Interface) ...
π Verifying Kubernetes components...
βͺ Using image docker.io/kubernetesui/metrics-scraper:v1.0.8
βͺ Using image gcr.io/k8s-minikube/storage-provisioner:v5
βͺ Using image docker.io/kubernetesui/dashboard:v2.7.0
π‘ Some dashboard features require the metrics-server addon. To enable all features please run:
minikube addons enable metrics-server
π Enabled addons: storage-provisioner, default-storageclass, dashboard
π Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
% kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:63402
CoreDNS is running at https://127.0.0.1:63402/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
% minikube dashboard
π€ Verifying dashboard health ...
π Launching proxy ...
π€ Verifying proxy health ...
π Opening http://127.0.0.1:63612/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...% docker image build -t ironoc .
[+] Building 100.5s (11/11) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 284B 0.0s
=> [internal] load metadata for docker.io/library/eclipse-temurin:21-jdk 2.0s
=> [auth] library/eclipse-temurin:pull token for registry-1.docker.io 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
.
.
.
% minikube image load ironoc:latest
% kubectl create ns ironoc-ns
namespace/ironoc-ns created
% kubectl apply -f k8s/ironoc.yaml --namespace=ironoc-ns
deployment.apps/ironoc-app-deployment created
horizontalpodautoscaler.autoscaling/ironoc-app-deployment-hpa-kbij created
% kubectl get pods --namespace=ironoc-ns
NAME READY STATUS RESTARTS AGE
ironoc-app-deployment-6d84f75b44-5xvgj 1/1 Running 0 8s
% kubectl get deployment --namespace=ironoc-ns
NAME READY UP-TO-DATE AVAILABLE AGE
ironoc-app-deployment 1/1 1 1 105s
% kubectl expose deployment ironoc-app-deployment --type=NodePort --namespace=ironoc-ns
service/ironoc-app-deployment exposed
% kubectl get svc --namespace=ironoc-ns
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ironoc-app-deployment NodePort 10.98.229.246 <none> 8080:31602/TCP 6s
% minikube service ironoc-app-deployment --url --namespace=ironoc-ns
http://127.0.0.1:63455
β Because you are using a Docker driver on darwin, the terminal needs to be open to run it.% kubectl get pods --namespace=ironoc-ns
NAME READY STATUS RESTARTS AGE
ironoc-app-deployment-6d84f75b44-5xvgj 1/1 Running 0 3m28s
% kubectl logs ironoc-app-deployment-6d84f75b44-5xvgj -f --namespace=ironoc-ns
java -jar /app.war
_________ _______ _______ _ _______ _______
\__ __/( ____ )( ___ )( ( /|( ___ )( ____ \
) ( | ( )|| ( ) || \ ( || ( ) || ( \/
| | | (____)|| | | || \ | || | | || |
| | | __)| | | || (\ \) || | | || |
| | | (\ ( | | | || | \ || | | || |
___) (___| ) \ \__| (___) || ) \ || (___) || (____/\
\_______/|/ \__/(_______)|/ )_)(_______)(_______/
2025-05-19T18:47:04.581Z INFO 7 --- [ main] n.i.p.App : Starting App v7.3.7 using Java 21.0.7 with PID 7 (/app.war started by root in /)
2025-05-19T18:47:04.765Z INFO 7 --- [ main] n.i.p.App : No active profile set, falling back to 1 default profile: "default"
2025-05-19T18:47:22.489Z INFO 7 --- [ main] o.s.b.w.e.t.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-05-19T18:47:22.788Z INFO 7 --- [ main] o.a.c.c.StandardService : Starting service [Tomcat]
2025-05-19T18:47:22.790Z INFO 7 --- [ main] o.a.c.c.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.40]
2025-05-19T18:48:03.677Z INFO 7 --- [ main] o.a.j.s.TldScanner : At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
2025-05-19T18:48:04.178Z INFO 7 --- [ main] o.a.c.c.C.[.[.[/] : Initializing Spring embedded WebApplicationContext
2025-05-19T18:48:04.181Z INFO 7 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 58800 ms
2025-05-19T18:48:06.066Z WARN 7 --- [ main] n.i.p.l.AbstractLogger : The job to pre-populate the cache of GitHub information is disabled.
2025-05-19T18:48:08.397Z INFO 7 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
2025-05-19T18:48:10.364Z INFO 7 --- [ main] efaultSchemaResourceGraphQlSourceBuilder : Loaded 1 resource(s) in the GraphQL schema.
2025-05-19T18:48:11.493Z INFO 7 --- [ main] o.s.b.a.g.GraphQlAutoConfiguration : GraphQL schema inspection:
Unmapped fields: {DonateItem=[donate, link, img, alt, name, overview, founded, phone], Query=[donateItemsByCountAndOffset]}
Unmapped registrations: {Query.donateItemsSchemaMapping=DonateGraphqlController#donateItemsSchemaMapping[0 args], Query.charityOptionByDonateLink=DonateGraphqlController#charityOptionByDonateLink[1 args], Donate.charityOptions=DonateGraphqlController#charityOptions[1 args]}
Unmapped arguments: {}
Skipped types: []
2025-05-19T18:48:11.699Z INFO 7 --- [ main] s.b.a.g.s.GraphQlWebMvcAutoConfiguration : GraphQL endpoint HTTP POST /graphql
2025-05-19T18:48:13.978Z INFO 7 --- [ main] o.s.b.w.e.t.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-05-19T18:48:14.061Z INFO 7 --- [ main] n.i.p.App : Started App in 73.393 seconds (process running for 81.378)
2025-05-19T18:50:24.815Z INFO 7 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-05-19T18:50:24.816Z INFO 7 --- [nio-8080-exec-1] o.s.w.s.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-05-19T18:50:24.821Z INFO 7 --- [nio-8080-exec-1] o.s.w.s.DispatcherServlet : Completed initialization in 5 ms
2025-05-19T18:50:27.308Z INFO 7 --- [nio-8080-exec-5] n.i.p.l.AbstractLogger : Retrieved brews from GraphQL query, coffeeDomains=[name: 'Black Coffee' id: '1', name: 'Latte' id: '2', name: 'Caramel Latte' id: '3', name: 'Cappuccino' id: '4', name: 'Americano' id: '5', name: 'Espresso' id: '6', name: 'Macchiato' id: '7', name: 'Mocha' id: '8', name: 'Hot Chocolate' id: '9', name: 'Chai Latte' id: '10', name: 'Matcha Latte' id: '11', name: 'Seasonal Brew' id: '12', name: 'Svart Te' id: '13', name: 'Islatte' id: '14', name: 'Islatte Mocha' id: '15', name: 'Frapino Caramel' id: '16', name: 'Frapino Mocka' id: '17', name: 'Apelsinjuice' id: '18', name: 'Frozen Lemonade' id: '19', name: 'Lemonad' id: '20', name: 'Iced Coffee' id: '1', name: 'Iced Espresso' id: '2', name: 'Cold Brew' id: '3', name: 'Frappuccino' id: '4', name: 'Nitro' id: '5', name: 'Mazagran' id: '6']% minikube delete
π₯ Deleting "minikube" in docker ...
π₯ Deleting container "minikube" ...
π₯ Removing /home/.minikube/machines/minikube ...
π Removed all traces of the "minikube" cluster.http://localhost:8080/graphiql?path=/graphql
or
https://ironoc.net/graphiql?path=/graphql











