From e05d0649449cec3e9c4e024264df8b7f9b4956ff Mon Sep 17 00:00:00 2001
From: Martin Schmollinger <martin.schmollinger@reutlingen-university.de>
Date: Mon, 28 Apr 2025 15:03:31 +0200
Subject: [PATCH] Added DB (GORM) for services CreateCampaign and GetCampaigns.
 Added Scripts to start maria db docker image

---
 go.work.sum                      |  3 +++
 scripts/start-mariadb.sh         |  2 ++
 scripts/stop-mariadb.sh          |  2 ++
 src/myaktion/db/db.go            | 31 +++++++++++++++++++++++++++++++
 src/myaktion/go.mod              |  9 ++++++++-
 src/myaktion/go.sum              | 18 ++++++++++++++++++
 src/myaktion/handler/campaign.go |  1 +
 src/myaktion/handler/utils.go    |  3 +++
 src/myaktion/model/account.go    |  6 +++---
 src/myaktion/model/campaign.go   | 18 ++++++++++--------
 src/myaktion/model/donation.go   | 14 +++++++++-----
 src/myaktion/service/campaign.go | 30 ++++++++++++------------------
 12 files changed, 102 insertions(+), 35 deletions(-)
 create mode 100644 go.work.sum
 create mode 100755 scripts/start-mariadb.sh
 create mode 100755 scripts/stop-mariadb.sh
 create mode 100644 src/myaktion/db/db.go

diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..92ca04a
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,3 @@
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
diff --git a/scripts/start-mariadb.sh b/scripts/start-mariadb.sh
new file mode 100755
index 0000000..0e31ab3
--- /dev/null
+++ b/scripts/start-mariadb.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+docker run -d -p 3306:3306 --name database -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=myaktion mariadb:10.5
diff --git a/scripts/stop-mariadb.sh b/scripts/stop-mariadb.sh
new file mode 100755
index 0000000..127f596
--- /dev/null
+++ b/scripts/stop-mariadb.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+docker kill database && docker rm database
diff --git a/src/myaktion/db/db.go b/src/myaktion/db/db.go
new file mode 100644
index 0000000..7f7bac7
--- /dev/null
+++ b/src/myaktion/db/db.go
@@ -0,0 +1,31 @@
+package db
+
+import (
+	"fmt"
+	"os"
+
+	log "github.com/sirupsen/logrus"
+	"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+)
+
+var DB *gorm.DB
+
+func init() {
+	dsn := fmt.Sprintf("root:root@tcp(%s)/myaktion?charset=utf8&parseTime=True&loc=Local", os.Getenv("DB_CONNECT"))
+	log.Info("Using DSN for DB:", dsn)
+	var err error
+	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
+	if err != nil {
+		panic("failed to connect to database")
+	}
+	log.Info("Starting automatic migrations")
+	if err := DB.Debug().AutoMigrate(&model.Campaign{}); err != nil {
+		panic(err)
+	}
+	if err := DB.Debug().AutoMigrate(&model.Donation{}); err != nil {
+		panic(err)
+	}
+	log.Info("Automatic migrations finished")
+}
diff --git a/src/myaktion/go.mod b/src/myaktion/go.mod
index c32099d..51d2907 100644
--- a/src/myaktion/go.mod
+++ b/src/myaktion/go.mod
@@ -3,7 +3,14 @@ module gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktio
 go 1.24.1
 
 require (
+	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/go-sql-driver/mysql v1.9.2 // indirect
 	github.com/gorilla/mux v1.8.1 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
-	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.24.0 // indirect
+	gorm.io/driver/mysql v1.5.7 // indirect
+	gorm.io/gorm v1.25.12 // indirect
 )
diff --git a/src/myaktion/go.sum b/src/myaktion/go.sum
index b146617..d079fc6 100644
--- a/src/myaktion/go.sum
+++ b/src/myaktion/go.sum
@@ -1,7 +1,16 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 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/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
+github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -9,5 +18,14 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
 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=
+gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
+gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
diff --git a/src/myaktion/handler/campaign.go b/src/myaktion/handler/campaign.go
index 62fb5fa..7fec4f9 100644
--- a/src/myaktion/handler/campaign.go
+++ b/src/myaktion/handler/campaign.go
@@ -14,6 +14,7 @@ func CreateCampaign(w http.ResponseWriter, r *http.Request) {
 	var campaign *model.Campaign
 	campaign, err := getCampaign(r)
 	if err != nil {
+		log.Printf("Error calling service CreateCampaign: %v", err)
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
diff --git a/src/myaktion/handler/utils.go b/src/myaktion/handler/utils.go
index 026e93a..88f389b 100644
--- a/src/myaktion/handler/utils.go
+++ b/src/myaktion/handler/utils.go
@@ -9,10 +9,12 @@ import (
 	log "github.com/sirupsen/logrus"
 )
 
+// Result type for handler that do not return a resource like e.g. deleting a resource.
 type result struct {
 	Success string `json:"success"`
 }
 
+// Helper function that sends an arbitrary object back encoded as JSON.
 func sendJson(w http.ResponseWriter, value interface{}) {
 	w.Header().Set("Content-Type", "application/json")
 	if err := json.NewEncoder(w).Encode(value); err != nil {
@@ -21,6 +23,7 @@ func sendJson(w http.ResponseWriter, value interface{}) {
 	}
 }
 
+// Helper function for handler working on IDs passed as URI path variable.
 func getId(r *http.Request) (uint, error) {
 	vars := mux.Vars(r)
 	id, err := strconv.ParseUint(vars["id"], 10, 0)
diff --git a/src/myaktion/model/account.go b/src/myaktion/model/account.go
index d2e74b4..e0e927a 100644
--- a/src/myaktion/model/account.go
+++ b/src/myaktion/model/account.go
@@ -1,7 +1,7 @@
 package model
 
 type Account struct {
-	Name     string
-	BankName string
-	Number   string
+	Name     string `gorm:"notNull;size:60"`
+	BankName string `gorm:"notNull;size:40"`
+	Number   string `gorm:"notNull;size:20"`
 }
diff --git a/src/myaktion/model/campaign.go b/src/myaktion/model/campaign.go
index 9fb5819..47c8cd4 100644
--- a/src/myaktion/model/campaign.go
+++ b/src/myaktion/model/campaign.go
@@ -1,12 +1,14 @@
 package model
 
+import "gorm.io/gorm"
+
 type Campaign struct {
-	ID                 uint
-	Name               string
-	OrganizerName      string
-	TargetAmount       float64
-	DonationMinimum    float64
-	AmountDonatedSoFar float64
-	Donations          []Donation
-	Account            Account
+	gorm.Model
+	Name               string     `gorm:"notNull;size:30"`
+	OrganizerName      string     `gorm:"notNull"`
+	TargetAmount       float64    `gorm:"notNull;check:target_amount >= 10.0"`
+	DonationMinimum    float64    `gorm:"notNull;check:donation_minimum >= 1.0"`
+	AmountDonatedSoFar float64    `gorm:"-"`
+	Donations          []Donation `gorm:"foreignKey:CampaignID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
+	Account            Account    `gorm:"embedded;embeddedPrefix:account_"`
 }
diff --git a/src/myaktion/model/donation.go b/src/myaktion/model/donation.go
index bf72103..f084ada 100644
--- a/src/myaktion/model/donation.go
+++ b/src/myaktion/model/donation.go
@@ -1,5 +1,7 @@
 package model
 
+import "gorm.io/gorm"
+
 type Status string
 
 const (
@@ -8,9 +10,11 @@ const (
 )
 
 type Donation struct {
-	Amount           float64
-	DonorName        string
-	ReceiptRequested bool
-	Status           Status
-	Account          Account
+	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/src/myaktion/service/campaign.go b/src/myaktion/service/campaign.go
index b15144d..cd2e89e 100644
--- a/src/myaktion/service/campaign.go
+++ b/src/myaktion/service/campaign.go
@@ -1,34 +1,28 @@
 package service
 
 import (
-	"log"
+	log "github.com/sirupsen/logrus"
 
+	"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/db"
 	"gitlab.reutlingen-university.de/go-exercises/myaktion-go-ss25/src/myaktion/model"
 )
 
-var (
-	campaignStore map[uint]*model.Campaign
-	actCampaignId uint = 1
-)
-
-func init() {
-	campaignStore = make(map[uint]*model.Campaign)
-}
-
 func CreateCampaign(campaign *model.Campaign) error {
-	campaign.ID = actCampaignId
-	campaignStore[actCampaignId] = campaign
-	actCampaignId += 1
-	log.Printf("Successfully stored new campaign with ID %v in database.", campaign.ID)
-	log.Printf("Stored: %v", campaign)
+	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
-	for _, campaign := range campaignStore {
-		campaigns = append(campaigns, *campaign)
+	result := db.DB.Preload("Donations").Find(&campaigns)
+	if result.Error != nil {
+		return nil, result.Error
 	}
-	log.Printf("Retrieved: %v", campaigns)
+	log.Tracef("Retrieved: %v", campaigns)
 	return campaigns, nil
 }
-- 
GitLab