diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | cmd/main.go | 42 | ||||
-rw-r--r-- | go.mod | 12 | ||||
-rw-r--r-- | http/middleware.go | 16 | ||||
-rw-r--r-- | http/rest/registrationHandler.go | 90 | ||||
-rw-r--r-- | log/log.go | 9 | ||||
-rw-r--r-- | nats/constants.go | 5 | ||||
-rw-r--r-- | nats/notify.go | 31 | ||||
-rw-r--r-- | notifier.go | 7 | ||||
-rw-r--r-- | persistence/sqlite/0001_Create_Initial_Tables.sql | 13 | ||||
-rw-r--r-- | persistence/sqlite/sqliteRepository.go | 100 | ||||
-rw-r--r-- | registration.go | 13 | ||||
-rw-r--r-- | repository.go | 7 | ||||
-rw-r--r-- | service.go | 22 |
14 files changed, 369 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51b7223 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.sqlite +*.sqlite3 diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..4ff1658 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "log" + "net/http" + "os" + "path" + + "uvok.de/go/training_fellow/registration" + myhttp "uvok.de/go/training_fellow/registration/http" + "uvok.de/go/training_fellow/registration/http/rest" + "uvok.de/go/training_fellow/registration/nats" + persistence "uvok.de/go/training_fellow/registration/persistence/sqlite" +) + +func main() { + log.Print("Starting application") + workDir, err := os.Getwd() + context := context.Background() + if err != nil { + workDir = "." + } + + notifier := &nats.NatsNotifier{} + + databasePath := path.Join(workDir, "registrations.sqlite") + repository := persistence.NewRepository(databasePath, context) + service := ®istration.RegistrationService{Notifier: notifier, Repository: repository} + handler := &rest.RegistrationHandler{Service: service} + + newRegistrationHandler := rest.HandleRegistration(handler) + + contentHandler := myhttp.IsFormContentCheck(newRegistrationHandler) + http.Handle("/register", contentHandler) + http.Handle("/registrations", rest.HandleGetRegistrations(handler)) + log.Print("Starting server") + err = http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("Error starting HTTP server: %v", err) + } +} @@ -0,0 +1,12 @@ +module uvok.de/go/training_fellow/registration + +go 1.21.4 + +require ( + github.com/klauspost/compress v1.17.0 // indirect + github.com/nats-io/nats.go v1.31.0 // indirect + github.com/nats-io/nkeys v0.4.5 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect +) diff --git a/http/middleware.go b/http/middleware.go new file mode 100644 index 0000000..f2fe90d --- /dev/null +++ b/http/middleware.go @@ -0,0 +1,16 @@ +package http + +import "net/http" + +func IsFormContentCheck(innerHandler http.Handler) http.Handler { + outerHandler := func(w http.ResponseWriter, r *http.Request) { + // TOdo: + contentType := r.Header.Get("Content-Type") + if contentType != "application/x-www-form-urlencoded" { + w.WriteHeader(http.StatusNotAcceptable) + return + } + innerHandler.ServeHTTP(w, r) + } + return http.HandlerFunc(outerHandler) +} diff --git a/http/rest/registrationHandler.go b/http/rest/registrationHandler.go new file mode 100644 index 0000000..b38d62c --- /dev/null +++ b/http/rest/registrationHandler.go @@ -0,0 +1,90 @@ +package rest + +import ( + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/google/uuid" + "uvok.de/go/training_fellow/registration" + mylog "uvok.de/go/training_fellow/registration/log" +) + +type RegistrationHandler struct { + Service *registration.RegistrationService +} + +// adapter to have multiple methods without repeating type +// (I have to implement ServeHTTP) +func HandleRegistration(handler *RegistrationHandler) http.Handler { + regHandler := func(w http.ResponseWriter, r *http.Request) { + handleRegistration(handler, w, r) + } + return http.HandlerFunc(regHandler) +} + +func HandleGetRegistrations(handler *RegistrationHandler) http.Handler { + getRegs := func(w http.ResponseWriter, r *http.Request) { + regs, err := handler.Service.GetUnconfirmedRegistrations() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + content, err := json.Marshal(regs) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Write(content) + } + return http.HandlerFunc(getRegs) +} + +func handleRegistration(handler *RegistrationHandler, rw http.ResponseWriter, req *http.Request) { + + //bodyReader := http.MaxBytesReader(rw, req.Body, 10) + //defer bodyReader.Close() + defer req.Body.Close() + + if req.Method != http.MethodPost { + rw.WriteHeader(http.StatusMethodNotAllowed) + return + } + + err := req.ParseForm() + if err != nil { + rw.WriteHeader(http.StatusNotAcceptable) + mylog.LogError.Printf("Form Parse: %v\n", err) + return + } + newRegistration := registration.Registration{} + + newRegistration.Company = req.Form.Get("company") + newRegistration.Date = req.Form.Get("date") + newRegistration.Email = req.Form.Get("email") + newRegistration.FirstName = req.Form.Get("first_name") + newRegistration.LastName = req.Form.Get("last_name") + newRegistration.TrainingCode = req.Form.Get("training_code") + val, err := strconv.ParseBool(req.Form.Get("privacy_acc")) + newRegistration.PrivacyPolicyAccepted = false + newRegistration.Confirmed = false + newRegistration.RegId = uuid.NewString() + + if err != nil { + mylog.LogWarning.Printf("Form Parse Bool: %v\n", err) + } else { + newRegistration.PrivacyPolicyAccepted = val + } + + log.Printf("New registration: %v", newRegistration) + if handler.Service != nil { + err = handler.Service.HandleNewRegistration(&newRegistration) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + mylog.LogError.Printf("Error submitting the following registration: %v. Error: %v", newRegistration, err) + return + } + } + rw.WriteHeader(http.StatusCreated) +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..7edb61c --- /dev/null +++ b/log/log.go @@ -0,0 +1,9 @@ +package log + +import ( + "log" + "os" +) + +var LogError = log.New(os.Stdout, "Error: ", log.LstdFlags) +var LogWarning = log.New(os.Stdout, "Warning: ", log.LstdFlags) diff --git a/nats/constants.go b/nats/constants.go new file mode 100644 index 0000000..82436b5 --- /dev/null +++ b/nats/constants.go @@ -0,0 +1,5 @@ +package nats + +const ( + NATS_TOPIC_REGISTRATION_NEW = "uvok.de.go.training_fellow.registration.new" +) diff --git a/nats/notify.go b/nats/notify.go new file mode 100644 index 0000000..56cbc7f --- /dev/null +++ b/nats/notify.go @@ -0,0 +1,31 @@ +package nats + +import ( + "github.com/nats-io/nats.go" + "uvok.de/go/training_fellow/registration" +) + +type NatsNotifier struct { +} + +func (notif *NatsNotifier) NotifyNewRegistration(reg *registration.Registration) error { + conn, err := nats.Connect("nats://nats-server:4222") + if err != nil { + return err + } + defer conn.Close() + + enc_conn, err := nats.NewEncodedConn(conn, nats.JSON_ENCODER) + + if err != nil { + return err + } + + defer enc_conn.Close() + + err = enc_conn.Publish(NATS_TOPIC_REGISTRATION_NEW, reg) + if err != nil { + return err + } + return nil +} diff --git a/notifier.go b/notifier.go new file mode 100644 index 0000000..f5169c3 --- /dev/null +++ b/notifier.go @@ -0,0 +1,7 @@ +package registration + +// RegistrationNotifier forwards new registrations +type RegistrationNotifier interface { + //NotifyNewRegistration notifies about a new registration + NotifyNewRegistration(reg *Registration) error +} diff --git a/persistence/sqlite/0001_Create_Initial_Tables.sql b/persistence/sqlite/0001_Create_Initial_Tables.sql new file mode 100644 index 0000000..d563e4a --- /dev/null +++ b/persistence/sqlite/0001_Create_Initial_Tables.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS registrations ( + reg_id TEXT PRIMARY KEY NOT NULL, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + email TEXT NOT NULL, + company TEXT, + training_code TEXT, + date TEXT NOT NULL, + privacy_policy_accepted INTEGER NOT NULL, + confirmed INTEGER NOT NULL +); + +PRAGMA user_version = 1 diff --git a/persistence/sqlite/sqliteRepository.go b/persistence/sqlite/sqliteRepository.go new file mode 100644 index 0000000..61e89d8 --- /dev/null +++ b/persistence/sqlite/sqliteRepository.go @@ -0,0 +1,100 @@ +package persistence + +import ( + "context" + "database/sql" + "log" + + _ "modernc.org/sqlite" + + "uvok.de/go/training_fellow/registration" +) + +var ( + debug bool = true +) + +type SqliteRepository struct { + FileName string + context context.Context +} + +func NewRepository(fileName string, context context.Context) *SqliteRepository { + repo := SqliteRepository{FileName: fileName, context: context} + return &repo +} + +func (repo *SqliteRepository) SaveRegistration(registration *registration.Registration) error { + db, err := repo.checkInit() + if err != nil { + return err + } + defer db.Close() + + res, err := db.ExecContext( + repo.context, + `INSERT INTO registrations + (reg_id, first_name, last_name, email, company, training_code, date, privacy_policy_accepted, confirmed) + VALUES (?,?,?,?,?,?,?,?,?)`, + registration.RegId, + registration.FirstName, + registration.LastName, + registration.Email, + registration.Company, + registration.TrainingCode, + registration.Date, + registration.PrivacyPolicyAccepted, + registration.Confirmed) + + if debug { + log.Printf("SQL result: %v", res) + } + return err +} + +func (repo *SqliteRepository) GetUnconfirmedRegistrations() ([]*registration.Registration, error) { + db, err := repo.checkInit() + if err != nil { + return nil, err + } + res, err := db.QueryContext( + repo.context, + `SELECT reg_id, first_name, last_name, email, company, training_code, date, privacy_policy_accepted + FROM registrations + WHERE confirmed = 0 + `) + + if err != nil { + return nil, err + } + defer res.Close() + + regArray := make([]*registration.Registration, 0) + for res.Next() { + reg := registration.Registration{} + err = res.Scan(®.RegId, ®.FirstName, ®.LastName, ®.Email, ®.Company, ®.TrainingCode, ®.Date, ®.PrivacyPolicyAccepted) + if err != nil { + log.Printf("Error scanning: %v", err) + } + regArray = append(regArray, ®) + } + return regArray, nil +} + +func (repo *SqliteRepository) ConfirmRegistration(registrationId string) (*registration.Registration, error) { + repo.checkInit() + panic("not implemented") // TODO: Implement +} + +func (repo *SqliteRepository) checkInit() (*sql.DB, error) { + db, err := sql.Open("sqlite", repo.FileName) + if err != nil { + return nil, err + } + err = db.PingContext(repo.context) + + if err != nil { + return nil, err + } + return db, nil +} diff --git a/registration.go b/registration.go new file mode 100644 index 0000000..c6becc6 --- /dev/null +++ b/registration.go @@ -0,0 +1,13 @@ +package registration + +type Registration struct { + FirstName string `json:"FirstName,omitempty"` + LastName string `json:"LastName,omitempty"` + Email string `json:"Mail,omitempty"` + Company string `json:"Company,omitempty"` + TrainingCode string `json:"TCode,omitempty"` + Date string `json:"Date,omitempty"` + PrivacyPolicyAccepted bool `json:"PPol,omitempty"` + Confirmed bool `json:"confirmed"` + RegId string `json:"id"` +} diff --git a/repository.go b/repository.go new file mode 100644 index 0000000..f559406 --- /dev/null +++ b/repository.go @@ -0,0 +1,7 @@ +package registration + +type RegistrationRepository interface { + SaveRegistration(*Registration) error + GetUnconfirmedRegistrations() ([]*Registration, error) + ConfirmRegistration(registrationId string) (*Registration, error) +} diff --git a/service.go b/service.go new file mode 100644 index 0000000..6523d21 --- /dev/null +++ b/service.go @@ -0,0 +1,22 @@ +package registration + +type RegistrationService struct { + Notifier RegistrationNotifier + Repository RegistrationRepository +} + +func (service *RegistrationService) HandleNewRegistration(reg *Registration) error { + return service.Repository.SaveRegistration(reg) +} + +func (service *RegistrationService) GetUnconfirmedRegistrations() ([]*Registration, error) { + return service.Repository.GetUnconfirmedRegistrations() +} + +func (service *RegistrationService) ConfirmRegistration(registrationId string) error { + registration, err := service.Repository.ConfirmRegistration(registrationId) + if err != nil { + return err + } + return service.Notifier.NotifyNewRegistration(registration) +} |