From a9235929231a423f13320817989c1d984a922ade Mon Sep 17 00:00:00 2001 From: kober <Katrin_Stephanie.Kober@Student.Reutlingen-University.DE> Date: Mon, 14 Apr 2025 16:25:42 +0200 Subject: [PATCH] Done --- go.work.sum | 3 ++ src/myaktion/db/db.go | 34 +++++++++++++++++++ src/myaktion/go.mod | 14 ++++++++ src/myaktion/go.sum | 31 ++++++++++++++++++ src/myaktion/handler/campaign.go | 47 +++++++++++++++++++++++++++ src/myaktion/handler/health.go | 14 ++++++++ src/myaktion/handler/utils.go | 34 +++++++++++++++++++ src/myaktion/main.go | 34 +++++++++++++++++-- src/myaktion/model/account.go | 6 ++-- src/myaktion/model/campaign.go | 17 ++++++---- src/myaktion/model/donation.go | 14 +++++--- src/myaktion/scripts/start-mariadb.sh | 2 ++ src/myaktion/scripts/stop-mariadb.sh | 2 ++ src/myaktion/service/campaign.go | 28 ++++++++++++++++ 14 files changed, 262 insertions(+), 18 deletions(-) create mode 100644 go.work.sum create mode 100644 src/myaktion/db/db.go create mode 100644 src/myaktion/go.sum create mode 100644 src/myaktion/handler/campaign.go create mode 100644 src/myaktion/handler/health.go create mode 100644 src/myaktion/handler/utils.go create mode 100644 src/myaktion/scripts/start-mariadb.sh create mode 100644 src/myaktion/scripts/stop-mariadb.sh create mode 100644 src/myaktion/service/campaign.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/src/myaktion/db/db.go b/src/myaktion/db/db.go new file mode 100644 index 0000000..07671bb --- /dev/null +++ b/src/myaktion/db/db.go @@ -0,0 +1,34 @@ +package db + +import ( + "fmt" + "os" + + log "github.com/sirupsen/logrus" + "gitlab.reutlingen-university.de/kober/myaktion-go/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 2d715af..f3e37cd 100644 --- a/src/myaktion/go.mod +++ b/src/myaktion/go.mod @@ -1,3 +1,17 @@ module gitlab.reutlingen-university.de/kober/myaktion-go/src/myaktion go 1.24.2 + +require github.com/sirupsen/logrus v1.9.3 + +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 + 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 new file mode 100644 index 0000000..d079fc6 --- /dev/null +++ b/src/myaktion/go.sum @@ -0,0 +1,31 @@ +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= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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 new file mode 100644 index 0000000..3ca57ed --- /dev/null +++ b/src/myaktion/handler/campaign.go @@ -0,0 +1,47 @@ +package handler + +import ( + "encoding/json" + "net/http" + + log "github.com/sirupsen/logrus" + + "gitlab.reutlingen-university.de/kober/myaktion-go/src/myaktion/model" + "gitlab.reutlingen-university.de/kober/myaktion-go/src/myaktion/service" +) + +func CreateCampaign(w http.ResponseWriter, r *http.Request) { + var campaign *model.Campaign + campaign, err := getCampaign(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + 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) { + var campaigns, err = service.GetCampaigns() + if err != nil { + log.Errorf("Error calling service GetCampaigns: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sendJson(w, campaigns) +} + +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 serialize request body to campaign struct: %v", err) + return nil, err + } + return &campaign, nil +} diff --git a/src/myaktion/handler/health.go b/src/myaktion/handler/health.go new file mode 100644 index 0000000..e338f6e --- /dev/null +++ b/src/myaktion/handler/health.go @@ -0,0 +1,14 @@ +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") + io.WriteString(w, `{"alive": true}`) + log.Info("Successful health check :)") +} diff --git a/src/myaktion/handler/utils.go b/src/myaktion/handler/utils.go new file mode 100644 index 0000000..16ca092 --- /dev/null +++ b/src/myaktion/handler/utils.go @@ -0,0 +1,34 @@ +package handler + +import ( + "encoding/json" + "net/http" + "strconv" + + log "github.com/sirupsen/logrus" + + "github.com/gorilla/mux" +) + +// REST services that just return a success message (like e.g. delete services) can send an instance of type result +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.Printf("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 get ID from request: %v", err) + return 0, err + } + return uint(id), nil +} diff --git a/src/myaktion/main.go b/src/myaktion/main.go index 7923039..6f7c872 100644 --- a/src/myaktion/main.go +++ b/src/myaktion/main.go @@ -1,9 +1,37 @@ -package myaktion +package main import ( - "fmt" + //"fmt" + "net/http" + "os" + + log "github.com/sirupsen/logrus" + + "github.com/gorilla/mux" + "gitlab.reutlingen-university.de/kober/myaktion-go/src/myaktion/handler" + //"gitlab.reutlingen-university.de/kober/myaktion-go/src/myaktion/model" ) +func init() { + log.SetFormatter(&log.TextFormatter{}) + log.SetReportCaller(true) + level, err := log.ParseLevel(os.Getenv("LOG_LEVEL")) + if err != nil { + log.Info("Log level not specified, set default to: INFO") + log.SetLevel(log.InfoLevel) + return + } + log.SetLevel(level) +} + func main() { - fmt.Println("Bloop") + log.Infoln("Starting My-Aktion API server") + router := mux.NewRouter() + router.HandleFunc("/health", handler.Health).Methods("GET") + router.HandleFunc("/campaigns", handler.CreateCampaign).Methods("POST") + router.HandleFunc("/campaigns", handler.GetCampaigns).Methods("GET") + if err := http.ListenAndServe(":8000", router); err != nil { + log.Fatal(err) + } + } 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 533597b..0451a19 100644 --- a/src/myaktion/model/campaign.go +++ b/src/myaktion/model/campaign.go @@ -1,11 +1,14 @@ package model +import "gorm.io/gorm" + type Campaign struct { - Name string - OrqanizerName string - TargetAmount float64 - DonationMinimum float64 - AmountDonatedSoFar float64 - Donation []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:"-"` + Donation []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 d89ed35..8bf2430 100644 --- a/src/myaktion/model/donation.go +++ b/src/myaktion/model/donation.go @@ -1,11 +1,15 @@ package model +import "gorm.io/gorm" + 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_"` } type Status string diff --git a/src/myaktion/scripts/start-mariadb.sh b/src/myaktion/scripts/start-mariadb.sh new file mode 100644 index 0000000..2cd94cb --- /dev/null +++ b/src/myaktion/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 \ No newline at end of file diff --git a/src/myaktion/scripts/stop-mariadb.sh b/src/myaktion/scripts/stop-mariadb.sh new file mode 100644 index 0000000..43a506d --- /dev/null +++ b/src/myaktion/scripts/stop-mariadb.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash + docker kill database && docker rm database \ No newline at end of file diff --git a/src/myaktion/service/campaign.go b/src/myaktion/service/campaign.go new file mode 100644 index 0000000..68ceaeb --- /dev/null +++ b/src/myaktion/service/campaign.go @@ -0,0 +1,28 @@ +package service + +import ( + log "github.com/sirupsen/logrus" + + "gitlab.reutlingen-university.de/kober/myaktion-go/src/myaktion/db" + "gitlab.reutlingen-university.de/kober/myaktion-go/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("Retrived: %v", campaigns) + return campaigns, nil +} -- GitLab