diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..679c54a --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(/opt/homebrew/bin/gh pr create --draft --title 'feat\\(finance\\): improved UX across dashboard, transactions, reports and categories' --body ' *)", + "Bash(/opt/homebrew/bin/gh auth *)", + "Bash(go build *)", + "Bash(go test *)", + "Bash(git add *)", + "Bash(git commit -m ' *)", + "Bash(git push *)", + "mcp__visualize__read_me" + ] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..471111c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Test + run: go test ./... diff --git a/Makefile b/Makefile index d380f21..62f757e 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ SHELL := /bin/zsh K3D_SCRIPT := infrastructure/k3d/k3d.sh TERRAFORM := terraform +SHA := $(shell git rev-parse --short HEAD) .PHONY: up up: ## Create the k3d dev cluster @@ -19,13 +20,28 @@ infra: ## Deploy shared infrastructure (MongoDB, monitoring, Traefik metrics) SERVICES := $(shell find apps -name Makefile -path "*/services/*" -exec dirname {} \;) +.PHONY: deploy-finance +deploy-finance: ## Build and deploy the finance API + $(MAKE) -C apps/finance/services/api build-deploy IMAGE_TAG=$(SHA) + +.PHONY: deploy-auth-users +deploy-auth-users: ## Build and deploy the auth users service + $(MAKE) -C apps/auth/services/users build-deploy IMAGE_TAG=$(SHA) + +.PHONY: deploy-auth-gateway +deploy-auth-gateway: ## Build and deploy the auth gateway service + $(MAKE) -C apps/auth/services/gateway build-deploy IMAGE_TAG=$(SHA) + +.PHONY: test +test: ## Run all tests + go test ./... + .PHONY: deploy-all deploy-all: ## Build, load, deploy, and restart every service @for dir in $(SERVICES); do \ echo "\033[36m>>> $$dir\033[0m"; \ - $(MAKE) -C "$$dir" build-deploy || true; \ + $(MAKE) -C "$$dir" build-deploy IMAGE_TAG=$(SHA) || true; \ done - $(MAKE) restart-all .PHONY: restart-all restart-all: ## Restart all deployments (pick up new images) diff --git a/apps/auth/services/gateway/k8s/deployment.yaml b/apps/auth/services/gateway/k8s/deployment.yaml index ade73ba..25b87ba 100644 --- a/apps/auth/services/gateway/k8s/deployment.yaml +++ b/apps/auth/services/gateway/k8s/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: gateway - image: homelab/gateway:latest + image: homelab/auth-gateway:latest imagePullPolicy: IfNotPresent ports: - name: http diff --git a/apps/auth/services/users/k8s/deployment.yaml b/apps/auth/services/users/k8s/deployment.yaml index 84e3d1d..0c27fbc 100644 --- a/apps/auth/services/users/k8s/deployment.yaml +++ b/apps/auth/services/users/k8s/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: users - image: homelab/users:latest + image: homelab/auth-users:latest imagePullPolicy: IfNotPresent ports: - name: http diff --git a/apps/finance/services/api/k8s/deployment.yaml b/apps/finance/services/api/k8s/deployment.yaml index ccf2151..4c19aa6 100644 --- a/apps/finance/services/api/k8s/deployment.yaml +++ b/apps/finance/services/api/k8s/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: api - image: homelab/api:latest + image: homelab/finance-api:latest imagePullPolicy: IfNotPresent ports: - name: http diff --git a/infrastructure/terraform/.terraform.lock.hcl b/infrastructure/terraform/.terraform.lock.hcl index 96fb950..1b494bf 100644 --- a/infrastructure/terraform/.terraform.lock.hcl +++ b/infrastructure/terraform/.terraform.lock.hcl @@ -61,3 +61,25 @@ provider "registry.terraform.io/hashicorp/random" { "zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04", ] } + +provider "registry.terraform.io/integrations/github" { + version = "6.12.1" + constraints = "~> 6.0" + hashes = [ + "h1:hHOwf554tYL9pQS2uRPbaAGSXRbbBJ/+HtQAoT9mtuA=", + "zh:3e1a4081ecb9518fdf0074db83c16ad00dc81ffe8249a6e3cf1894e947e28df6", + "zh:4cb8224b7f530795b674ac044675f6b22a7c9154f55eb9f76c5af6c7534056a4", + "zh:560bc08637926191f6871a89e986022ca67c70afda5bebca34b5216e6fac69c9", + "zh:5a70b5d2ac650c5c9819a1875411ebda229d0fcc6c9f57f9d751852ca3cd77ac", + "zh:8668d93bd4dc2ffa2545e1473af600a925d479b16033a71a4498a16f3b683c0c", + "zh:86eacc6059fd057948e178b665ba5cce74bd5488a9e1035734e60ff5ef1b6f8f", + "zh:a329fac98881d8dfc211a9bdc0ec6f2948f0b0c2704d1b6cbe5307403c7ad1b2", + "zh:dadd44abab3c52b9d572955afaef1658790e17ea355ee22b58996d81d28e02d8", + "zh:de9f455ef342cc38fb76bce844bfcd376fb81a4b9f9bc2fae023ff99efdf1338", + "zh:f8c6d2e8351b334491790358574e0a30a7c6d7f5b80f7daf32a7c0f3e9b1ab19", + "zh:fab41971a3edee04ab6eceaeab4eeb9a2b2f38a2af3b06eda93e2117b64994be", + "zh:fb1279b566dd9c8c117b2e4e0cc8344413b8fc8f2a3e24be22a9b2610551777b", + "zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25", + "zh:fe79d2a861fb9af420fa5bd7f02c031b2a0a3edf5dbc46022c8ecc7a33cf2b6d", + ] +} diff --git a/infrastructure/terraform/terraform.tfstate.backup b/infrastructure/terraform/terraform.tfstate.backup index 16c9dfb..f542bd2 100644 --- a/infrastructure/terraform/terraform.tfstate.backup +++ b/infrastructure/terraform/terraform.tfstate.backup @@ -1,7 +1,7 @@ { "version": 4, "terraform_version": "1.15.5", - "serial": 352, + "serial": 412, "lineage": "28673c1d-998f-000c-38f5-de7c9e848250", "outputs": {}, "resources": [ @@ -34,8 +34,8 @@ { "app_version": "3.2.10", "chart": "fluent-bit", - "first_deployed": 1780862639, - "last_deployed": 1780862639, + "first_deployed": 1781347541, + "last_deployed": 1781347541, "name": "fluent-bit", "namespace": "monitoring", "notes": "Get Fluent Bit build information by running these commands:\n\nexport POD_NAME=$(kubectl get pods --namespace monitoring -l \"app.kubernetes.io/name=fluent-bit,app.kubernetes.io/instance=fluent-bit\" -o jsonpath=\"{.items[0].metadata.name}\")\nkubectl --namespace monitoring port-forward $POD_NAME 2020:2020\ncurl http://127.0.0.1:2020 \n\n", @@ -119,8 +119,8 @@ { "app_version": "2.18.0", "chart": "jaeger", - "first_deployed": 1780862639, - "last_deployed": 1780862639, + "first_deployed": 1781347536, + "last_deployed": 1781347536, "name": "jaeger", "namespace": "monitoring", "notes": "###################################################################\n### ⚠️ EXPERIMENTAL - NO STABILITY GUARANTEES ###\n### ###\n### This chart is under active development. ###\n### Breaking changes may occur in minor versions. ###\n### ###\n### See README.md for configuration details. ###\n###################################################################\n\n🚀 Congratulations on successfully installing Jaeger v2.18.0 (Chart v4.8.0)!\n\nTo access the query UI:\n http://jaeger.homelab.local\n", @@ -175,105 +175,6 @@ } ] }, - { - "mode": "managed", - "type": "helm_release", - "name": "kube_prometheus_stack", - "provider": "provider[\"registry.terraform.io/hashicorp/helm\"]", - "instances": [ - { - "schema_version": 1, - "attributes": { - "atomic": true, - "chart": "kube-prometheus-stack", - "cleanup_on_fail": false, - "create_namespace": false, - "dependency_update": false, - "description": null, - "devel": null, - "disable_crd_hooks": false, - "disable_openapi_validation": false, - "disable_webhooks": false, - "force_update": false, - "id": "kps", - "keyring": null, - "lint": false, - "manifest": null, - "max_history": 0, - "metadata": [ - { - "app_version": "v0.91.0", - "chart": "kube-prometheus-stack", - "first_deployed": 1780862636, - "last_deployed": 1780862636, - "name": "kps", - "namespace": "monitoring", - "notes": "kube-prometheus-stack has been installed. Check its status by running:\n kubectl --namespace monitoring get pods -l \"release=kps\"\n\nGet Grafana 'admin' user password by running:\n\n kubectl --namespace monitoring get secrets kps-grafana -o jsonpath=\"{.data.admin-password}\" | base64 -d ; echo\n\nAccess Grafana local instance:\n\n export POD_NAME=$(kubectl --namespace monitoring get pod -l \"app.kubernetes.io/name=grafana,app.kubernetes.io/instance=kps\" -oname)\n kubectl --namespace monitoring port-forward $POD_NAME 3000\n\nGet your grafana admin user password by running:\n\n kubectl get secret --namespace monitoring -l app.kubernetes.io/component=admin-secret -o jsonpath=\"{.items[0].data.admin-password}\" | base64 --decode ; echo\n\n\nVisit https://github.com/prometheus-operator/kube-prometheus for instructions on how to create \u0026 configure Alertmanager and Prometheus instances using the Operator.\n\nkube-state-metrics is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects.\nThe exposed metrics can be found here:\nhttps://github.com/kubernetes/kube-state-metrics/blob/master/docs/README.md#exposed-metrics\n\nThe metrics are exported on the HTTP endpoint /metrics on the listening port.\nIn your case, kps-kube-state-metrics.monitoring.svc.cluster.local:8080/metrics\n\nThey are served either as plaintext or protobuf depending on the Accept header.\nThey are designed to be consumed either by Prometheus itself or by a scraper that is compatible with scraping a Prometheus client endpoint.\n\n1. Get the application URL by running these commands:\n export POD_NAME=$(kubectl get pods --namespace monitoring -l \"app.kubernetes.io/name=prometheus-node-exporter,app.kubernetes.io/instance=kps\" -o jsonpath=\"{.items[0].metadata.name}\")\n echo \"Visit http://127.0.0.1:9100 to use your application\"\n kubectl port-forward --namespace monitoring $POD_NAME 9100\n1. Get your 'admin' user password by running:\n\n kubectl get secret --namespace monitoring kps-grafana -o jsonpath=\"{.data.admin-password}\" | base64 --decode ; echo\n\n\n2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:\n\n kps-grafana.monitoring.svc.cluster.local\n\n If you bind grafana to 80, please update values in values.yaml and reinstall:\n ```\n securityContext:\n runAsUser: 0\n runAsGroup: 0\n fsGroup: 0\n\n command:\n - \"setcap\"\n - \"'cap_net_bind_service=+ep'\"\n - \"/usr/sbin/grafana-server \u0026\u0026\"\n - \"sh\"\n - \"/run.sh\"\n ```\n Details refer to https://grafana.com/docs/installation/configuration/#http-port.\n Or grafana would always crash.\n\n From outside the cluster, the server URL(s) are:\n http://grafana.homelab.local\n\n3. Login with the password from step 1 and the username: admin\n#################################################################################\n###### WARNING: Persistence is disabled!!! You will lose your data when #####\n###### the Grafana pod is terminated. #####\n#################################################################################\n", - "revision": 1, - "values": "{\"alertmanager\":{\"enabled\":false},\"grafana\":{\"additionalDataSources\":[{\"access\":\"proxy\",\"isDefault\":false,\"name\":\"Jaeger\",\"type\":\"jaeger\",\"uid\":\"jaeger\",\"url\":\"http://jaeger.monitoring.svc:16686\"},{\"access\":\"proxy\",\"isDefault\":false,\"name\":\"Loki\",\"type\":\"loki\",\"uid\":\"loki\",\"url\":\"http://loki-gateway.monitoring.svc\"}],\"adminPassword\":\"jXWfhChbpD6QEDK2wLXbLHy7\",\"ingress\":{\"annotations\":{\"traefik.ingress.kubernetes.io/router.middlewares\":\"auth-forward-auth@kubernetescrd\"},\"enabled\":true,\"hosts\":[\"grafana.homelab.local\"],\"ingressClassName\":\"traefik\"}},\"kube-state-metrics\":{\"resources\":{\"requests\":{\"cpu\":\"50m\",\"memory\":\"128Mi\"}}},\"node-exporter\":{\"resources\":{\"requests\":{\"cpu\":\"25m\",\"memory\":\"64Mi\"}}},\"prometheus\":{\"prometheusSpec\":{\"resources\":{\"limits\":{\"cpu\":\"1\",\"memory\":\"1Gi\"},\"requests\":{\"cpu\":\"200m\",\"memory\":\"512Mi\"}}}}}", - "version": "86.0.2" - } - ], - "name": "kps", - "namespace": "monitoring", - "pass_credentials": false, - "postrender": [], - "recreate_pods": false, - "render_subchart_notes": true, - "replace": false, - "repository": "https://prometheus-community.github.io/helm-charts", - "repository_ca_file": null, - "repository_cert_file": null, - "repository_key_file": null, - "repository_password": null, - "repository_username": null, - "reset_values": false, - "reuse_values": false, - "set": [], - "set_list": [], - "set_sensitive": [], - "skip_crds": false, - "status": "deployed", - "timeout": 300, - "upgrade_install": null, - "values": [ - "\"alertmanager\":\n \"enabled\": false\n\"grafana\":\n \"additionalDataSources\":\n - \"access\": \"proxy\"\n \"isDefault\": false\n \"name\": \"Jaeger\"\n \"type\": \"jaeger\"\n \"uid\": \"jaeger\"\n \"url\": \"http://jaeger.monitoring.svc:16686\"\n - \"access\": \"proxy\"\n \"isDefault\": false\n \"name\": \"Loki\"\n \"type\": \"loki\"\n \"uid\": \"loki\"\n \"url\": \"http://loki-gateway.monitoring.svc\"\n \"adminPassword\": \"jXWfhChbpD6QEDK2wLXbLHy7\"\n \"ingress\":\n \"annotations\":\n \"traefik.ingress.kubernetes.io/router.middlewares\": \"auth-forward-auth@kubernetescrd\"\n \"enabled\": true\n \"hosts\":\n - \"grafana.homelab.local\"\n \"ingressClassName\": \"traefik\"\n\"kube-state-metrics\":\n \"resources\":\n \"requests\":\n \"cpu\": \"50m\"\n \"memory\": \"128Mi\"\n\"node-exporter\":\n \"resources\":\n \"requests\":\n \"cpu\": \"25m\"\n \"memory\": \"64Mi\"\n\"prometheus\":\n \"prometheusSpec\":\n \"resources\":\n \"limits\":\n \"cpu\": \"1\"\n \"memory\": \"1Gi\"\n \"requests\":\n \"cpu\": \"200m\"\n \"memory\": \"512Mi\"\n" - ], - "verify": false, - "version": "86.0.2", - "wait": true, - "wait_for_jobs": false - }, - "sensitive_attributes": [ - [ - { - "type": "get_attr", - "value": "repository_password" - } - ], - [ - { - "type": "get_attr", - "value": "values" - }, - { - "type": "index", - "value": { - "value": 0, - "type": "number" - } - } - ] - ], - "identity_schema_version": 0, - "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==", - "dependencies": [ - "kubernetes_namespace.domains", - "random_password.grafana" - ] - } - ] - }, { "mode": "managed", "type": "helm_release", @@ -303,8 +204,8 @@ { "app_version": "3.4.2", "chart": "loki", - "first_deployed": 1780862638, - "last_deployed": 1780862638, + "first_deployed": 1781347540, + "last_deployed": 1781347540, "name": "loki", "namespace": "monitoring", "notes": "***********************************************************************\n Welcome to Grafana Loki\n Chart version: 6.28.0\n Chart Name: loki\n Loki version: 3.4.2\n***********************************************************************\n\n** Please be patient while the chart is being deployed **\n\nTip:\n\n Watch the deployment status using the command: kubectl get pods -w --namespace monitoring\n\nIf pods are taking too long to schedule make sure pod affinity can be fulfilled in the current cluster.\n\n***********************************************************************\nInstalled components:\n***********************************************************************\n* loki\n\nLoki has been deployed as a single binary.\nThis means a single pod is handling reads and writes. You can scale that pod vertically by adding more CPU and memory resources.\n\n\n***********************************************************************\nSending logs to Loki\n***********************************************************************\n\nLoki has been configured with a gateway (nginx) to support reads and writes from a single component.\n\nYou can send logs from inside the cluster using the cluster DNS:\n\nhttp://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push\n\nYou can test to send data from outside the cluster by port-forwarding the gateway to your local machine:\n\n kubectl port-forward --namespace monitoring svc/loki-gateway 3100:80 \u0026\n\nAnd then using http://127.0.0.1:3100/loki/api/v1/push URL as shown below:\n\n```\ncurl -H \"Content-Type: application/json\" -XPOST -s \"http://127.0.0.1:3100/loki/api/v1/push\" \\\n--data-raw \"{\\\"streams\\\": [{\\\"stream\\\": {\\\"job\\\": \\\"test\\\"}, \\\"values\\\": [[\\\"$(date +%s)000000000\\\", \\\"fizzbuzz\\\"]]}]}\"\n```\n\nThen verify that Loki did receive the data using the following command:\n\n```\ncurl \"http://127.0.0.1:3100/loki/api/v1/query_range\" --data-urlencode 'query={job=\"test\"}' | jq .data.result\n```\n\n***********************************************************************\nConnecting Grafana to Loki\n***********************************************************************\n\nIf Grafana operates within the cluster, you'll set up a new Loki datasource by utilizing the following URL:\n\nhttp://loki-gateway.monitoring.svc.cluster.local/\n", @@ -377,8 +278,8 @@ "generation": 0, "labels": null, "name": "auth", - "resource_version": "563", - "uid": "955b1b5e-870d-4e10-ba2a-df9fd3837886" + "resource_version": "560", + "uid": "a6f4f18e-ebfa-445d-9021-01e7e3443df5" } ], "timeouts": null, @@ -400,8 +301,8 @@ "generation": 0, "labels": null, "name": "finance", - "resource_version": "560", - "uid": "c5e86a50-58dd-4449-8009-021fc4db6c47" + "resource_version": "558", + "uid": "bb8df1ed-024b-407c-8ded-65ba7d5983af" } ], "timeouts": null, @@ -423,8 +324,8 @@ "generation": 0, "labels": null, "name": "home", - "resource_version": "562", - "uid": "832f867c-1c87-4916-bb2a-6a553ff9fe97" + "resource_version": "561", + "uid": "b79f5a4a-345e-4fa7-9ab2-d4b71a6ac4d7" } ], "timeouts": null, @@ -446,8 +347,8 @@ "generation": 0, "labels": null, "name": "infrastructure", - "resource_version": "561", - "uid": "946fad48-8db3-4c65-901f-a41647aff588" + "resource_version": "563", + "uid": "9d9e5d6e-47fc-4833-b63e-8cc0d4fdce03" } ], "timeouts": null, @@ -470,7 +371,7 @@ "labels": null, "name": "monitoring", "resource_version": "559", - "uid": "890e74f8-5aab-4ab1-b29f-aeca95468824" + "uid": "bf35e1ff-1a6b-46f9-ae6c-3a67ac42a82e" } ], "timeouts": null, @@ -492,8 +393,8 @@ "generation": 0, "labels": null, "name": "test", - "resource_version": "564", - "uid": "cea8776d-af80-4031-9f2a-8db84300bbfc" + "resource_version": "557", + "uid": "d60671b5-4764-4bbb-acd0-bb02c246dcbc" } ], "timeouts": null, @@ -530,8 +431,8 @@ "labels": null, "name": "mongodb", "namespace": "infrastructure", - "resource_version": "571", - "uid": "b060268d-ca4e-435d-860e-40a3d88c7593" + "resource_version": "580", + "uid": "57167d70-7714-4c9e-ba80-333c83e483a9" } ], "timeouts": null, @@ -612,8 +513,8 @@ "labels": null, "name": "mongodb-shared-config", "namespace": "auth", - "resource_version": "576", - "uid": "d15b185d-4df9-459b-a404-e213f362284b" + "resource_version": "586", + "uid": "77fdd3c5-50ed-41f4-916c-3625cbbe244c" } ], "timeouts": null, @@ -673,8 +574,8 @@ "labels": null, "name": "mongodb-shared-config", "namespace": "finance", - "resource_version": "574", - "uid": "8b9cefc6-147f-4beb-99c5-7fd1845a26bb" + "resource_version": "587", + "uid": "def8ec73-bb14-475b-b26e-e3ae7146fe0a" } ], "timeouts": null, @@ -734,8 +635,8 @@ "labels": null, "name": "mongodb-shared-config", "namespace": "home", - "resource_version": "577", - "uid": "83e46b25-edec-42e9-9e03-fb394779d6d5" + "resource_version": "578", + "uid": "f8fb372f-98e4-4a59-9e40-ae4dd8424bc0" } ], "timeouts": null, @@ -795,8 +696,8 @@ "labels": null, "name": "mongodb-shared-config", "namespace": "infrastructure", - "resource_version": "589", - "uid": "470fd582-2e00-4c00-98b9-e42b67a2d455" + "resource_version": "577", + "uid": "f7604981-db0a-4ef0-b8ff-c633cd107b7a" } ], "timeouts": null, @@ -856,8 +757,8 @@ "labels": null, "name": "mongodb-shared-config", "namespace": "monitoring", - "resource_version": "588", - "uid": "1f20a3be-a400-47e9-8526-6c3825189302" + "resource_version": "576", + "uid": "72b10323-8251-4188-b980-456c4066abe8" } ], "timeouts": null, @@ -918,7 +819,7 @@ "name": "mongodb-shared-config", "namespace": "test", "resource_version": "575", - "uid": "df1de823-571b-46d3-b85b-5082b03b8bed" + "uid": "fc743768-ceee-4e2b-afb9-9edb671668a8" } ], "timeouts": null, @@ -979,16 +880,16 @@ "labels": null, "name": "mongodb", "namespace": "infrastructure", - "resource_version": "578", - "uid": "435bfa82-026a-4ad3-92af-5913673d9819" + "resource_version": "582", + "uid": "f555214a-ead6-4e93-9d9a-a2b522d71e24" } ], "spec": [ { "allocate_load_balancer_node_ports": true, - "cluster_ip": "10.43.49.246", + "cluster_ip": "10.43.171.195", "cluster_ips": [ - "10.43.49.246" + "10.43.171.195" ], "external_ips": null, "external_name": "", @@ -1060,8 +961,8 @@ "labels": null, "name": "mongodb", "namespace": "infrastructure", - "resource_version": "590", - "uid": "ea21f54f-e972-4fdf-97f2-a8c538e3868f" + "resource_version": "588", + "uid": "7e5f61fe-b6a1-457e-8db3-cbb13c0f671a" } ], "spec": [