diff --git a/README.md b/README.md index 981cf17d748410bface5cdddef7f332639476dc9..b4dfd1960efa7754c625ff3a58d2fa9336ce0cd1 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,18 @@ -# myaktion-go-msa - - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.reutlingen-university.de/poegeln/myaktion-go-msa.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.reutlingen-university.de/poegeln/myaktion-go-msa/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +# myaktion-go-msa + +An examplary application for the microservice-architecture provided under https://gitlab.reutlingen-university.de/poegel/microservice-architecture. + +## Getting started + +To make use of this example-application, the [base microservice-architecture](https://gitlab.reutlingen-university.de/poegel/microservice-architecture) is required. Make sure it is deployed correctly before deploying the example provided in this repository. + +The code used for this example uses [myaktion-go-2023](https://github.com/turngeek/myaktion-go-2023) as it's basis and only has been slighty adjusted to make use of the authentication service provided by the base microservices-architecture. + +## Deploying the application + +After deploying the [base microservice-architecture](https://gitlab.reutlingen-university.de/poegel/microservice-architecture), this application can be simply deployed in the kubernetes cluster by applying all files found in the configuration directory. +This can be achieved using following command: +``` +kubectl apply -f {filename} +``` +Make sure to deploy them in the correct order of: Namespace > PersistentVolume > Deployment > Service > IngressRoute \ No newline at end of file diff --git a/code/README.md b/code/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ab8fe55a24ce49d565b968f5f9dc62335089b8aa --- /dev/null +++ b/code/README.md @@ -0,0 +1,66 @@ +# Myaktion-Go Microservices + +## Prepare development environment + +1. Install `protoc` compiler + + # brew install protoc + +2. Install go plugins for protoc + + Make sure to call the following command in this directory (to make sure the protoc generators are installed + globally and not added to a `go.mod` file of an individual microservice): + + # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 + # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3 + +After that, follow the individual development instructions for each microservice. + +## Build + +Build and run the docker containers: + + # docker compose up --build + +Shut down the docker containers: + + # docker compose down + +## Use the REST API + +### Add a campaign + + # curl -H "Content-Type: application/json" -d '{"name":"Covid","organizerName":"Martin","donationMinimum":2,"targetAmount":100,"account":{"name":"Martin","bankName":"DKB","number":"123456"}}' localhost:8000/campaign + +To check if the campaign was persisted, call: + + # docker exec -it myaktion-go_mariadb_1 mysql -uroot -proot -e 'USE myaktion; SELECT * FROM campaigns' + +### Get campaign data + +To retrieve all the persisted campaign objects, send this GET request: + + # curl localhost:8000/campaigns + +For retrieving just a specific campaign, append the campaign's ID. E.g. for retrieving the +campaign with ID 1, call: + + # curl localhost:8000/campaigns/1 + +### Delete a campaign + +The following command deletes the campaign with ID 1: + + # curl -X DELETE localhost:8000/campaigns/1 + +### Update a campaign + +The following command updates the campaign with ID 1: + + # curl -X PUT -H "Content-Type: application/json" -d '{"name":"Corona","organizerName":"Marcus","donationMinimum":2,"targetAmount":100,"Account":{"Name":"Marcus","BankName":"DKB","Number":"123456"}}' localhost:8000/campaigns/1 + +### Add a donation to a campaign + +This command adds a donation to the campaign with ID 1: + + # curl -H "Content-Type: application/json" -d '{"Amount":20,"donorName":"Martin","receiptRequested":true,"status":"IN_PROCESS","account":{"name":"Martin","bankName":"DKB","number":"123456"}}' localhost:8000/campaigns/1/donation diff --git a/code/docker-compose.yml b/code/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..0e7436112104ae0a584935b8525d7f0d9d7a54ee --- /dev/null +++ b/code/docker-compose.yml @@ -0,0 +1,38 @@ +services: + banktransfer: + build: + context: ./src + dockerfile: banktransfer/Dockerfile + environment: + - KAFKA_CONNECT=kafka:9092 + - LOG_LEVEL=info # change to trace for debugging + myaktion: + build: + context: ./src + dockerfile: myaktion/Dockerfile + ports: + - "8000:8000" + environment: + - DB_CONNECT=mariadb:3306 + - BANKTRANSFER_CONNECT=banktransfer:9111 + - LOG_LEVEL=info # change to trace for debugging + mariadb: + image: mariadb:10.5 + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=myaktion + kafka: + image: bitnami/kafka:3.3.2 + container_name: kafka + environment: + - KAFKA_ENABLE_KRAFT=yes + - KAFKA_CFG_NODE_ID=1 + - KAFKA_CFG_PROCESS_ROLES=broker,controller + - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 + - KAFKA_CFG_BROKER_ID=1 + - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@kafka:9093 + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_KRAFT_CLUSTER_ID=r4zt_wrqTRuT7W2NJsB_GA diff --git a/code/go.work b/code/go.work new file mode 100644 index 0000000000000000000000000000000000000000..61b18bdf7cda63b330bab62ce325cf37dc85dc2a --- /dev/null +++ b/code/go.work @@ -0,0 +1,4 @@ +go 1.20 + +use ./src/myaktion +use ./src/banktransfer \ No newline at end of file diff --git a/code/go.work.sum b/code/go.work.sum new file mode 100644 index 0000000000000000000000000000000000000000..5733d480e877f618467fceee60aa31203f6fc033 --- /dev/null +++ b/code/go.work.sum @@ -0,0 +1,323 @@ +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.6.0 h1:x0cEHro/JFPd7eS4BlEWNTMecIj2HdXjOVB5BtvwER0= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.7.0 h1:MG60JgnEoawHJrbWw0jGdv6HLNSf6gQvYRiXpuzqgEA= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.37.0 h1:zTw+suCVchgZyO+k847wjzdVjWmrAuehxdvcZvJwfGg= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.19.0 h1:LqAo3tAh2FU9+w/r7vc3hBjU23Kv7GhO/PDIW7kIYgM= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.5.0 h1:ZI9mVO7x3E9RK/BURm2p1aw9YTBSCQe3klmyP1WxWEg= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.5.0 h1:sWOmgDyAsi1AZ48XRHcATC0tsi9SkPT7DA/+VCfkaeA= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.6.0 h1:E43RdhhCxdlV+I161gUY2rI4eOaMzHTA5kNkvRsFXvc= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.6.0 h1:B9CdHFZTFjVti89tmyXXrO+7vSNo2jvZuHG8zD5trdQ= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.7.1 h1:aBGDKmRIaRRoWJ2tAoN0oVSHoWLhtO9aj/NvUyP4aYs= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.7.1 h1:ugckkFh4XkHJMPhTIx0CyvdoBxmOpMe8rNs4Ok8GAag= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.13.0 h1:o1Q80vqEB6Qp8WLEH3b8FBLNUCrGQ4k5RFj0sn/sgO8= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.13.0 h1:YAsssO08BqZ6mncbb6FPlj9h6ACS7bJQUOlzciSfbNk= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.10.0 h1:VLGnVFta+N4WM+ASHbhc14ZOItOabDLH1MSoDv+Xuag= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.12.0 h1:50VugllC+U4IGl3tDNcZaWvApHBTrn/TvyHDJ0wM+Uw= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.5.0 h1:2AipdYXL0VxMboelTTw8c1UJ7gYu35LZYUbuRv9Q28s= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.7.0 h1:YbMt0E6BtqeD5FvSv1d56jbVsWEzlGm55lYte+M6Mzs= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.5.0 h1:UkY2BTZkEUAVrgqnSdOJ4p3y9ZRBPEe1LkjgC8Bj/Pc= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.50.0 h1:RscMV6LbnAmhAzD893Lv9nXXy2WCaJmbxYPWDLbGqNQ= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.13.0 h1:JYj28UYF5w6VBAh0gQYlgHJ/OD1oA+JgW29YZQU+UHM= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.5.0 h1:d3pMDBCCNivxt5a4eaV7FwL7cSH0H7RrEnFrTb1QKWs= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.6.0 h1:5C5UWeSt8Jkgp7OWn2rCkLmYurar/vIWIoSQ2+LaTOc= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.12.0 h1:GpcQY5UJKeOekYgsX3QXbzzAc/kRGtBq43fTmyKe6Uw= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.9.0 h1:GHQCjV4WlPPVU/j3Rlpc8vNIDwThhd1U9qSY/NPZdko= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.5.0 h1:E7v4TpDGUyEm1C/4KIrpVSOCTm0P6vWdHT0I4mostRA= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.10.0 h1:uK5k6abf4yligFgYFnG0ni8msai/dSv6mDmiBulU0hU= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.6.0 h1:jXIpfcH/VYSE1SYcPzO0n1VVb+sAamiLOgCw45JbOQk= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.15.0 h1:NKlY/wCDapfVZlbVVaeuu2UZZED5Dy1z4Zx1KhEzm8c= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.9.0 h1:EQ4FFxNaEAg8PqQCO7bVQfWz9NVwZCUKaM1b3ycfx3U= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.13.0 h1:4H5IJiyUE0X6ShQBqgFFZvGGcrwGVndTwUSLP4c52gw= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.8.0 h1:eYyD9o/8Nm6EttsKZaEGD84xC17bNgSKCu0ZxwqUbpg= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.7.0 h1:Dyk+fufup1FR6cbHjFpMuP4SfPiF3LI3JtoIIALoq48= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.6.0 h1:sZjRnS3TWkGsu1LjYPFD/fHeMLZNXDK6PDHi2s2s/bk= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.7.0 h1:ch4qA2yvddGRUrlfwrNJCr79qLqhS9QBwofPHfFlDIk= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.6.0 h1:RvoZ5T7gySwm1CHzAw7yY1QwwqaGswunmqEssPxU/AM= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.7.0 h1:yFzi/YU4YAdjyo7pXkBE2FeHbgz5OQQBVDdbErEHmVQ= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.11.0 h1:iF6I/HaLs3Ado8uRKMvZRvF/ZLkWaWE9i8AiHzbC774= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.7.0 h1:BBCBTnWMDwwEzQQmipUXxATa7Cm7CA/gKjKcR2w35T0= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.8.0 h1:otshdKEbmsi1ELYeCKNYppwV0UH5xD05drSdBm7ouTk= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.32.0 h1:uVlKKzp6G/VtSW0E7IH1Y5o0H48/UOCmqksG2riYCwQ= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.9.0 h1:1JoJqezlgu6NWCroBxr4rOZnwNFILXr4cB9dMaSKO4A= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.18.0 h1:KM3Xh0QQyyEdC8Gs2vhZfU+rt6OCPF0dwVwxKgLmWfI= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.8.0 h1:2ti/o9tlWL4N+wIuWUNH+LbfgpwxPr8J1sv9RHA4bYQ= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v1.0.0 h1:O0YVE5v+O0Q/ODXYsQHmHb+sYM8KNjGZw2pjX2Ws41c= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0 h1:kj1XEWMu8P0qlLhm3FwcaFsUvXChV/OraZwA70trRR0= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.5.0 h1:gIzEhCoOT7bi+6QZqZIzX1Erj4SswMPIteNvYVlu+pM= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.11.0 h1:fsJmNeqvqtk74FsaVDU6cH79lyZNCYP8Rrv7EhaB/PU= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.6.0 h1:ckTEXN5towyTMu4q0uQ1Mde/JwTHur0gXs8oaIZnKfw= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.13.0 h1:pPDqtsXG2g9HeOQLoquLbmvmb82Y4Ezdo1GXuotFoWg= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.9.0 h1:7vEhFnZmd931Mo7sZ6pJy7uQPDxF7m7v8xtBheG08tc= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.4.0 h1:za3QZvw6ujR0uyqkhomKKKNoXDyqYGPJies3voUK8DA= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.7.0 h1:gXYKciHS/Lgq0GJ5Kc9SzPA35NGc3yqu6SkjonpEr2Q= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.12.0 h1:TqCSPsEBQ6oZSJgEYZ3XT8x2gUadbvfwI32YB0kuHCs= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.5.0 h1:8I84Q4vl02rJRsFiinBxl7WCozfdLlUVBQuSrqr9Wtk= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/gsuiteaddons v1.5.0 h1:1mvhXqJzV0Vg5Fa95QwckljODJJfDFXV4pn+iL50zzA= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iap v1.7.1 h1:PxVHFuMxmSZyfntKXHXhd8bo82WJ+LcATenq7HLdVnU= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.3.0 h1:fodnCDtOXuMmS8LTC2y3h8t24U8F3eKWfhi+3LY6Qf0= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.6.0 h1:39W5BFSarRNZfVG0eXI5LYux+OVQT8GkgpHCnrZL2vM= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.10.1 h1:7hm1bRqGCA1GBRQUrp831TwJ9TWhP+tvLuP497CQS2g= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.9.0 h1:7Ulo2mDk9huBoBi8zCE3ONOoBrL6UXfAI71CLQ9GEIM= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.8.0 h1:uWrMjWTsGjLZpCTWEAzYvyXj+7fhiZST45u9AgasasI= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.5.0 h1:ZRQ4k21/jAhrHBVKl/AY7SjgzeJwG1iZa+mJ82P+VNg= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.7.0 h1:mv9YaczD4oZBZkM5XJl6fXQ984IkJNHPwkc8MUsdkBo= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.7.0 h1:anPxH+/WWt8Yc3EdoEJhPMBRF7EhIdz426A+tuoA0OU= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.9.0 h1:8/VEmWCpnETCrBwS3z4MhT+tIdKgR1Z4Tr2tvYH32rg= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.10.0 h1:QCFhZVe2289KDBQ7WxaHV2rAmPrmRAdLC6gbjUd3HPo= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.13.0 h1:2qsrgXGVoRXpP7otZ14eE1I568zAa92sJSDPyOJvwjM= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.11.0 h1:ZD6b4Pk1jEtp/cx9nx0ZYcL3BKqDa+KixNDZ6Bjs1B8= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.6.0 h1:8KWEUNGcpSX9WwZXq7FtciuNGPdPdPN/ruDm769yAEM= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.8.0 h1:sOc42Ig1K2LiKlzG71GUVloeSJ0J3mffEBYmvu+P0eo= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.8.0 h1:Kg2K3K7CbSXYJHZ1aGQpf1xi5x2GUvQWf2sFVuiZh8M= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.3.1 h1:dj8O4VOJRB4CUwZXdmwNViH1OtI0WtWL867/lnYH248= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.6.0 h1:Vw+CEXo8M/FZ1rb4EjcLv0gJqqw89b7+g+C/EmniTb8= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.10.0 h1:XDriMWug7sd0kYT1QKofRpRHzjad0bK8Q8uA9q+XrU4= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.11.0 h1:PkSQx4OHit5xz2bNyr11KGcaFccL5oqglFPdTboyqwQ= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.9.0 h1:whP7vhpmc+ufZa90eVpkfbgzJRK/Xomjz+XCD4aGwWw= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.7.0 h1:l6tDkT7qAEV49MNEJkEJTB6vOO/onbSOcNtAT09HPuA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.6.0 h1:yKAGC4p9O61ttZUswaq9GAn1SZnEzTd0vUYXD7ZBT7Y= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.8.0 h1:EPEJ1DpEGXLDnmc7mnCAqFmkwUJbIsaLAiLHVOkkwtc= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.30.0 h1:vCge8m7aUKBJYOgrZp7EsNDf6QMd2CAlXZqWTn3yq6s= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.7.0 h1:cb9fsrtpINtETHiJ3ECeaVzrfIVhcGjhhJEjybHXHao= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0 h1:6iOCujSNJ0YS7oNymI64hXsjGq60T4FK1zdLugxbzvU= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.7.0 h1:VibRFCwWXrFebEWKHfZAt2kta6pS7Tlimsnms0fjv7k= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.9.0 h1:ZnFRY5R6zOVk2IDS1Jbv5Bw+DExCI5rFumsTnMXiu/A= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.11.0 h1:JoAd3SkeDt3rLFAAxEvw6wV4t+8y4ZzfZcZmddqphQ8= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.7.0 h1:NRM0p+RJkaQF9Ee9JMnUV9BQ2QBIOq/v8M+Pbv/wmCs= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.5.0 h1:8Dua37kQt27CCWHm4h/Q1XqCF6ByD7Ouu49xg95qJzI= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.12.0 h1:1Dda2OpFNzIb4qWgFZjYlpP7sxX3aLeypKG6A3H4Yys= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.9.0 h1:ydJQo+k+MShYnBfhaRHSZYeD/SQKZzZLAROyfpeD9zw= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.9.0 h1:NpQAHtx3sulByTLe2dMwWmah8PWgeoieFPpJpArwFV0= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.10.0 h1:pu03bha7ukxF8otyPKTFdDz+rr9sE3YauS5PliDXK60= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.13.0 h1:PYvDxopRQBfYAXKAuDpFCKBvDOWPWzp9k/H5nB3ud3o= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.19.0 h1:AF3c2s3awNTMoBtMX3oCUoOMmGlYxGOeuXSYHNBkf14= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.11.1 h1:d0uV7Qegtfaa7Z2ClDzr9HJmnbJW7jn0WhZ7wOX6hLE= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.9.0 h1:SJwk0XX2e26o25ObYUORXx6torSFiYgsGkWSkZgkoSU= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.8.0 h1:fopAQI/IAzlxnVeiKn/8WiV6zKndjFkvi+gzu+NjywY= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.6.0 h1:rXyq+0+RSIm3HFypctp7WoXxIA563rn206CfMWdqXX4= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.6.0 h1:wT0Uw7ib7+AgZST9eCDygwTJn4+bHMDtZo5fh7kGWDU= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.45.0 h1:7VdjZ8zj4sHbDw55atp5dfY6kn1j9sam9DRNpPQhqR4= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.15.0 h1:JEVoWGNnTF128kNty7T4aG4eqv2z86yiMJPT9Zjp+iw= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storagetransfer v1.8.0 h1:5T+PM+3ECU3EY2y9Brv0Sf3oka8pKmsCfpQ07+91G9o= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.5.0 h1:nI9sVZPjMKiO2q3Uu0KhTDVov3Xrlpt63fghP9XjyEM= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.6.0 h1:H4g1ULStsbVtalbZGktyzXzw6jP26RjVGYx9RaYjBzc= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.5.0 h1:/34T6CbSi+kTv5E19Q9zbU/ix8IviInZpzwz3rsFE+A= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.9.0 h1:olxC0QHC59zgJVALtgqfD9tGk0lfeCP5/AGXL3Px/no= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.7.0 h1:GvLP4oQ4uPdChBmBaUSa/SaZxCdyWELtlAaKzpHsXdA= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.15.0 h1:upIbnGI0ZgACm58HPjAeBMleW3sl5cT84AbYQ8PWOgM= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.10.0 h1:Uh5BdoET8XXqXX2uXIahGb+wTKbLkGH7s4GXR58RrG8= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision/v2 v2.7.0 h1:8C8RXUJoflCI4yVdqhTy9tRyygSHmp60aP363z23HKg= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.6.0 h1:Azs5WKtfOC8pxvkyrDvt7J0/4DYBch0cVbuFfCCFt5k= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.3.0 h1:b0NBu7S294l0gmtrT0nOJneMYgZapr5x9tVWvgDoVEM= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.6.0 h1:FOe6CuiQD3BhHJWt7E8QlbBcaIzVRddupwJlp7eqmn4= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.8.0 h1:IY+L2+UwxcVm2zayMAtBhZleecdIFLiC+QJMzgb0kT0= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.5.0 h1:AHC1xmaNMOZtNqxI9Rmm87IJEyPaRkOxeI0gpAacXGk= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.10.0 h1:FfGp9w0cYnaKZJhUOMqCOJCYT/WlvYBfTQhFWV3sRKI= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0 h1:wpFFOoomK3389ue2lAb0Boag6XPht5QYpipxmSNL4d8= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b h1:ACGZRIr7HsgBKHsueQ1yM4WaVaXh21ynwqsF8M8tXhA= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/envoyproxy/go-control-plane v0.10.3 h1:xdCVXxEe0Y3FQith+0cj2irwZudqGYvecuLB1HtdexY= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= diff --git a/code/src/banktransfer/Dockerfile b/code/src/banktransfer/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6df3312b7c500e60e5f09f7c4ac913cb18312a71 --- /dev/null +++ b/code/src/banktransfer/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.20-buster + +RUN apt update && apt install -y protobuf-compiler +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3 + +WORKDIR /go/src/app +COPY ./banktransfer . + +RUN go mod download +RUN go generate ./... +RUN go install + +COPY ./wait-for-it.sh . +RUN chmod +x ./wait-for-it.sh ./docker-entrypoint.sh + +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["banktransfer"] + +EXPOSE 9111 diff --git a/code/src/banktransfer/README.md b/code/src/banktransfer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3799657135c61e237e52aa752d61ed18d3b70486 --- /dev/null +++ b/code/src/banktransfer/README.md @@ -0,0 +1,10 @@ +## Development + +### Ensure dependencies in IDE + +The following step is only necessary for resolving dependencies in the +IDE and for local testing. The docker build will work nevertheless. + +1. Run `go generate` in this folder: + + # go generate ./... \ No newline at end of file diff --git a/code/src/banktransfer/docker-entrypoint.sh b/code/src/banktransfer/docker-entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..6834ba760a0cdc0aefe1408d7957471f7da7fc18 --- /dev/null +++ b/code/src/banktransfer/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# Wait for kafka +if [ -n "$KAFKA_CONNECT" ]; then + /go/src/app/wait-for-it.sh "$KAFKA_CONNECT" -t 20 +fi + +# Run the main container command. +exec "$@" diff --git a/code/src/banktransfer/go.mod b/code/src/banktransfer/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..bac3aab5728c5f094595bda10408ce31dac5c2ec --- /dev/null +++ b/code/src/banktransfer/go.mod @@ -0,0 +1,17 @@ +module github.com/turngeek/myaktion-go-2023/src/banktransfer + +go 1.20 + +require ( + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect + github.com/segmentio/kafka-go v0.4.40 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect +) diff --git a/code/src/banktransfer/go.sum b/code/src/banktransfer/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..1ff36fc9c36d7552e40645ce703af3c7aa0f53a8 --- /dev/null +++ b/code/src/banktransfer/go.sum @@ -0,0 +1,66 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/kafka-go v0.4.40 h1:sszW7c0/uyv7+VcTW5trx2ZC7kMWDTxuR/6Zn8U1bm8= +github.com/segmentio/kafka-go v0.4.40/go.mod h1:naFEZc5MQKdeL3W6NkZIAn48Y6AazqjRFDhnXeg3h94= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/src/banktransfer/grpc/banktransfer/.gitignore b/code/src/banktransfer/grpc/banktransfer/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9b0b440dc050b16d9b21f468e5e73687e84766b1 --- /dev/null +++ b/code/src/banktransfer/grpc/banktransfer/.gitignore @@ -0,0 +1 @@ +*.pb.go \ No newline at end of file diff --git a/code/src/banktransfer/grpc/banktransfer/banktransfer.proto b/code/src/banktransfer/grpc/banktransfer/banktransfer.proto new file mode 100644 index 0000000000000000000000000000000000000000..eb2e79b40a80ddb25b17f5a513966a1441818fc6 --- /dev/null +++ b/code/src/banktransfer/grpc/banktransfer/banktransfer.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package banktransfer; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/turngeek/myaktion-go-2023/src/banktransfer/grpc/banktransfer"; + +service BankTransfer { + rpc TransferMoney (Transaction) returns (google.protobuf.Empty) {} + rpc ProcessTransactions (stream ProcessingResponse) returns (stream Transaction) {} +} + +message Account { + string name = 1; + string bank_name = 2; + string number = 3; +} + +message Transaction { + string id = 1 ; + int32 donation_id = 2; + float amount = 3; + string reference = 4; + Account from_account = 5; + Account to_account = 6; +} + +message ProcessingResponse { + string id = 1; +} \ No newline at end of file diff --git a/code/src/banktransfer/grpc/gen.go b/code/src/banktransfer/grpc/gen.go new file mode 100644 index 0000000000000000000000000000000000000000..39599f9ab19fc34a3fc8884cbe614f12d9f5f0b6 --- /dev/null +++ b/code/src/banktransfer/grpc/gen.go @@ -0,0 +1,3 @@ +package grpc + +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative banktransfer/banktransfer.proto diff --git a/code/src/banktransfer/kafka/reader.go b/code/src/banktransfer/kafka/reader.go new file mode 100644 index 0000000000000000000000000000000000000000..72104b0875255410e74331396fcac4a935d2f33f --- /dev/null +++ b/code/src/banktransfer/kafka/reader.go @@ -0,0 +1,60 @@ +package kafka + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/segmentio/kafka-go" + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/grpc/banktransfer" +) + +type TransactionReader interface { + Close() error + Read(ctx context.Context, handler ReadHandler) error +} + +type ReadHandler func(*banktransfer.Transaction) error + +type reader struct { + reader *kafka.Reader +} + +func NewTransactionReader() *reader { + return &reader{reader: kafka.NewReader(kafka.ReaderConfig{ + Brokers: []string{connect}, + GroupID: groupID, + Topic: Topic, + MinBytes: 10e2, // 1KB + MaxBytes: 10e5, // 1MB + })} +} + +func (r *reader) Read(ctx context.Context, handler ReadHandler) error { + msg, err := r.reader.FetchMessage(ctx) + if err != nil { + return fmt.Errorf("failed to fetch message from kafka server: %w", err) + } + entry := log.WithField("message", msg) + entry.Trace("received message from Kafka server") + var transaction banktransfer.Transaction + if err := json.Unmarshal(msg.Value, &transaction); err != nil { + return fmt.Errorf("failed to unmarshal transaction: %w", err) + } + err = handler(&transaction) + if err != nil { + return fmt.Errorf("failed to call read handler: %w", err) + } + if err := r.reader.CommitMessages(ctx, msg); err != nil { + return fmt.Errorf("failed to commit message: %w", err) + } + return nil +} + +func (r *reader) Close() error { + if err := r.reader.Close(); err != nil { + return fmt.Errorf("could not close Kafka subscription: %w", err) + } + return nil +} diff --git a/code/src/banktransfer/kafka/topic.go b/code/src/banktransfer/kafka/topic.go new file mode 100644 index 0000000000000000000000000000000000000000..b8fc741855235f6e7bf4f84174ce03772c4e5add --- /dev/null +++ b/code/src/banktransfer/kafka/topic.go @@ -0,0 +1,58 @@ +package kafka + +import ( + "net" + "strconv" + + "github.com/segmentio/kafka-go" + log "github.com/sirupsen/logrus" +) + +const ( + Topic = "banktransfer" +) + +func EnsureTransactionTopic() { + if err := ensureTopic(Topic, 10); err != nil { + panic(err.Error()) + } +} + +func ensureTopic(topic string, numPartitions int) error { + conn, err := kafka.Dial("tcp", connect) + if err != nil { + return err + } + defer conn.Close() + + controller, err := conn.Controller() + if err != nil { + return err + } + var leaderConn *kafka.Conn + leaderConn, err = kafka.Dial("tcp", net.JoinHostPort(controller.Host, strconv.Itoa(controller.Port))) + if err != nil { + return err + } + defer leaderConn.Close() + + topicConfigs := []kafka.TopicConfig{ + { + Topic: topic, + NumPartitions: numPartitions, + ReplicationFactor: 1, + }, + } + + err = leaderConn.CreateTopics(topicConfigs...) + if err != nil { + return err + } + + log.Infof( + "Topic '%s' with %d partitions successfully created\n", + topic, + numPartitions, + ) + return nil +} diff --git a/code/src/banktransfer/kafka/writer.go b/code/src/banktransfer/kafka/writer.go new file mode 100644 index 0000000000000000000000000000000000000000..d424ec393684cde22500501cdc2ecd218bfb467f --- /dev/null +++ b/code/src/banktransfer/kafka/writer.go @@ -0,0 +1,59 @@ +package kafka + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/segmentio/kafka-go" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/grpc/banktransfer" +) + +const ( + groupID = "banktransfers-watchers" +) + +var connect = os.Getenv("KAFKA_CONNECT") + +type TransactionWriter interface { + Close() error + Write(transaction *banktransfer.Transaction) error +} + +type writer struct { + writer *kafka.Writer + writeContext context.Context + writeCancelFunc context.CancelFunc +} + +func NewTransactionWriter() *writer { + writeContext, writeCancelFunc := context.WithCancel(context.Background()) + return &writer{&kafka.Writer{ + Addr: kafka.TCP(connect), + Topic: Topic, + Balancer: &kafka.LeastBytes{}, + }, writeContext, writeCancelFunc} +} + +func (w *writer) Write(transaction *banktransfer.Transaction) error { + bytes, err := json.Marshal(&transaction) + if err != nil { + return fmt.Errorf("error marshalling transaction: %w", err) + } + if err := w.writer.WriteMessages(w.writeContext, kafka.Message{ + Key: []byte(transaction.Id), + Value: bytes, + }); err != nil { + return fmt.Errorf("can't write message to kafka server: %w", err) + } + return nil +} + +func (w *writer) Close() error { + w.writeCancelFunc() + if err := w.writer.Close(); err != nil { + return fmt.Errorf("couldn't close connection to Kafka server: %w", err) + } + return nil +} diff --git a/code/src/banktransfer/main.go b/code/src/banktransfer/main.go new file mode 100644 index 0000000000000000000000000000000000000000..703a2124dc507ebcd4320910f381ef79eb4fb1f7 --- /dev/null +++ b/code/src/banktransfer/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "net" + "os" + + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/grpc/banktransfer" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/kafka" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/service" + "google.golang.org/grpc" +) + +func init() { + defer kafka.EnsureTransactionTopic() + // init logger + log.SetFormatter(&log.TextFormatter{}) + log.SetReportCaller(true) + level, err := log.ParseLevel(os.Getenv("LOG_LEVEL")) + if err != nil { + log.Info("Log level not specified, using default log level: INFO") + log.SetLevel(log.InfoLevel) + return + } + log.SetLevel(level) +} + +var grpcPort = 9111 + +func main() { + log.Info("Starting Banktransfer service") + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort)) + if err != nil { + log.Fatalf("failed to listen on grpc port %d: %v", grpcPort, err) + } + grpcServer := grpc.NewServer() + transferService := service.NewBankTransferService() + transferService.Start() + defer transferService.Stop() + banktransfer.RegisterBankTransferServer(grpcServer, transferService) + if err := grpcServer.Serve(lis); err != nil { + log.Fatalf("failed to serve gRPC server over port %d: %v", grpcPort, err) + } +} diff --git a/code/src/banktransfer/service/banktransfer.go b/code/src/banktransfer/service/banktransfer.go new file mode 100644 index 0000000000000000000000000000000000000000..63f9e3a718ebae8b7bcc0f5b2031e55a3c9de62a --- /dev/null +++ b/code/src/banktransfer/service/banktransfer.go @@ -0,0 +1,94 @@ +package service + +import ( + "context" + "errors" + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/grpc/banktransfer" + "github.com/turngeek/myaktion-go-2023/src/banktransfer/kafka" + "google.golang.org/protobuf/types/known/emptypb" +) + +type BankTransferService struct { + banktransfer.BankTransferServer + keyGenerator *KeyGenerator + transactionWriter kafka.TransactionWriter +} + +func NewBankTransferService() *BankTransferService { + return &BankTransferService{keyGenerator: NewKeyGenerator()} +} + +func (s *BankTransferService) TransferMoney(_ context.Context, transaction *banktransfer.Transaction) (*emptypb.Empty, error) { + entry := log.WithField("transaction", transaction) + entry.Info("Received transaction") + s.processTransaction(transaction) + return &emptypb.Empty{}, nil +} + +func (s *BankTransferService) ProcessTransactions(stream banktransfer.BankTransfer_ProcessTransactionsServer) error { + return func() error { + r := kafka.NewTransactionReader() + for { + err := r.Read(stream.Context(), func(transaction *banktransfer.Transaction) error { + id := transaction.Id + entry := log.WithField("transaction", transaction) + entry.Info("Sending transaction to the client") + if err := stream.Send(transaction); err != nil { + return fmt.Errorf("error sending transaction: %w", err) + } + entry.Info("Transaction sent. Waiting for processing response") + response, err := stream.Recv() + if err != nil { + return fmt.Errorf("error receiving transaction response: %w", err) + } + if response.Id != id { + // NOTE: this is just a guard and not happening as transaction is local per connection + return errors.New("received processing response of a different transaction") + } + entry.Info("Processing response received") + return nil + }) + if err != nil { + log.WithError(err).Error("Failed to read transaction") + break + } + } + if err := r.Close(); err != nil { + log.WithError(err).Error("Failed to close transaction reader") + return nil + } + log.Info("Transaction reader successfully closed") + return nil + }() +} + +func (s *BankTransferService) Start() { + log.Info("Starting banktransfer service") + s.transactionWriter = kafka.NewTransactionWriter() + log.Info("Successfully created transaction writer") +} + +func (s *BankTransferService) Stop() { + log.Info("Stopping banktransfer service") + s.transactionWriter.Close() + log.Info("Successfully closed connection to transaction writer") +} + +func (s *BankTransferService) processTransaction(transaction *banktransfer.Transaction) { + entry := log.WithField("transaction", transaction) + // NOTE: we need to copy the transaction as we are using a own go routine to process the transaction + // this produces a `go vet` warning as gRPC messages contain a mutex that is also copied but not used. + // therefore this warning can be ignored + go func(transaction banktransfer.Transaction) { + entry.Info("Start processing transaction") + transaction.Id = s.keyGenerator.getUniqueId() + if err := s.transactionWriter.Write(&transaction); err != nil { + entry.WithError(err).Error("Can't write transaction to transaction writer") + return + } + entry.Info("Transaction forwarded to transaction writer. Processing transaction finished") + }(*transaction) +} diff --git a/code/src/banktransfer/service/keygenerator.go b/code/src/banktransfer/service/keygenerator.go new file mode 100644 index 0000000000000000000000000000000000000000..45c5e42a3df17c0f3d586b8358e42d1a28259d52 --- /dev/null +++ b/code/src/banktransfer/service/keygenerator.go @@ -0,0 +1,27 @@ +package service + +import ( + "fmt" + "os" + "sync/atomic" + + log "github.com/sirupsen/logrus" +) + +// KeyGenerator generates keys in the pattern {hostname}-{counter} +type KeyGenerator struct { + counter int32 + hostName string +} + +func NewKeyGenerator() *KeyGenerator { + hostName, err := os.Hostname() + if err != nil { + log.Fatal(err) + } + return &KeyGenerator{counter: 0, hostName: hostName} +} + +func (g *KeyGenerator) getUniqueId() string { + return fmt.Sprintf("%s-%d", g.hostName, atomic.AddInt32(&g.counter, 1)) +} diff --git a/code/src/myaktion/Dockerfile b/code/src/myaktion/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..ab59bfc4384b90a3b0cc4b48e1a637c55b27cf84 --- /dev/null +++ b/code/src/myaktion/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:1.20-buster + +# non-go modules dependencies +RUN apt update && apt install -y protobuf-compiler +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3 + +# copy code and protobuf +WORKDIR /go/src/app +COPY ./myaktion . +COPY ./banktransfer/grpc/banktransfer/banktransfer.proto ./client/banktransfer/ +COPY ./wait-for-it.sh . + +RUN go mod download +RUN go generate ./... +RUN go install + +RUN chmod +x ./wait-for-it.sh ./docker-entrypoint.sh +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["myaktion"] +EXPOSE 8000 \ No newline at end of file diff --git a/code/src/myaktion/README.md b/code/src/myaktion/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7db8d4e7440771426ed56ba8f80f909da8af8dd8 --- /dev/null +++ b/code/src/myaktion/README.md @@ -0,0 +1,16 @@ +## Development + +### Ensure dependencies in IDE + +The following steps are only necessary for resolving dependencies in the +IDE and for local testing. The docker build will work nevertheless. + +1. Copy protobuf file from `banktransfer` service: + + # cp ../banktransfer/grpc/banktransfer/banktransfer.proto ./client/banktransfer/ + +2. Run `go generate` in this folder: + + # go generate ./... + + diff --git a/code/src/myaktion/client/banktransfer.go b/code/src/myaktion/client/banktransfer.go new file mode 100644 index 0000000000000000000000000000000000000000..68ce662a58723b8df83b98cefbbb3651a19881cd --- /dev/null +++ b/code/src/myaktion/client/banktransfer.go @@ -0,0 +1,27 @@ +package client + +import ( + "context" + "os" + + log "github.com/sirupsen/logrus" + + "google.golang.org/grpc" +) + +var ( + bankTransferTarget = os.Getenv("BANKTRANSFER_CONNECT") +) + +func GetBankTransferConnection(ctx context.Context) (*grpc.ClientConn, error) { + var err error + log.WithFields(log.Fields{ + "target": bankTransferTarget, + }).Infoln("Connecting to banktransfer service") + var conn *grpc.ClientConn + conn, err = grpc.DialContext(ctx, bankTransferTarget, grpc.WithInsecure(), grpc.WithBlock()) + if err != nil { + return nil, err + } + return conn, nil +} diff --git a/code/src/myaktion/client/banktransfer/.gitignore b/code/src/myaktion/client/banktransfer/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..be3b9b9b75c4f5695dd4559d95ed0524654115df --- /dev/null +++ b/code/src/myaktion/client/banktransfer/.gitignore @@ -0,0 +1,2 @@ +banktransfer.proto +*.pb.go \ No newline at end of file diff --git a/code/src/myaktion/client/gen.go b/code/src/myaktion/client/gen.go new file mode 100644 index 0000000000000000000000000000000000000000..f39823e3f5d9545a0756faa1e082b081ad34fb74 --- /dev/null +++ b/code/src/myaktion/client/gen.go @@ -0,0 +1,4 @@ +package client + +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative banktransfer/banktransfer.proto + diff --git a/code/src/myaktion/db/db.go b/code/src/myaktion/db/db.go new file mode 100644 index 0000000000000000000000000000000000000000..59d1cf0dd1abb556f07e9c638df9cc50b16cff59 --- /dev/null +++ b/code/src/myaktion/db/db.go @@ -0,0 +1,37 @@ +package db + +import ( + "errors" + "fmt" + "os" + + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/myaktion/model" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +var DB *gorm.DB + +func Init() { + err := Connect(os.Getenv("DB_CONNECT")) + if err != nil { + panic(err) + } +} + +func Connect(connect string) error { + dsn := fmt.Sprintf("root:root@tcp(%s)/myaktion?charset=utf8&parseTime=True&loc=Local", connect) + log.Info("Using database connection string: ", dsn) + var err error + DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + return errors.New("failed to connect database") + } + log.Info("Starting automatic migration") + if err := DB.Debug().AutoMigrate(&model.Campaign{}, &model.Donation{}); err != nil { + return err + } + log.Info("Finished automatic migration") + return nil +} diff --git a/code/src/myaktion/db/setuptest.go b/code/src/myaktion/db/setuptest.go new file mode 100644 index 0000000000000000000000000000000000000000..906c282299de2e17f680b82d40270452f197b710 --- /dev/null +++ b/code/src/myaktion/db/setuptest.go @@ -0,0 +1,43 @@ +package db + +import ( + "testing" + + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" +) + +func SetupTestDB(t *testing.T) func() { + pool, err := dockertest.NewPool("") + if err != nil { + t.Fatalf("Could not connect to docker: %s", err) + } + + runDockerOpt := &dockertest.RunOptions{ + Repository: "mariadb", + Tag: "10.5", + Env: []string{"MYSQL_ROOT_PASSWORD=root", "MYSQL_DATABASE=myaktion"}, + PortBindings: map[docker.Port][]docker.PortBinding{ + "3306/tcp": {{HostIP: "localhost", HostPort: "3306"}}, + }, + } + + fnConfig := func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.NeverRestart() + } + + resource, err := pool.RunWithOptions(runDockerOpt, fnConfig) + if err != nil { + t.Fatalf("Could not start test DB: %s", err) + } + + err = pool.Retry(func() error { + return Connect("localhost:3306") + }) + if err != nil { + t.Fatalf("Could not connect to test DB: %s", err) + } + + return func() { resource.Close() } +} diff --git a/code/src/myaktion/docker-entrypoint.sh b/code/src/myaktion/docker-entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..608cb58ebe38d802dedf9171ff81d20be9aecacd --- /dev/null +++ b/code/src/myaktion/docker-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# Wait for DB +if [ -n "$DB_CONNECT" ]; then + /go/src/app/wait-for-it.sh "$DB_CONNECT" -t 20 +fi + +# Run the main container command. +exec "$@" diff --git a/code/src/myaktion/go.mod b/code/src/myaktion/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..19789b7eff4535a8c1560ab4f23a258a2af9172f --- /dev/null +++ b/code/src/myaktion/go.mod @@ -0,0 +1,45 @@ +module github.com/turngeek/myaktion-go-2023/src/myaktion + +go 1.20 + +require ( + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/containerd/continuity v0.3.0 // indirect + github.com/docker/cli v23.0.3+incompatible // indirect + github.com/docker/docker v23.0.3+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/opencontainers/runc v1.1.6 // indirect + github.com/ory/dockertest/v3 v3.9.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gorm.io/driver/mysql v1.5.0 // indirect + gorm.io/gorm v1.25.0 // indirect +) diff --git a/code/src/myaktion/go.sum b/code/src/myaktion/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..4b1d52b38260914312faee3c562b9ff93fe364e5 --- /dev/null +++ b/code/src/myaktion/go.sum @@ -0,0 +1,124 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/cli v23.0.3+incompatible h1:Zcse1DuDqBdgI7OQDV8Go7b83xLgfhW1eza4HfEdxpY= +github.com/docker/cli v23.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= +github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA= +github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.0 h1:6hSAT5QcyIaty0jfnff0z0CLDjyRgZ8mlMHLqSt7uXM= +gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= diff --git a/code/src/myaktion/handler/campaign.go b/code/src/myaktion/handler/campaign.go new file mode 100644 index 0000000000000000000000000000000000000000..45768396b96417570c4714b0e36ba34f8c1e5a26 --- /dev/null +++ b/code/src/myaktion/handler/campaign.go @@ -0,0 +1,147 @@ +package handler + +import ( + "encoding/json" + "net/http" + + log "github.com/sirupsen/logrus" + + "github.com/turngeek/myaktion-go-2023/src/myaktion/model" + "github.com/turngeek/myaktion-go-2023/src/myaktion/service" +) + +func getCampaign(r *http.Request) (*model.Campaign, error) { + var campaign model.Campaign + err := json.NewDecoder(r.Body).Decode(&campaign) + if err != nil { + log.Errorf("Can't decode request body to campaign struct: %v", err) + return nil, err + } + return &campaign, nil +} + +func CreateCampaign(w http.ResponseWriter, r *http.Request) { + campaign, err := getCampaign(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + campaign.OrganizerId = r.Header.Get("Userid") + if err := service.CreateCampaign(campaign); err != nil { + log.Errorf("Error calling service CreateCampaign: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sendJson(w, campaign) +} + +func GetCampaigns(w http.ResponseWriter, r *http.Request) { + campaigns, err := service.GetCampaigns() + if err != nil { + log.Errorf("Error calling service GetCampaigns: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + userid := r.Header.Get("Userid") + var campaignsOfUser []model.Campaign + for _, campaign := range campaigns { + if campaign.OrganizerId == userid { + campaignsOfUser = append(campaignsOfUser, campaign) + } + } + sendJson(w, campaignsOfUser) +} + +func GetCampaign(w http.ResponseWriter, r *http.Request) { + id, err := getId(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + campaign, err := service.GetCampaign(id) + if err != nil { + log.Errorf("Failure retrieving campaign with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if campaign == nil { + http.Error(w, "404 campaign not found", http.StatusNotFound) + return + } + if campaign.OrganizerId != r.Header.Get("Userid") { + http.Error(w, "403 campaign belongs to a different user", http.StatusForbidden) + return + } + sendJson(w, campaign) +} + +func UpdateCampaign(w http.ResponseWriter, r *http.Request) { + id, err := getId(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + campaign, err := getCampaign(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + existingCampaign, err := service.GetCampaign(id) + if err != nil { + log.Errorf("Failure retrieving campaign with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if existingCampaign == nil { + http.Error(w, "404 campaign not found", http.StatusNotFound) + return + } + if existingCampaign.OrganizerId != r.Header.Get("Userid") { + http.Error(w, "403 campaign belongs to a different user", http.StatusForbidden) + return + } + campaign, err = service.UpdateCampaign(id, campaign) + if err != nil { + log.Errorf("Failure updating campaign with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if campaign == nil { + http.Error(w, "404 campaign not found", http.StatusNotFound) + return + } + sendJson(w, campaign) +} + +func DeleteCampaign(w http.ResponseWriter, r *http.Request) { + id, err := getId(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + existingCampaign, err := service.GetCampaign(id) + if err != nil { + log.Errorf("Failure retrieving campaign with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if existingCampaign == nil { + http.Error(w, "404 campaign not found", http.StatusNotFound) + return + } + if existingCampaign.OrganizerId != r.Header.Get("Userid") { + http.Error(w, "403 campaign belongs to a different user", http.StatusForbidden) + return + } + campaign, err := service.DeleteCampaign(id) + if err != nil { + log.Errorf("Failure deleting campaign with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if campaign == nil { + http.Error(w, "404 campaign not found", http.StatusNotFound) + return + } + sendJson(w, result{Success: "OK"}) +} diff --git a/code/src/myaktion/handler/campaign_test.go b/code/src/myaktion/handler/campaign_test.go new file mode 100644 index 0000000000000000000000000000000000000000..86cf9d11302aa62c928bd46c9a03c85fb84fa6a1 --- /dev/null +++ b/code/src/myaktion/handler/campaign_test.go @@ -0,0 +1,39 @@ +package handler_test + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/turngeek/myaktion-go-2023/src/myaktion/db" + "github.com/turngeek/myaktion-go-2023/src/myaktion/handler" + "github.com/turngeek/myaktion-go-2023/src/myaktion/model" +) + +func TestCreateCampaign(t *testing.T) { + cleanUpDB := db.SetupTestDB(t) + defer cleanUpDB() + rr := httptest.NewRecorder() + jsonData := `{"name":"Covid","organizerName":"Martin","donationMinimum":2,"targetAmount":100,"account":{"name":"Martin","bankName":"DKB","number":"123456"}}` + req := httptest.NewRequest(http.MethodPost, "/dummy-url", bytes.NewBufferString(jsonData)) + req.Header.Set("Content-Type", "application/json") + + handler := http.HandlerFunc(handler.CreateCampaign) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + } + + var campaign model.Campaign + err := json.NewDecoder(rr.Body).Decode(&campaign) + if err != nil { + t.Errorf("handler returned unexpected body: %v", err) + return + } + if campaign.ID != 1 { + t.Errorf("handler returned unexpected ID: got %v want %v", campaign.ID, 1) + } +} diff --git a/code/src/myaktion/handler/donation.go b/code/src/myaktion/handler/donation.go new file mode 100644 index 0000000000000000000000000000000000000000..cc2310dce5998650002584687cba8498a1b4f8c5 --- /dev/null +++ b/code/src/myaktion/handler/donation.go @@ -0,0 +1,40 @@ +package handler + +import ( + "encoding/json" + "net/http" + + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/myaktion/model" + "github.com/turngeek/myaktion-go-2023/src/myaktion/service" +) + +func AddDonation(w http.ResponseWriter, r *http.Request) { + id, err := getId(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + donation, err := getDonation(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + err = service.AddDonation(id, donation) + if err != nil { + log.Errorf("Failure adding donation to campaign with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sendJson(w, donation) +} + +func getDonation(r *http.Request) (*model.Donation, error) { + var donation model.Donation + err := json.NewDecoder(r.Body).Decode(&donation) + if err != nil { + log.Errorf("Can't serialize request body to donation struct: %v", err) + return nil, err + } + return &donation, nil +} diff --git a/code/src/myaktion/handler/health.go b/code/src/myaktion/handler/health.go new file mode 100644 index 0000000000000000000000000000000000000000..98999a3d02100fb47924f6129ef98111224c460f --- /dev/null +++ b/code/src/myaktion/handler/health.go @@ -0,0 +1,18 @@ +package handler + +import ( + "io" + "net/http" + + log "github.com/sirupsen/logrus" +) + +func Health(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, err := io.WriteString(w, `{"alive": true}`) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + log.Info("API Health is OK") +} diff --git a/code/src/myaktion/handler/health_test.go b/code/src/myaktion/handler/health_test.go new file mode 100644 index 0000000000000000000000000000000000000000..adcc118103ce9139312495bb22c023c63792bf4b --- /dev/null +++ b/code/src/myaktion/handler/health_test.go @@ -0,0 +1,25 @@ +package handler_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/turngeek/myaktion-go-2023/src/myaktion/handler" +) + +func TestHealth(t *testing.T) { + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/health", nil) + handler := http.HandlerFunc(handler.Health) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + } + + expected := `{"alive": true}` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected) + } +} diff --git a/code/src/myaktion/handler/utils.go b/code/src/myaktion/handler/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..68384beb9089ba0dbdee2b67d8a10ab7d1965f92 --- /dev/null +++ b/code/src/myaktion/handler/utils.go @@ -0,0 +1,32 @@ +package handler + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" +) + +type result struct { + Success string `json:"success"` +} + +func sendJson(w http.ResponseWriter, value interface{}) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(value); err != nil { + log.Errorf("Failure encoding value to JSON: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func getId(r *http.Request) (uint, error) { + vars := mux.Vars(r) + id, err := strconv.ParseUint(vars["id"], 10, 0) + if err != nil { + log.Errorf("Can't parse ID from request: %v", err) + return 0, err + } + return uint(id), nil +} diff --git a/code/src/myaktion/main.go b/code/src/myaktion/main.go new file mode 100644 index 0000000000000000000000000000000000000000..0fe73ea815bc25a99cf79dd7e5b705af07391e0d --- /dev/null +++ b/code/src/myaktion/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "net/http" + "os" + + log "github.com/sirupsen/logrus" + + "github.com/gorilla/mux" + "github.com/turngeek/myaktion-go-2023/src/myaktion/db" + "github.com/turngeek/myaktion-go-2023/src/myaktion/handler" +) + +func init() { + // ensure that logger is initialized before connecting to DB + defer db.Init() + // init logger + log.SetFormatter(&log.TextFormatter{}) + log.SetReportCaller(true) + level, err := log.ParseLevel(os.Getenv("LOG_LEVEL")) + if err != nil { + log.Info("Log level not specified, using default log level: INFO") + log.SetLevel(log.InfoLevel) + return + } + log.SetLevel(level) +} + +func main() { + log.Println("Starting My-Aktion API server") + router := mux.NewRouter() + router.HandleFunc("/health", handler.Health).Methods("GET") + router.HandleFunc("/campaign", handler.CreateCampaign).Methods("POST") + router.HandleFunc("/campaigns", handler.GetCampaigns).Methods("GET") + router.HandleFunc("/campaigns/{id}", handler.GetCampaign).Methods("GET") + router.HandleFunc("/campaigns/{id}", handler.UpdateCampaign).Methods("PUT") + router.HandleFunc("/campaigns/{id}", handler.DeleteCampaign).Methods("DELETE") + router.HandleFunc("/campaigns/{id}/donation", handler.AddDonation).Methods("POST") + go monitortransactions() + log.Fatal(http.ListenAndServe(":8000", router)) +} diff --git a/code/src/myaktion/model/account.go b/code/src/myaktion/model/account.go new file mode 100644 index 0000000000000000000000000000000000000000..0c3689b827f13cf7314ddd64e318103018285f2c --- /dev/null +++ b/code/src/myaktion/model/account.go @@ -0,0 +1,7 @@ +package model + +type Account struct { + Name string `gorm:"notNull;size:60"` + BankName string `gorm:"notNull;size:40"` + Number string `gorm:"notNull;size:20"` +} diff --git a/code/src/myaktion/model/campaign.go b/code/src/myaktion/model/campaign.go new file mode 100644 index 0000000000000000000000000000000000000000..811c8eb6656cebf7854cf7a869eb546e05b9343e --- /dev/null +++ b/code/src/myaktion/model/campaign.go @@ -0,0 +1,25 @@ +package model + +import "gorm.io/gorm" + +type Campaign struct { + gorm.Model + Name string `gorm:"notNull;size:30"` + OrganizerName string `gorm:"notNull"` + OrganizerId string `gorm:"notNull"` + TargetAmount float64 `gorm:"notNull;check:target_amount >= 10.0"` + DonationMinimum float64 `gorm:"notNull;check:donation_minimum >= 1.0"` + Donations []Donation `gorm:"foreignKey:CampaignID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` + AmountDonatedSoFar float64 `gorm:"-"` + Account Account `gorm:"embedded;embeddedPrefix:account_"` +} + +func (c *Campaign) AfterFind(tx *gorm.DB) (err error) { + var sum float64 + result := tx.Model(&Donation{}).Select("ifnull(sum(amount),0)").Where("campaign_id = ?", c.ID).Scan(&sum) + if result.Error != nil { + return result.Error + } + c.AmountDonatedSoFar = sum + return nil +} diff --git a/code/src/myaktion/model/donation.go b/code/src/myaktion/model/donation.go new file mode 100644 index 0000000000000000000000000000000000000000..86e3df382e487326a76e3f07d3a8bdd67a6d78f4 --- /dev/null +++ b/code/src/myaktion/model/donation.go @@ -0,0 +1,20 @@ +package model + +import "gorm.io/gorm" + +type Status string + +const ( + TRANSFERRED Status = "TRANSFERRED" + IN_PROCESS Status = "IN_PROCESS" +) + +type Donation struct { + gorm.Model + CampaignID uint + Amount float64 `gorm:"notNull;check:amount >= 1.0"` + DonorName string `gorm:"notNull;size:40"` + ReceiptRequested bool `gorm:"notNull"` + Status Status `gorm:"notNull;type:ENUM('TRANSFERRED', 'IN_PROCESS')"` + Account Account `gorm:"embedded;embeddedPrefix:account_"` +} diff --git a/code/src/myaktion/monitor.go b/code/src/myaktion/monitor.go new file mode 100644 index 0000000000000000000000000000000000000000..defb0d2cfc5564bbbc79c35752c1a1d3d5048f2d --- /dev/null +++ b/code/src/myaktion/monitor.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/myaktion/client" + "github.com/turngeek/myaktion-go-2023/src/myaktion/client/banktransfer" + "github.com/turngeek/myaktion-go-2023/src/myaktion/service" +) + +func monitortransactions() { + for { + connectandmonitor() + time.Sleep(time.Second) + } +} + +func connectandmonitor() { + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + conn, err := client.GetBankTransferConnection(ctx) + if err != nil { + log.WithError(err).Fatal("Error connecting to the banktransfer service") + } + defer conn.Close() + banktransferClient := banktransfer.NewBankTransferClient(conn) + watcher, err := banktransferClient.ProcessTransactions(ctx) + if err != nil { + log.WithError(err).Fatal("Error watching transactions") + } + log.Info("Successfully connected to banktransfer service for processing transactions") + for { + transaction, err := watcher.Recv() + if err != nil { + if _, deadline := ctx.Deadline(); deadline { + log.Info("Deadline reached. Reconnect client") + break + } + log.WithError(err).Error("Error receiving transaction") + continue + } + entry := log.WithField("transaction", transaction) + entry.Info("Received transaction. Sending processing response") + err = service.MarkDonation(uint(transaction.DonationId)) + if err != nil { + entry.WithError(err).Error("Error changing donation status") + continue + } + entry.Info("Sending processing response") + err = watcher.Send(&banktransfer.ProcessingResponse{Id: transaction.Id}) + if err != nil { + entry.WithError(err).Error("Error sending processing response") + continue + } + entry.Info("Processing response sent") + } +} diff --git a/code/src/myaktion/service/campaign.go b/code/src/myaktion/service/campaign.go new file mode 100644 index 0000000000000000000000000000000000000000..27d3571568f32802ff4bca34c5590bc7a925c205 --- /dev/null +++ b/code/src/myaktion/service/campaign.go @@ -0,0 +1,79 @@ +package service + +import ( + "errors" + + log "github.com/sirupsen/logrus" + "gorm.io/gorm" + + "github.com/turngeek/myaktion-go-2023/src/myaktion/db" + "github.com/turngeek/myaktion-go-2023/src/myaktion/model" +) + +func CreateCampaign(campaign *model.Campaign) error { + result := db.DB.Create(campaign) + if result.Error != nil { + return result.Error + } + log.Infof("Successfully stored new campaign with ID %v in database.", campaign.ID) + log.Tracef("Stored: %v", campaign) + return nil +} + +func GetCampaigns() ([]model.Campaign, error) { + var campaigns []model.Campaign + result := db.DB.Preload("Donations").Find(&campaigns) + if result.Error != nil { + return nil, result.Error + } + log.Tracef("Retrieved: %v", campaigns) + return campaigns, nil +} + +func GetCampaign(id uint) (*model.Campaign, error) { + campaign := new(model.Campaign) + result := db.DB.Preload("Donations").First(campaign, id) + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, nil + } + if result.Error != nil { + return nil, result.Error + } + log.Tracef("Retrieved: %v", campaign) + return campaign, nil +} + +func UpdateCampaign(id uint, campaign *model.Campaign) (*model.Campaign, error) { + existingCampaign, err := GetCampaign(id) + if existingCampaign == nil || err != nil { + return existingCampaign, err + } + existingCampaign.Name = campaign.Name + existingCampaign.OrganizerName = campaign.OrganizerName + existingCampaign.TargetAmount = campaign.TargetAmount + existingCampaign.DonationMinimum = campaign.DonationMinimum + existingCampaign.Account = campaign.Account + result := db.DB.Save(existingCampaign) + if result.Error != nil { + return nil, result.Error + } + entry := log.WithField("ID", id) + entry.Info("Successfully updated campaign.") + entry.Tracef("Updated: %v", existingCampaign) + return existingCampaign, nil +} + +func DeleteCampaign(id uint) (*model.Campaign, error) { + campaign, err := GetCampaign(id) + if campaign == nil || err != nil { + return campaign, err + } + result := db.DB.Delete(campaign) + if result.Error != nil { + return nil, result.Error + } + entry := log.WithField("ID", id) + entry.Info("Successfully deleted campaign.") + entry.Tracef("Deleted: %v", campaign) + return campaign, nil +} \ No newline at end of file diff --git a/code/src/myaktion/service/donation.go b/code/src/myaktion/service/donation.go new file mode 100644 index 0000000000000000000000000000000000000000..28d8c7ffe7da37e81b3962cd5abe836135525f02 --- /dev/null +++ b/code/src/myaktion/service/donation.go @@ -0,0 +1,91 @@ +package service + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "github.com/turngeek/myaktion-go-2023/src/myaktion/client" + "github.com/turngeek/myaktion-go-2023/src/myaktion/client/banktransfer" + "github.com/turngeek/myaktion-go-2023/src/myaktion/db" + "github.com/turngeek/myaktion-go-2023/src/myaktion/model" +) + +func AddDonation(campaignId uint, donation *model.Donation) error { + campaign, err := GetCampaign(campaignId) + if err != nil { + return err + } + donation.CampaignID = campaign.ID + result := db.DB.Create(donation) + if result.Error != nil { + return result.Error + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + conn, err := client.GetBankTransferConnection(ctx) + if err != nil { + log.Errorf("Failed to connect to bank transfer service: %v", err) + deleteDonation(donation) + return err + } + defer conn.Close() + banktransferClient := banktransfer.NewBankTransferClient(conn) + _, err = banktransferClient.TransferMoney(ctx, &banktransfer.Transaction{ + DonationId: int32(donation.ID), + Amount: float32(donation.Amount), + Reference: "Donation", + FromAccount: convertAccount(&donation.Account), + ToAccount: convertAccount(&campaign.Account), + }) + if err != nil { + log.Errorf("error calling the banktransfer service: %v", err) + deleteDonation(donation) + return err + } + entry := log.WithField("ID", campaignId) + entry.Info("Successfully added new donation to campaign in database.") + entry.Tracef("Stored: %v", donation) + return nil +} + +func MarkDonation(id uint) error { + entry := log.WithField("donationId", id) + donation := new(model.Donation) + result := db.DB.First(donation, id) + if result.Error != nil { + entry.WithError(result.Error).Error("Error retrieving donation") + return result.Error + } + entry = entry.WithField("donation", donation) + entry.Trace("Retrieved donation") + donation.Status = model.TRANSFERRED + result = db.DB.Save(donation) + if result.Error != nil { + entry.WithError(result.Error).Error("Can't update donation") + return result.Error + } + entry.Info("Successfully updated status of donation") + return nil +} + +func convertAccount(account *model.Account) *banktransfer.Account { + return &banktransfer.Account{ + Name: account.Name, + BankName: account.BankName, + Number: account.Number, + } +} + +func deleteDonation(donation *model.Donation) error { + entry := log.WithField("donationID", donation.ID) + entry.Info("Trying to delete donation to make state consistent.") + result := db.DB.Delete(donation) + if result.Error != nil { + // Note: configure logger to raise an alarm to compensate inconsitent state + entry.WithField("alarm", true).Error("") + return result.Error + } + entry.Info("Successfully deleted donation to make state consistent.") + return nil +} diff --git a/code/src/wait-for-it.sh b/code/src/wait-for-it.sh new file mode 100644 index 0000000000000000000000000000000000000000..e1101549f73f779a7eab97fe1f52cc7cd16e9d3d --- /dev/null +++ b/code/src/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi diff --git a/configuration/deployment/banktransfer-deployment.yaml b/configuration/deployment/banktransfer-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f764a4ac62c7c0ffdfe2609f52791daf59c4e26b --- /dev/null +++ b/configuration/deployment/banktransfer-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: banktransfer + namespace: myaktion +spec: + selector: + matchLabels: + run: banktransfer + replicas: 2 + template: + metadata: + annotations: + linkerd.io/inject: enabled + labels: + run: banktransfer + spec: + containers: + - name: banktransfer + image: ginyanote/myaktion-go-banktransfer:1.0.0 + imagePullPolicy: IfNotPresent + env: + - name: KAFKA_CONNECT + value: mykafka:9092 \ No newline at end of file diff --git a/configuration/deployment/mariadb-deployment.yaml b/configuration/deployment/mariadb-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..adb71250edb953feea9144053dee80eb5855ca88 --- /dev/null +++ b/configuration/deployment/mariadb-deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mariadb + namespace: myaktion +spec: + selector: + matchLabels: + run: mariadb + replicas: 1 + template: + metadata: + annotations: + linkerd.io/inject: enabled + labels: + run: mariadb + spec: + containers: + - name: mariadb + image: mariadb:10.5 + imagePullPolicy: IfNotPresent + env: + - name: MYSQL_ROOT_PASSWORD + value: root + - name: MYSQL_DATABASE + value: myaktion + volumeMounts: + - mountPath: /var/lib/mysql + name: myaktion-pv + volumes: + - name: myaktion-pv + persistentVolumeClaim: + claimName: myaktion-pv-claim \ No newline at end of file diff --git a/configuration/deployment/myaktion-deployment.yaml b/configuration/deployment/myaktion-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..415fb6b40c8356981fa67bbea4d73268c6d84cf4 --- /dev/null +++ b/configuration/deployment/myaktion-deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myaktion + namespace: myaktion +spec: + selector: + matchLabels: + run: myaktion + replicas: 1 + template: + metadata: + annotations: + linkerd.io/inject: enabled + labels: + run: myaktion + spec: + containers: + - name: myaktion + image: ginyanote/myaktion-go-myaktion:1.0.0 + imagePullPolicy: IfNotPresent + env: + - name: DB_CONNECT + value: mariadb:3306 + - name: BANKTRANSFER_CONNECT + value: banktransfer:9111 + - name: LOG_LEVEL + value: info \ No newline at end of file diff --git a/configuration/deployment/mykafka-deployment.yaml b/configuration/deployment/mykafka-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..678a7cea06fbb1e968ab7f82a0c035daaaca6b92 --- /dev/null +++ b/configuration/deployment/mykafka-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kafka-env-config + namespace: myaktion +data: + KAFKA_ENABLE_KRAFT: "yes" + KAFKA_CFG_NODE_ID: "1" + KAFKA_CFG_PROCESS_ROLES: "broker,controller" + KAFKA_CFG_CONTROLLER_LISTENER_NAMES: "CONTROLLER" + KAFKA_CFG_LISTENERS: "PLAINTEXT://:9092,CONTROLLER://:9093" + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" + KAFKA_CFG_ADVERTISED_LISTENERS: "PLAINTEXT://mykafka:9092" + KAFKA_CFG_BROKER_ID: "1" + KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: "1@mykafka:9093" + ALLOW_PLAINTEXT_LISTENER: "yes" + KAFKA_KRAFT_CLUSTER_ID: "r4zt_wrqTRuT7W2NJsB_GA" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mykafka + namespace: myaktion +spec: + selector: + matchLabels: + run: mykafka + replicas: 1 + template: + metadata: + annotations: + linkerd.io/inject: enabled + labels: + run: mykafka + spec: + containers: + - name: mykafka + image: bitnami/kafka:3.3.2 + imagePullPolicy: IfNotPresent + envFrom: + - configMapRef: + name: kafka-env-config \ No newline at end of file diff --git a/configuration/ingressroute/myaktion-ingressroute.yaml b/configuration/ingressroute/myaktion-ingressroute.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e83b50313e73f9d3238f9008c941b0c9255c49e4 --- /dev/null +++ b/configuration/ingressroute/myaktion-ingressroute.yaml @@ -0,0 +1,48 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + annotations: + kubernetes.io/ingress.class: traefik + creationTimestamp: null + name: myaktion-ingressroute + namespace: myaktion +spec: + routes: + + - kind: Rule + match: Path(`/health`) && Method(`GET`) + services: + - kind: Service + name: myaktion + namespace: myaktion + port: 8000 + + - kind: Rule + match: Path(`/campaign`) && Method(`POST`) + middlewares: + - name: fw-auth-mw + namespace: default + services: + - kind: Service + name: myaktion + namespace: myaktion + port: 8000 + + - kind: Rule + match: Path(`/campaigns`) || Path(`/campaigns/{id:[0-9]+}`) + middlewares: + - name: fw-auth-mw + namespace: default + services: + - kind: Service + name: myaktion + namespace: myaktion + port: 8000 + + - kind: Rule + match: Path(`/campaigns/{id:[0-9]+}/donation`) && Method(`POST`) + services: + - kind: Service + name: myaktion + namespace: myaktion + port: 8000 \ No newline at end of file diff --git a/configuration/namespace/myaktion-namespace.yaml b/configuration/namespace/myaktion-namespace.yaml new file mode 100644 index 0000000000000000000000000000000000000000..df8d041a7373e21264fc1771417972e8eece7b52 --- /dev/null +++ b/configuration/namespace/myaktion-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myaktion \ No newline at end of file diff --git a/configuration/persistentvolume/myaktion-pv.yaml b/configuration/persistentvolume/myaktion-pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..056f7e7e3867ca3d3f255569a5afe011a4f25f47 --- /dev/null +++ b/configuration/persistentvolume/myaktion-pv.yaml @@ -0,0 +1,30 @@ +kind: PersistentVolume +apiVersion: v1 +metadata: + name: myaktion-pv + namespace: myaktion + labels: + type: local +spec: + storageClassName: standard + capacity: + storage: 100M + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/myaktion" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app: myaktion + name: myaktion-pv-claim + namespace: myaktion +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100M \ No newline at end of file diff --git a/configuration/service/banktransfer-service.yaml b/configuration/service/banktransfer-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5a83e4c560470c06412e5cf0565bbbdab171c0ad --- /dev/null +++ b/configuration/service/banktransfer-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: banktransfer + namespace: myaktion +spec: + type: ClusterIP + ports: + - port: 9111 + targetPort: 9111 + name: "grpc" + selector: + run: banktransfer \ No newline at end of file diff --git a/configuration/service/mariadb-service.yaml b/configuration/service/mariadb-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..04296f8b207bb32c57a6d7a6f538f22ad2154e85 --- /dev/null +++ b/configuration/service/mariadb-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: mariadb + namespace: myaktion +spec: + type: ClusterIP + ports: + - port: 3306 + targetPort: 3306 + selector: + run: mariadb \ No newline at end of file diff --git a/configuration/service/myaktion-service.yaml b/configuration/service/myaktion-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4fbf0562e91789cb6417bdd90c2129875c85415d --- /dev/null +++ b/configuration/service/myaktion-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: myaktion + namespace: myaktion +spec: + type: ClusterIP + ports: + - port: 8000 + targetPort: 8000 + name: "http" + selector: + run: myaktion \ No newline at end of file diff --git a/configuration/service/mykafka-service.yaml b/configuration/service/mykafka-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..78410490d472d9a61a77151a21495d0a5c1d439e --- /dev/null +++ b/configuration/service/mykafka-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + # reason for name mykafka: https://morioh.com/p/d1bafbd024d6 + name: mykafka + namespace: myaktion +spec: + type: ClusterIP + ports: + - name: broker + port: 9092 + targetPort: 9092 + - name: controller + port: 9093 + targetPort: 9093 + selector: + run: mykafka \ No newline at end of file