diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..d6a0db8b409d161eb0a688d02fa472b24aa7e560 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: “3.8†+services: + emailnotification: + build: + context: ./src + dockerfile: emailnotification/Dockerfile + ports: + - "8001:8001" + depends_on: + - nats + environment: + - NATS_CONNECT=nats:4222 + - LOG_LEVEL=info # change to trace for debugging + highlanderticketing: + build: + context: ./src + dockerfile: highlanderticketing/Dockerfile + ports: + - "8000:8000" + environment: + - NATS_CONNECT=nats:4222 + - EMAILNOT_CONNECT=emailnotification:8001 + - DB_CONNECT=mongo:27017 + - LOG_LEVEL=info # change to trace for debugging + mongo: + container_name: mongo + image: mongo:4.4 + ports: + - 27017:27017 + command: mongod + nats: + image: nats:latest + container_name: nats + ports: + - 4222:4222 \ No newline at end of file diff --git a/go.work b/go.work index b3a61eca966ff63e593268cbb6f88753a902d844..5ef30290575bf47777f03221e4ba6d65154550ec 100644 --- a/go.work +++ b/go.work @@ -1 +1,6 @@ go 1.20 + +use ( + ./src/emailnotification + ./src/highlanderticketing +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000000000000000000000000000000000000..537ca644d7c487f6a9a2be7a24fb98ec9a9ce754 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,6 @@ +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= diff --git a/src/emailnotification/Dockerfile b/src/emailnotification/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2946e191d1e3ce3334463deaaacd1d3e4d03e3de --- /dev/null +++ b/src/emailnotification/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.20-buster +# non-go modules dependencies +RUN apt update + +# copy code +WORKDIR /go/src/app +COPY ./emailnotification . + +RUN go mod download +RUN go install + +RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh +RUN chmod +x ./wait-for-it.sh ./docker-entrypoint.sh + +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["emailnotification"] + +EXPOSE 8001 \ No newline at end of file diff --git a/src/emailnotification/docker-entrypoint.sh b/src/emailnotification/docker-entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..9e499df2a2095fb4dc62ba9e4ab62f1bfb0506b0 --- /dev/null +++ b/src/emailnotification/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Abort on any error (including if wait-for-it fails). +set -e +# Wait for nats +if [ -n "$NATS_CONNECT" ]; then +/go/src/app/wait-for-it.sh "$NATS_CONNECT" -t 20 +fi +# Run the main container command. +exec "$@" \ No newline at end of file diff --git a/src/emailnotification/go.mod b/src/emailnotification/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..a360529a0794f0e09f51c469d39dd951a2f06ca3 --- /dev/null +++ b/src/emailnotification/go.mod @@ -0,0 +1,19 @@ +module gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/emailnotification + +go 1.20 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/nats-io/nats.go v1.27.1 +) + +require ( + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/nats-io/nats-server/v2 v2.9.19 // indirect + github.com/nats-io/nkeys v0.4.4 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/sys v0.8.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) diff --git a/src/emailnotification/go.sum b/src/emailnotification/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..2d4b752023f0f3424adb139e9c5abec6dafdee45 --- /dev/null +++ b/src/emailnotification/go.sum @@ -0,0 +1,28 @@ +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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= +github.com/nats-io/nats-server/v2 v2.9.19 h1:OF9jSKZGo425C/FcVVIvNgpd36CUe7aVTTXEZRJk6kA= +github.com/nats-io/nats-server/v2 v2.9.19/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw= +github.com/nats-io/nats.go v1.27.1 h1:OuYnal9aKVSnOzLQIzf7554OXMCG7KbaTkCSBHRcSoo= +github.com/nats-io/nats.go v1.27.1/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= +github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= +github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/src/emailnotification/main.go b/src/emailnotification/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d5723b7859b2932cbf44856d8b6a243baba639e0 --- /dev/null +++ b/src/emailnotification/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/nats-io/nats.go" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/emailnotification/model" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/emailnotification/service" +) + +func main() { + nc, err := service.ConnectToNats() + if err != nil { + log.Fatalf("unable to connect to nats %v", err) + } + nc.Subscribe("confirmOrder.*", func(m *nats.Msg) { + var ( + req model.EmialContent + res model.Response + ) + if err := json.Unmarshal(m.Data, &req); err != nil { + panic(err) + } + + emailadre, emailcontent, emailtype := service.CreateEmail(req, "confirm") + err := service.SendEmail(emailadre, emailcontent, emailtype) + if err != nil { + res.Send = false + } else { + res.Send = true + } + e, errMarshal := json.Marshal(res) + if errMarshal != nil { + fmt.Println(errMarshal) + return + } + nc.Publish(m.Reply, []byte(e)) + }) + + nc.Subscribe("confirmCancel.*", func(m *nats.Msg) { + var ( + req model.EmialContent + res model.Response + ) + if err := json.Unmarshal(m.Data, &req); err != nil { + panic(err) + } + emailadre, emailcontent, emailtype := service.CreateEmail(req, "cancel") + if err := service.SendEmail(emailadre, emailcontent, emailtype); err != nil { + res.Send = false + } else { + res.Send = true + } + e, errMarshal := json.Marshal(res) + if errMarshal != nil { + fmt.Println(errMarshal) + return + } + nc.Publish(m.Reply, []byte(e)) + }) + + if err := http.ListenAndServe(":8181", nil); err != nil { + log.Fatal(err) + } + +} diff --git a/src/emailnotification/model/emailContent.go b/src/emailnotification/model/emailContent.go new file mode 100644 index 0000000000000000000000000000000000000000..5a7867cd142ec0802e44c25d342aed04a441285c --- /dev/null +++ b/src/emailnotification/model/emailContent.go @@ -0,0 +1,10 @@ +package model + +type EmialContent struct { + OrderID string `json:"orderid"` + Name string `json:"name"` + AwayMatch bool `json:"awaymatch"` + Location string `json:"location"` + Date string `json:"date"` + Emailadress string `json:"emailadress"` +} diff --git a/src/emailnotification/model/natsResponse.go b/src/emailnotification/model/natsResponse.go new file mode 100644 index 0000000000000000000000000000000000000000..ea4856d8c7a413929becc22fe1eff1dc742d89f7 --- /dev/null +++ b/src/emailnotification/model/natsResponse.go @@ -0,0 +1,5 @@ +package model + +type Response struct { + Send bool `json:"send"` +} diff --git a/src/emailnotification/service/email.go b/src/emailnotification/service/email.go new file mode 100644 index 0000000000000000000000000000000000000000..cacd6c2a4b1874e91b5f84e1ec534944ab0911d7 --- /dev/null +++ b/src/emailnotification/service/email.go @@ -0,0 +1,62 @@ +package service + +import ( + "fmt" + "log" + "net/mail" + "net/smtp" + "os" + "strings" + + "github.com/joho/godotenv" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/emailnotification/model" +) + +func CreateEmail(emailContenct model.EmialContent, subject string) (string, string, string) { + if subject == "confirm" { + return emailContenct.Emailadress, fmt.Sprintf("Hallo Herr/Frau, %s\r\nHiermit bestaetigen wird deine Bestellung fuer das VFB Spiel", emailContenct.Name), "Confirm Order" + } + if subject == "cancel" { + return emailContenct.Emailadress, fmt.Sprintf("Hallo Herr/Frau, %s\r\nHiermit bestaetigen wird die Stornierung deiner Bestellung fuer das VFB Spiel in %s, am %s", emailContenct.Name, emailContenct.Location, emailContenct.Date), "Confirm Cancelation" + } + return "", "", "" +} +func SendEmail(receiver string, body string, subject string) error { + err := godotenv.Load(".env") + + if err != nil { + log.Fatalf("Error loading .env file") + return fmt.Errorf("Error loading .env file") + } + + from := mail.Address{ + Name: "Highlander Ticketing", + Address: os.Getenv("EMAIL_ADRESS"), + } + + toList := []string{} + toList = append(toList, receiver) + + header := make(map[string]string) + header["From"] = from.String() + header["To"] = strings.Join(toList, ", ") + header["Subject"] = subject + + message := "" + for key, value := range header { + message += fmt.Sprintf("%s: %s\r\n", key, value) + } + message += "\r\n" + body + + smtpServer := "smtp.web.de" + smtpPort := "587" + password := os.Getenv("EMAIL_PW") + + auth := smtp.PlainAuth("", from.Address, password, smtpServer) + + err1 := smtp.SendMail(smtpServer+":"+smtpPort, auth, from.Address, toList, []byte(message)) + if err1 != nil { + return err1 + } + return nil +} diff --git a/src/emailnotification/service/nats.go b/src/emailnotification/service/nats.go new file mode 100644 index 0000000000000000000000000000000000000000..f2a82a6d1f5c71e0c2c27c9b5803cb768179f35b --- /dev/null +++ b/src/emailnotification/service/nats.go @@ -0,0 +1,23 @@ +package service + +import ( + "log" + "os" + + "github.com/joho/godotenv" + "github.com/nats-io/nats.go" +) + +func ConnectToNats() (*nats.Conn, error) { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("Error loading .env file") + } + uri := os.Getenv("NATS_URI") + var err error + var nc *nats.Conn + nc, err = nats.Connect(uri) + if err != nil { + log.Fatal("Error establishing connection to NATS:", err) + } + return nc, nil +} diff --git a/src/emailnotification/wait-for-it.sh b/src/emailnotification/wait-for-it.sh new file mode 100644 index 0000000000000000000000000000000000000000..3974640b053e6f84a21c292c01e3674348445831 --- /dev/null +++ b/src/emailnotification/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 \ No newline at end of file diff --git a/src/highlanderticketing/Dockerfile b/src/highlanderticketing/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..13e739190a4ac6d3e89a01e274f3cc61f1e1519a --- /dev/null +++ b/src/highlanderticketing/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.20-buster +# non-go modules dependencies +RUN apt update + +# copy code and protobuf +WORKDIR /go/src/app +COPY ./highlanderticketing . + +RUN go mod download +RUN go install + +RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/81b1373f17855a4dc21156cfe1694c31d7d1792e/wait-for-it.sh +RUN chmod +x ./wait-for-it.sh ./docker-entrypoint.sh + +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["highlanderticketing"] + +EXPOSE 8000 \ No newline at end of file diff --git a/src/highlanderticketing/api/match.go b/src/highlanderticketing/api/match.go new file mode 100644 index 0000000000000000000000000000000000000000..cd4fea9151aacb9c822cd97a8224a6637a08a732 --- /dev/null +++ b/src/highlanderticketing/api/match.go @@ -0,0 +1,121 @@ +package api + +import ( + "encoding/json" + "io" + "net/http" + "time" + + log "github.com/sirupsen/logrus" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" +) + +func GetMatchesOfApi(apiUrl string) ([]*model.Match, error) { + data, err := getData(apiUrl) + if err != nil { + log.Errorf("Failure loading data of match %v", err) + return []*model.Match{}, err + } + matches, err := formatJsonToMatch(data) + if err != nil { + log.Errorf("Failure formating match %v", err) + return []*model.Match{}, err + } + log.Info("Got matches of API") + return matches, nil +} + +func GetlatestMatchesOfApi(url string, updateChan chan<- *model.Match) error { + data, err := getData(url) + if err != nil { + log.Errorf("Failure loading data of match %v", err) + return err + } + matches, err := formatJsonToMatch(data) + if err != nil { + log.Errorf("Failure formating match %v", err) + return err + } + for _, match := range matches { + updateChan <- match + } + log.Info("added matches to chain") + return nil +} + +func getData(apiUrl string) ([]byte, error) { + request, err := http.NewRequest("GET", apiUrl, nil) + + if err != nil { + log.Errorf("Failure creating request %v", err) + return []byte{}, err + } + client := &http.Client{} + response, err := client.Do(request) + + if err != nil { + log.Errorf("Failure making request %v", err) + return []byte{}, err + } + + responseBody, err := io.ReadAll(response.Body) + + if err != nil { + log.Errorf("Failure searalizing request %v", err) + return []byte{}, err + } + defer response.Body.Close() + log.Info("Got data of API") + return responseBody, nil +} + +func formatJsonToMatch(jsonArray []byte) ([]*model.Match, error) { + var matches []*model.Match + var results []map[string]interface{} + + if err := json.Unmarshal(jsonArray, &results); err != nil { + log.Errorf("Failure unmarshaling data %v", err) + return nil, err + } + + for _, result := range results { + var match model.Match + match.ExternalID = int64(result["matchID"].(float64)) + match.LeagueName = result["leagueName"].(string) + + matchDate, err := time.Parse("2006-01-02T15:04:05", result["matchDateTime"].(string)) + if err != nil { + log.Errorf("Failure parsing date %v", err) + return nil, err + } + match.Date = matchDate + + if team1, ok := result["team1"].(map[string]interface{}); ok { + if name, ok := team1["shortName"].(string); ok { + match.Location = name + } + } + + if team1, ok := result["team1"].(map[string]interface{}); ok { + if name, ok := team1["teamName"].(string); ok { + if name != "VfB Stuttgart" { + match.Opponenent = name + } + } + } + + if team2, ok := result["team2"].(map[string]interface{}); ok { + if name, ok := team2["teamName"].(string); ok { + if name == "VfB Stuttgart" { + match.AwayMatch = true + } else { + match.AwayMatch = false + match.Opponenent = name + } + } + } + matches = append(matches, &match) + } + log.Info("formated matches in json") + return matches, nil +} diff --git a/src/highlanderticketing/config/oauthconf.go b/src/highlanderticketing/config/oauthconf.go new file mode 100644 index 0000000000000000000000000000000000000000..60cc802a1013437dcfdd3cc5fc396e184d35dccc --- /dev/null +++ b/src/highlanderticketing/config/oauthconf.go @@ -0,0 +1,42 @@ +package config + +import ( + "log" + "os" + + "github.com/joho/godotenv" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +func GetOAuthConfigLogin() *oauth2.Config { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("Error loading .env file") + } + return &oauth2.Config{ + ClientID: os.Getenv("CLIENT_ID"), + ClientSecret: os.Getenv("CLIENT_SECRET"), + RedirectURL: "http://localhost:8000/callback/login", + Scopes: []string{ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + }, + Endpoint: google.Endpoint, + } +} + +func GetOAuthConfigRegister() *oauth2.Config { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("Error loading .env file") + } + return &oauth2.Config{ + ClientID: os.Getenv("CLIENT_ID"), + ClientSecret: os.Getenv("CLIENT_SECRET"), + RedirectURL: "http://localhost:8000/callback/register", + Scopes: []string{ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + }, + Endpoint: google.Endpoint, + } +} diff --git a/src/highlanderticketing/db/db.go b/src/highlanderticketing/db/db.go new file mode 100644 index 0000000000000000000000000000000000000000..a2e6500b670b4ef7bc59be021edb2fec238be262 --- /dev/null +++ b/src/highlanderticketing/db/db.go @@ -0,0 +1,60 @@ +package db + +import ( + "context" + "sync" + + log "github.com/sirupsen/logrus" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var ( + clientInstance *mongo.Client + clientInstanceError error + clientOnce sync.Once +) + +const ( + CONNECTIONSTRING = "mongodb://mongo:27017" + DB = "db_issue_manager" + DBUSER = "db_user" + MATCHES = "col_matches" + USERS = "col_users" + USERS_TEST = "col_users_test" + POOL_SIZE = 10000 // Anzahl der Verbindungen im Pool +) + +func GetMongoClient() (*mongo.Client, error) { + clientOnce.Do(func() { + // Erstelle den Verbindungspool + clientOptions := options.Client().ApplyURI(CONNECTIONSTRING) + clientOptions.SetMaxPoolSize(POOL_SIZE) + client, err := mongo.Connect(context.TODO(), clientOptions) + if err != nil { + + clientInstanceError = err + log.Errorf("Failure instancing client %v", err) + } + err = client.Ping(context.TODO(), nil) + if err != nil { + clientInstanceError = err + log.Errorf("Failure pinging client %v", err) + } + clientInstance = client + }) + log.Info("client returned") + return clientInstance, clientInstanceError +} +func CloseMongoClient() error { + if clientInstance != nil { + err := clientInstance.Disconnect(context.Background()) + if err != nil { + log.Errorf("Failure disconnecting client %v", err) + return err + } + } + log.Info("client disconnected") + return nil +} diff --git a/src/highlanderticketing/docker-entrypoint.sh b/src/highlanderticketing/docker-entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..8e1940f55f098449f0d5294e970b18d26ddb4358 --- /dev/null +++ b/src/highlanderticketing/docker-entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# Abort on any error (including if wait-for-it fails). +set -e +# Wait for DB +if [ -n "$DB_CONNECT" ]; then +/go/src/app/wait-for-it.sh "$DB_CONNECT" -t 20 +fi +# Wait for nats +if [ -n "$NATS_CONNECT" ]; then +/go/src/app/wait-for-it.sh "$DB_CONNECT" -t 20 +fi +# Wait for emailnotification +if [ -n "$NATS_CONNECT" ]; then +/go/src/app/wait-for-it.sh "$DB_CONNECT" -t 20 +fi +# Run the main container command. +exec "$@" \ No newline at end of file diff --git a/src/highlanderticketing/go.mod b/src/highlanderticketing/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..442c668bd6498691f7998b87d59e3e00c1b2f548 --- /dev/null +++ b/src/highlanderticketing/go.mod @@ -0,0 +1,39 @@ +module gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing + +go 1.20 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/nats-io/nats.go v1.27.1 + golang.org/x/oauth2 v0.9.0 +) + +require ( + cloud.google.com/go/compute/metadata v0.2.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/klauspost/compress v1.16.5 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/nats-io/nats-server/v2 v2.9.19 // indirect + github.com/nats-io/nkeys v0.4.4 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/net v0.11.0 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect + google.golang.org/appengine v1.6.7 // indirect +) + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gorilla/mux v1.8.0 + github.com/sirupsen/logrus v1.9.3 + go.mongodb.org/mongo-driver v1.11.7 + google.golang.org/protobuf v1.30.0 // indirect +) diff --git a/src/highlanderticketing/go.sum b/src/highlanderticketing/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..ab1e0c32aedf6b58739c90b12d316f49d1775018 --- /dev/null +++ b/src/highlanderticketing/go.sum @@ -0,0 +1,102 @@ +cloud.google.com/go/compute/metadata v0.2.0 h1:nBbNSZyDpkNlo3DepaaLKVuO7ClyifSAmNloSCZrHnQ= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= +github.com/nats-io/nats-server/v2 v2.9.19 h1:OF9jSKZGo425C/FcVVIvNgpd36CUe7aVTTXEZRJk6kA= +github.com/nats-io/nats-server/v2 v2.9.19/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw= +github.com/nats-io/nats.go v1.27.1 h1:OuYnal9aKVSnOzLQIzf7554OXMCG7KbaTkCSBHRcSoo= +github.com/nats-io/nats.go v1.27.1/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= +github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= +github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs= +go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= +golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20210423082822-04245dca01da/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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/check.v1 v1.0.0-20180628173108-788fd7840127/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/highlanderticketing/handler/health.go b/src/highlanderticketing/handler/health.go new file mode 100644 index 0000000000000000000000000000000000000000..61dc8dd4750ec01a3a1c6d24be603c003870ab18 --- /dev/null +++ b/src/highlanderticketing/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") + log.Infof("healthy") + io.WriteString(w, `{"alive": true}`) +} diff --git a/src/highlanderticketing/handler/match.go b/src/highlanderticketing/handler/match.go new file mode 100644 index 0000000000000000000000000000000000000000..3ef79c80c0cba4694cde131765719ea888934fac --- /dev/null +++ b/src/highlanderticketing/handler/match.go @@ -0,0 +1,170 @@ +package handler + +import ( + "encoding/json" + "net/http" + + log "github.com/sirupsen/logrus" + + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/service" +) + +func CreateMatch(w http.ResponseWriter, r *http.Request) { + var match *model.Match + if err, _ := CheckAccessToken(w, r, true); err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + match, err := getMatch(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if err := service.CreateMatch(match); err != nil { + log.Errorf("Error calling service CreateMatch: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sendJson(w, match) +} + +func UpdateMatch(w http.ResponseWriter, r *http.Request) { + if err, _ := CheckAccessToken(w, r, true); err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + id, err := getID(r) + if err != nil { + log.Errorf("Please parse in ID at the url %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + MatchToUpdate, err := getMatch(r) + if err != nil { + log.Errorf("Match not found %v", err) + return + } + MatchUpdated, err := service.UpdateMatch(id, MatchToUpdate) + if err != nil { + log.Errorf("Campaign could not be updated %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + sendJson(w, MatchUpdated) +} + +func GetAllMatches(w http.ResponseWriter, r *http.Request) { + if err, _ := CheckAccessToken(w, r, false); err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + matches, err := service.GetAllMatches() + if err != nil { + log.Errorf("Error calling service GetAllMatches: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sendJson(w, matches) +} + +func GetMatchByID(w http.ResponseWriter, r *http.Request) { + if err, _ := CheckAccessToken(w, r, false); err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + id, err := getID(r) + if err != nil { + log.Errorf("Please parse in ID at the url %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + } + campaign, err := service.GetMatchByID(id) + if err != nil { + log.Errorf("No Match with this ID %v", err) + return + } + sendJson(w, campaign) +} + +func DeleteMatch(w http.ResponseWriter, r *http.Request) { + if err, _ := CheckAccessToken(w, r, true); err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + id, err := getID(r) + if err != nil { + log.Errorf("Please parse in ID at the url %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } else { + log.Infof("ID to delete was found in struct") + } + err1 := service.DeleteMatch(id) + if err1 != nil { + log.Errorf("Match could not be deleted %v", err1) + http.Error(w, err1.Error(), http.StatusInternalServerError) + return + } else { + log.Infof("ID deleted") + log.Tracef("ID: %v deleted", id) + } + sendJson(w, result{Success: "OK"}) +} + +func UpdateTickets(w http.ResponseWriter, r *http.Request) { + if err, _ := CheckAccessToken(w, r, true); err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + id, err := getID(r) + if err != nil { + log.Errorf("Please parse in ID at the url %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + MatchToUpdate, err := getMatch(r) + if err != nil { + log.Errorf("Match not found %v", err) + return + } + MatchUpdated, err := service.UpdateTickets(id, MatchToUpdate) + if err != nil { + log.Errorf("Match could not be updated %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + sendJson(w, MatchUpdated) +} + +// nur intern +func DeleteAllMatches(w http.ResponseWriter, r *http.Request) { + err := service.DeleteAllMatches() + if err != nil { + log.Errorf("Match could not be deleted %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + log.Infof("Matches deleted") + } + sendJson(w, result{Success: "OK"}) +} + +func getMatch(r *http.Request) (*model.Match, error) { + var match *model.Match + err := json.NewDecoder(r.Body).Decode(&match) + if err != nil { + log.Errorf("Can't serialize request body to campaign struct: %v", err) + return nil, err + } else { + log.Infof("request body seralized to campaign struct") + log.Tracef("body seralized in struct campaign: %v", match) + } + return match, nil +} diff --git a/src/highlanderticketing/handler/oauth.go b/src/highlanderticketing/handler/oauth.go new file mode 100644 index 0000000000000000000000000000000000000000..6fbeb9141f4ac0f92160ddba7c1ba6d8e357f1bd --- /dev/null +++ b/src/highlanderticketing/handler/oauth.go @@ -0,0 +1,138 @@ +package handler + +import ( + "context" + "fmt" + "io" + "net/http" + "time" + + log "github.com/sirupsen/logrus" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/config" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/service" + + "github.com/dgrijalva/jwt-go" + "golang.org/x/oauth2" +) + +var secretKey = []byte("mysecretkey") + +// aufgerufen, wenn der benutzer sich ein loggt, ruft oauth conf auf, leitet zu authorisisuerngsseite um +func HandleLogin(w http.ResponseWriter, r *http.Request) { + oauthConfig := config.GetOAuthConfigLogin() + url := oauthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) +} + +// aufgerufen, wenn der benutzer sich registriert, ruft oauth conf auf, leitet zu authorisisuerngsseite um +func HandleRegister(w http.ResponseWriter, r *http.Request) { + oauthConfig := config.GetOAuthConfigRegister() + url := oauthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline) + http.Redirect(w, r, url, http.StatusTemporaryRedirect) +} + +// aufgerufen, wenn autorisierungscode von google kommt, tauscht code gegen access token ein, und registriert den benutzer +func HandleCallbackRegister(w http.ResponseWriter, r *http.Request) { + oauthConfig := config.GetOAuthConfigRegister() + code := r.URL.Query().Get("code") + token, err := oauthConfig.Exchange(context.Background(), code) + if err != nil { + log.Errorf("Failure exchanging authorisingcode: %v", err) + http.Error(w, "Fehler beim Authentifizieren", http.StatusInternalServerError) + return + } + err = service.Register(token.AccessToken) + if err != nil { + log.Errorf("Failure registrating: %v", err) + io.WriteString(w, `user besteht bereits`) + } else { + sendJson(w, "user erfolgreich angelegt") + } + +} + +// aufgerufen, wenn autorisierungscode von google kommt, tauscht code gegen Zugriffstoken ein, generiert jwt token +func HandleCallbackLogin(w http.ResponseWriter, r *http.Request) { + + oauthConfig := config.GetOAuthConfigLogin() + code := r.URL.Query().Get("code") + token, err := oauthConfig.Exchange(context.Background(), code) + if err != nil { + log.Errorf("Failure exchanging authorisingcode: %v", err) + http.Error(w, "Fehler beim Authentifizieren", http.StatusInternalServerError) + return + } + + user, err := service.GetUserInfoByToken(token.AccessToken) + if err != nil { + sendJson(w, err) + return + } + userfound, errUser := service.GetUserByEmail(user.Email) + if errUser != nil { + sendJson(w, err) + sendJson(w, "user nicht registriert") + return + } + + tokenJwt := jwt.New(jwt.SigningMethodHS256) + claims := tokenJwt.Claims.(jwt.MapClaims) + claims["username"] = userfound.Email + claims["exp"] = time.Now().Add(time.Minute * 5).Unix() + + tokenString, err := tokenJwt.SignedString(secretKey) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + log.Info("Login erfolgreich") + sendJson(w, tokenString) +} + +// überprüft den jwt token +func CheckAccessToken(w http.ResponseWriter, r *http.Request, needAdmin bool) (error, string) { + tokenString, err := getBearerToken(r) + if err != nil { + log.Errorf("no bearer token handed over: %v", err) + return err, "" + } + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return secretKey, nil + }) + + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprint(w, "Ungültiges Authorization-Token") + return err, "" + } + var username string + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + username = claims["username"].(string) + } else { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprint(w, "Ungültiges Authorization-Token") + } + if needAdmin { + err := checkAdmin(username) + if err != nil { + return err, "" + } + } + log.Info("token checked succesfully") + return nil, username +} + +func checkAdmin(userEmail string) error { + user, err := service.GetUserByEmail(userEmail) + if err != nil { + log.Errorf("failure getting user by email: %v", err) + return err + } + if user.IsAdmin { + return nil + } else { + return fmt.Errorf("User has not adminrights") + } +} diff --git a/src/highlanderticketing/handler/order.go b/src/highlanderticketing/handler/order.go new file mode 100644 index 0000000000000000000000000000000000000000..a5868eebedb660bbfa00d5005ab5f99487ee563a --- /dev/null +++ b/src/highlanderticketing/handler/order.go @@ -0,0 +1,102 @@ +package handler + +import ( + "encoding/json" + "net/http" + + log "github.com/sirupsen/logrus" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/service" +) + +func AddMatchOrder(w http.ResponseWriter, r *http.Request) { + err, userOfOrder := CheckAccessToken(w, r, false) + if err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + id, err := getID(r) + if err != nil { + log.Errorf("Eror gettin id in request: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + order, err := getOrder(r) + if err != nil { + log.Errorf("Eror gettin order in request: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + internalUser, err := service.GetUserByEmail(userOfOrder) + if err != nil { + log.Errorf("Failure loading internal user Info %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + order.User = *internalUser + err = service.AddMatchOrder(id, order) + if err != nil { + log.Errorf("Failure adding order to match with ID %v: %v", id, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + sendJson(w, order) + +} +func CancelOrder(w http.ResponseWriter, r *http.Request) { + err, userOfOrder := CheckAccessToken(w, r, false) + if err != nil { + log.Errorf("Eror checking AccessToken: %v", err) + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + id, err := getID(r) + if err != nil { + log.Errorf("Eror gettin id in request: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + orderId, err := getOrderID(r) + if err != nil { + log.Errorf("Eror gettin order in request: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + order, err := service.GetOrderById(orderId) + if err != nil { + log.Errorf("Eror order internal: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + internalUser, err := service.GetUserByEmail(userOfOrder) + if err != nil { + log.Errorf("Failure loading internal user Info %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + if order.User != *internalUser { + http.Error(w, "can not cancel order with this user", http.StatusInternalServerError) + sendJson(w, "user is not allowed to cancel this order") + return + } + + err = service.CancelOrder(id, order) + 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, order) +} + +func getOrder(r *http.Request) (*model.Order, error) { + var order model.Order + err := json.NewDecoder(r.Body).Decode(&order) + if err != nil { + log.Errorf("Can't serialize request body to order struct: %v", err) + return nil, err + } else { + log.Infof("request body seralized to order struct") + log.Tracef("body seralized in struct order: %v", order) + } + return &order, nil +} diff --git a/src/highlanderticketing/handler/utils.go b/src/highlanderticketing/handler/utils.go new file mode 100644 index 0000000000000000000000000000000000000000..8304c975d6c2d3b06ccaaf286f003477ebed1de0 --- /dev/null +++ b/src/highlanderticketing/handler/utils.go @@ -0,0 +1,65 @@ +package handler + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +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) (primitive.ObjectID, error) { + vars := mux.Vars(r) + id := vars["id"] + + objectID, err := primitive.ObjectIDFromHex(id) + if err != nil { + log.Errorf("Can't get ObjectID from request: %v", err) + return primitive.NilObjectID, err + } + + return objectID, nil +} +func getOrderID(r *http.Request) (primitive.ObjectID, error) { + vars := mux.Vars(r) + id := vars["orderid"] + + objectID, err := primitive.ObjectIDFromHex(id) + if err != nil { + log.Errorf("Can't get ObjectID from request: %v", err) + return primitive.NilObjectID, err + } + + return objectID, nil +} + +func getBearerToken(r *http.Request) (string, error) { + reqToken := r.Header.Get("Authorization") + if reqToken == "" { + log.Error("no Bearer Token in Request") + return "", fmt.Errorf("Please parse in Bearer Token") + + } + splitToken := strings.Split(reqToken, "Bearer") + if len(splitToken) != 2 { + log.Error("Beaerer Token could not be extracted") + return "", fmt.Errorf("Can not extract Token") + } + + reqToken = strings.TrimSpace(splitToken[1]) + return reqToken, nil +} diff --git a/src/highlanderticketing/main.go b/src/highlanderticketing/main.go new file mode 100644 index 0000000000000000000000000000000000000000..7e829b137531a2857b1657834c8558de9b2741d9 --- /dev/null +++ b/src/highlanderticketing/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "net/http" + "os" + "time" + + "github.com/gorilla/mux" + "github.com/joho/godotenv" + "github.com/nats-io/nats.go" + log "github.com/sirupsen/logrus" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/api" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/db" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/handler" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/service" +) + +func main() { + updateChan := make(chan *model.Match) + service.DeleteAllMatches() + service.DeleteAllUsers() + /* var userArray []model.User + userArray, _ = service.GetAllUsers() + fmt.Println(userArray)*/ + + go func() { + for { + err := api.GetlatestMatchesOfApi("https://api.openligadb.de/getmatchesbyteamid/16/10/0", updateChan) + if err != nil { + log.Println("Fehler beim Abrufen der Matches:", err) + } + time.Sleep(3 * time.Minute) + } + }() + + go func() { + for { + match := <-updateChan + + service.InserExternalMatch(match) + } + }() + + /* + matches, errMatches := api.GetMatchesOfApi("https://api.openligadb.de/getmatchesbyteamid/16/10/0") + if errMatches != nil { + return + } + for _, match := range matches { + service.CreateMatch(match) + } + */ + + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("Error loading .env file") + } + + var natsServer service.NatsServer + + uri := os.Getenv("NATS_URI") + + nc, err := nats.Connect(uri) + if err == nil { + natsServer.Nc = nc + } + if err != nil { + log.Fatal("Error establishing connection to NATS:", err) + } + + log.Println("Starting Highlander Ticketing server") + router := mux.NewRouter() + router.HandleFunc("/register", handler.HandleRegister).Methods("GET") + router.HandleFunc("/callback/register", handler.HandleCallbackRegister).Methods("GET") + router.HandleFunc("/login", handler.HandleLogin).Methods("GET") + router.HandleFunc("/callback/login", handler.HandleCallbackLogin).Methods("GET") + router.HandleFunc("/health", handler.Health).Methods("GET") + router.HandleFunc("/match", handler.CreateMatch).Methods("POST") + router.HandleFunc("/matches", handler.GetAllMatches).Methods("GET") + router.HandleFunc("/match/{id}", handler.GetMatchByID).Methods("GET") + router.HandleFunc("/match/{id}", handler.UpdateMatch).Methods("PUT") + router.HandleFunc("/match/{id}", handler.DeleteMatch).Methods("DELETE") + router.HandleFunc("/match/{id}/updatetickets", handler.UpdateTickets).Methods("PUT") + router.HandleFunc("/match/{id}/matchorder", handler.AddMatchOrder).Methods("POST") + router.HandleFunc("/match/{id}/cancelorder/{orderid}", handler.CancelOrder).Methods("PUT") + if err := http.ListenAndServe(":8000", router); err != nil { + log.Fatal(err) + } + + err = db.CloseMongoClient() + if err != nil { + log.Fatal(err) + } + +} + +func init() { + //init db + _, err := db.GetMongoClient() + if err != nil { + log.Fatal(err) + } + // init logger + log.SetFormatter(&log.TextFormatter{}) + log.SetReportCaller(true) + + os.Setenv("LOG_LEVEL", "INFO") + + 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) +} diff --git a/src/highlanderticketing/model/emailContent.go b/src/highlanderticketing/model/emailContent.go new file mode 100644 index 0000000000000000000000000000000000000000..5a7867cd142ec0802e44c25d342aed04a441285c --- /dev/null +++ b/src/highlanderticketing/model/emailContent.go @@ -0,0 +1,10 @@ +package model + +type EmialContent struct { + OrderID string `json:"orderid"` + Name string `json:"name"` + AwayMatch bool `json:"awaymatch"` + Location string `json:"location"` + Date string `json:"date"` + Emailadress string `json:"emailadress"` +} diff --git a/src/highlanderticketing/model/match.go b/src/highlanderticketing/model/match.go index 82a92f562ffdd3b603cce61fd8389c2c3ed9f058..7c063dd0ae4f401c958eacd0b79658cde7cbc438 100644 --- a/src/highlanderticketing/model/match.go +++ b/src/highlanderticketing/model/match.go @@ -1,17 +1,21 @@ package model -import "google.golang.org/genproto/googleapis/type/date" +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) type Match struct { - ID uint - InitialTicketAmount int32 - AvailableTicketAmount int32 - AwayMatch bool - Location string - Date date.Date - Travel Travel - Orders []Order + ID primitive.ObjectID `bson:"_id, omitempty"` + ExternalID int64 `bson:"externalID"` + Price int32 `bson:"price, omitempty"` + InitialTicketAmount int32 `bson:"initial_ticket_amount"` + AvailableTicketAmount int32 `bson:"available_ticket_amount"` + Opponenent string `bson:"opponent"` + LeagueName string `bson:"league_name"` + AwayMatch bool `bson:"away_match"` + Location string `bson:"location"` + Date time.Time `bson:"date"` + Orders []Order `bson:"orders"` } - -// Funktion ins Modell (siehe -//Myaktion), welche den available_ Ticket_Amount berechnet diff --git a/src/highlanderticketing/model/natsResponse.go b/src/highlanderticketing/model/natsResponse.go new file mode 100644 index 0000000000000000000000000000000000000000..ea4856d8c7a413929becc22fe1eff1dc742d89f7 --- /dev/null +++ b/src/highlanderticketing/model/natsResponse.go @@ -0,0 +1,5 @@ +package model + +type Response struct { + Send bool `json:"send"` +} diff --git a/src/highlanderticketing/model/order.go b/src/highlanderticketing/model/order.go index b4a4fb48b7bf2fa20748d4fd1cd2b0dc2767d1ad..4ccab1876b6cf53b41edc8c6c6e5fa2902a5dcff 100644 --- a/src/highlanderticketing/model/order.go +++ b/src/highlanderticketing/model/order.go @@ -1,13 +1,12 @@ package model +import "go.mongodb.org/mongo-driver/bson/primitive" + type Order struct { - OrderType OrderType - Amount int32 - User User + ID primitive.ObjectID `bson:"_id, omitempty"` + Amount int32 `bson:"amount"` + User User `bson:"user, omitempty"` + Ordernotified bool `bson:"ordernotified, omitempty"` + Canceled bool `bson:"canceled, omitempty"` + Cancelnotified bool `bson:"cancelnotified, omitempty"` } -type OrderType string - -const ( - MATCHTICKET OrderType = "MATCHTICKET" - BUSTICKET OrderType = "BUSTICKET" -) diff --git a/src/highlanderticketing/model/travel.go b/src/highlanderticketing/model/travel.go deleted file mode 100644 index 9b36343aef09d9ac4d1237c4246ca4d3c3aaffcb..0000000000000000000000000000000000000000 --- a/src/highlanderticketing/model/travel.go +++ /dev/null @@ -1,24 +0,0 @@ -package model - -import ( - "google.golang.org/genproto/googleapis/type/date" -) - -type Travel struct { - ID uint - TravelType TravelType - InitialSeatAmount int32 - AvailableSeatAmount int32 - StartLocation string - EndLocation string - StartDate date.Date - Orders []Order -} - -type TravelType string - -const ( - CAR TravelType = "CAR" - BUS TravelType = "BUS" - PLANE TravelType = "PLANE" -) diff --git a/src/highlanderticketing/model/user.go b/src/highlanderticketing/model/user.go index 799191784f96313c91795144ba074ee015ccb67e..1583dceefa58fcf68079367ebfd0b5d40fd48803 100644 --- a/src/highlanderticketing/model/user.go +++ b/src/highlanderticketing/model/user.go @@ -1,7 +1,12 @@ package model +import "go.mongodb.org/mongo-driver/bson/primitive" + type User struct { - FirstName string - LastName string - Role string + ID primitive.ObjectID `bson:"_id,omitempty"` + GoogleID string `json:"id,omitempty" bson:"google_id"` + Email string `json:"email" bson:"email"` + Name string `json:"name" bson:"name"` + FamilyName string `json:"family_name" bson:"family_name"` + IsAdmin bool `json:"is_admin" bson:"is_admin"` } diff --git a/src/highlanderticketing/service/match.go b/src/highlanderticketing/service/match.go new file mode 100644 index 0000000000000000000000000000000000000000..8b1c2c9fbe2ae4658c5a8618ffc72586e739f92a --- /dev/null +++ b/src/highlanderticketing/service/match.go @@ -0,0 +1,223 @@ +package service + +import ( + "context" + "fmt" + + log "github.com/sirupsen/logrus" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/db" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +func CreateMatch(match *model.Match) error { + match.ID = primitive.NewObjectID() + match.Orders = []model.Order{} + client, err := db.GetMongoClient() + if err != nil { + log.Errorf("Eror gettin db client: %v", err) + return err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + + _, err = collection.InsertOne(context.TODO(), match) + if err != nil { + log.Errorf("Erorr inserting match: %v", err) + return nil + } + log.Info("match inserted: %v", match) + return nil +} + +func InserExternalMatch(match *model.Match) error { + match.ID = primitive.NewObjectID() + match.Orders = []model.Order{} + existingMatch := &model.Match{} + filter := bson.M{"externalID": match.ExternalID} + + client, err := db.GetMongoClient() + if err != nil { + log.Errorf("Eror gettin db client: %v", err) + return err + } + + collection := client.Database(db.DB).Collection(db.MATCHES) + err = collection.FindOne(context.TODO(), filter).Decode(existingMatch) + if err == nil { + log.Info("match already exists") + return fmt.Errorf("Match mit ExternalID %d exists already", match.ExternalID) + } else if err != mongo.ErrNoDocuments { + return err + } + _, err = collection.InsertOne(context.TODO(), match) + if err != nil { + return nil + } + log.Info("match inserted: %v", match) + return nil +} + +func UpdateMatch(matchID primitive.ObjectID, match *model.Match) (*model.Match, error) { + result := model.Match{} + + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + + updater := bson.D{primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "initial_ticket_amount", Value: match.InitialTicketAmount}, + primitive.E{Key: "external_id", Value: match.ExternalID}, + primitive.E{Key: "price", Value: match.Price}, + primitive.E{Key: "opponent", Value: match.Opponenent}, + primitive.E{Key: "league_name", Value: match.LeagueName}, + primitive.E{Key: "available_ticket_amount", Value: match.AvailableTicketAmount}, + primitive.E{Key: "away_match", Value: match.AwayMatch}, + primitive.E{Key: "location", Value: match.Location}, + primitive.E{Key: "date", Value: match.Date}, + }}} + + client, err := db.GetMongoClient() + if err != nil { + log.Errorf("Eror gettin db client: %v", err) + return nil, err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + + updateResult, err := collection.UpdateOne(context.TODO(), filter, updater) + if err != nil { + log.Errorf("Erorr getting collection of db: %v", err) + return nil, err + } + + if updateResult.ModifiedCount == 0 { + return nil, fmt.Errorf("no document was updated") + } + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + log.Errorf("Erorr finding match in db: %v", err) + return nil, err + } + log.Info("match updated: %v", match) + return &result, nil +} + +func UpdateTickets(matchID primitive.ObjectID, match *model.Match) (*model.Match, error) { + result := model.Match{} + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + + existingmatch, err := GetMatchByID(matchID) + if err != nil { + log.Errorf("Erorr getting match by id: %v", err) + return &result, err + } + + match.AvailableTicketAmount = existingmatch.AvailableTicketAmount + match.InitialTicketAmount + match.InitialTicketAmount = existingmatch.InitialTicketAmount + match.InitialTicketAmount + + updater := bson.D{primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "initial_ticket_amount", Value: match.InitialTicketAmount}, + primitive.E{Key: "price", Value: match.Price}, + primitive.E{Key: "available_ticket_amount", Value: match.AvailableTicketAmount}, + }}} + + client, err := db.GetMongoClient() + if err != nil { + log.Errorf("Eror gettin db client: %v", err) + return nil, err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + + updateResult, err := collection.UpdateOne(context.TODO(), filter, updater) + if err != nil { + log.Errorf("Erorr getting collection of db: %v", err) + return nil, err + } + + if updateResult.ModifiedCount == 0 { + return nil, fmt.Errorf("no document was updated") + } + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + return nil, err + } + log.Info("tickets updated: %v", match) + return &result, nil +} + +func GetAllMatches() ([]model.Match, error) { + filter := bson.D{{}} + matches := []model.Match{} + + client, err := db.GetMongoClient() + if err != nil { + return matches, err + } + + collection := client.Database(db.DB).Collection(db.MATCHES) + cur, err := collection.Find(context.TODO(), filter) + if err != nil { + return matches, err + } + defer cur.Close(context.TODO()) + + for cur.Next(context.TODO()) { + var match model.Match + if err := cur.Decode(&match); err != nil { + return matches, err + } + matches = append(matches, match) + } + + if len(matches) == 0 { + return matches, mongo.ErrNoDocuments + } + + return matches, nil +} + +func GetMatchByID(matchID primitive.ObjectID) (*model.Match, error) { + result := model.Match{} + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + + client, err := db.GetMongoClient() + if err != nil { + return &result, err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + return &result, err + } + return &result, nil +} + +func DeleteMatch(matchID primitive.ObjectID) error { + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + client, err := db.GetMongoClient() + if err != nil { + return err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + _, err = collection.DeleteOne(context.TODO(), filter) + if err != nil { + return err + } + return nil +} + +func DeleteAllMatches() error { + selector := bson.D{{}} + client, err := db.GetMongoClient() + if err != nil { + return err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + _, err = collection.DeleteMany(context.TODO(), selector) + if err != nil { + return err + } + return nil +} diff --git a/src/highlanderticketing/service/nats.go b/src/highlanderticketing/service/nats.go new file mode 100644 index 0000000000000000000000000000000000000000..8ad15824451e471f8937c00f21fc8f48ad11d4a3 --- /dev/null +++ b/src/highlanderticketing/service/nats.go @@ -0,0 +1,80 @@ +package service + +import ( + "encoding/json" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/nats-io/nats.go" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" +) + +type NatsServer struct { + Nc *nats.Conn +} + +func ConnectToNats() (NatsServer, error) { + var natsServer NatsServer + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("Error loading .env file") + } + uri := os.Getenv("NATS_URI") + nc, err := nats.Connect(uri) + if err != nil { + log.Fatal("Error establishing connection to NATS:", err) + return natsServer, err + } + natsServer.Nc = nc + fmt.Println("Connected to NATS at:", natsServer.Nc.ConnectedUrl()) + return natsServer, nil + +} + +func (s NatsServer) ConfirmOrder(e *model.EmialContent) (error, bool) { + var res *model.Response + emailContenct, errMarshal := json.Marshal(e) + if errMarshal != nil { + fmt.Println(errMarshal) + return fmt.Errorf(errMarshal.Error()), false + } + response, err := s.Nc.Request("confirmOrder."+string(e.OrderID), []byte(emailContenct), 2*time.Second) + if err != nil { + log.Println("Error making NATS request:", err) + return fmt.Errorf(err.Error()), false + } + + if err := json.Unmarshal(response.Data, &res); err != nil { + return fmt.Errorf(err.Error()), false + } + if res.Send != true { + return fmt.Errorf("emain not succesfuly send"), false + } + fmt.Println("hier die nats response", *res) + return nil, true +} + +func (s NatsServer) ConfirmCancel(e *model.EmialContent) (error, bool) { + var res *model.Response + emailContenct, errMarshal := json.Marshal(e) + if errMarshal != nil { + fmt.Println(errMarshal) + return fmt.Errorf(errMarshal.Error()), false + } + response, err := s.Nc.Request("confirmCancel."+string(e.OrderID), []byte(emailContenct), 2*time.Second) + if err != nil { + log.Println("Error making NATS request:", err) + return fmt.Errorf(err.Error()), false + } + + if err := json.Unmarshal(response.Data, &res); err != nil { + return fmt.Errorf(err.Error()), false + } + if res.Send != true { + return fmt.Errorf("emain not succesfuly send"), false + } + fmt.Println("hier die nats response", *res) + return nil, true +} diff --git a/src/highlanderticketing/service/oauth.go b/src/highlanderticketing/service/oauth.go new file mode 100644 index 0000000000000000000000000000000000000000..7c7167cd00990d27e84efa5b3c51716edf32f210 --- /dev/null +++ b/src/highlanderticketing/service/oauth.go @@ -0,0 +1,78 @@ +package service + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" +) + +func ValidateGoogleAccessToken(accessToken string) (bool, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token="+accessToken, nil) + if err != nil { + return false, err + } + + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, err + } + + var tokenInfo struct { + ExpiresIn int `json:"expires_in"` + Error string `json:"error"` + } + + err = json.Unmarshal(body, &tokenInfo) + if err != nil { + return false, err + } + + if tokenInfo.Error != "" { + return false, fmt.Errorf("Fehler bei der Überprüfung des Tokens: %s", tokenInfo.Error) + } + //fmt.Println(tokenInfo.ExpiresIn) + + if tokenInfo.ExpiresIn > 0 { + return true, nil + } + + return false, nil +} + +func GetUserInfoByToken(accessToken string) (model.User, error) { + var userInfo model.User + client := &http.Client{} + req, err := http.NewRequest("GET", "https://www.googleapis.com/oauth2/v1/userinfo", nil) + if err != nil { + return userInfo, err + } + + req.Header.Set("Authorization", "Bearer "+accessToken) + + resp, err := client.Do(req) + if err != nil { + return userInfo, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return userInfo, err + } + err = json.Unmarshal(body, &userInfo) + if err != nil { + return userInfo, err + } + + return userInfo, nil +} diff --git a/src/highlanderticketing/service/order.go b/src/highlanderticketing/service/order.go new file mode 100644 index 0000000000000000000000000000000000000000..09100c0abefb6981708dc8b9223a63a055b577a7 --- /dev/null +++ b/src/highlanderticketing/service/order.go @@ -0,0 +1,243 @@ +package service + +import ( + "context" + "errors" + "fmt" + "time" + + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/db" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func AddMatchOrder(matchID primitive.ObjectID, order *model.Order) error { + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + order.ID = primitive.NewObjectID() + matchToFind := &model.Match{} + emailContent := model.EmialContent{Name: order.User.Name, AwayMatch: matchToFind.AwayMatch, Location: matchToFind.Location, Date: matchToFind.Date.String(), Emailadress: order.User.Email, OrderID: matchToFind.ID.String()} + + updater := bson.D{primitive.E{Key: "$push", Value: bson.D{ + primitive.E{Key: "orders", Value: order}, + }}} + + updaterNotification := bson.D{primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "orders.$[element]", Value: order}, + }}} + + options := options.Update().SetArrayFilters(options.ArrayFilters{ + Filters: []interface{}{ + bson.D{{Key: "element._id", Value: order.ID}}, + }, + }) + + client, err := db.GetMongoClient() + if err != nil { + return err + } + + collection := client.Database(db.DB).Collection(db.MATCHES) + // find match + err = collection.FindOne(context.TODO(), filter).Decode(&matchToFind) + if err != nil { + return err + } + if matchToFind.AvailableTicketAmount < order.Amount { + return fmt.Errorf("ticket amount not available") + } else { + matchToFind.AvailableTicketAmount = matchToFind.AvailableTicketAmount - order.Amount + } + // push order + updateResult, err := collection.UpdateOne(context.TODO(), filter, updater) + if err != nil { + return err + } + if updateResult.ModifiedCount == 0 { + return fmt.Errorf("no document was updated, please send order again") + } + // update match with new available ticketamount + _, errUpdate := UpdateMatch(matchToFind.ID, matchToFind) + if errUpdate != nil { + errUpdate = fmt.Errorf("can not update match amount, please send order again") + err := deleteOrder(order.ID, matchToFind.ID) + natsServer, err := ConnectToNats() + if err != nil { + return err + } + // send email cancel notification + defer natsServer.Nc.Close() + order.Cancelnotified = true + if err, _ := natsServer.ConfirmCancel(&emailContent); err != nil { + time.Sleep(1 * time.Second) + err, _ := natsServer.ConfirmCancel(&emailContent) + if err != nil { + order.Ordernotified = false + err = fmt.Errorf("error sending cancel email: %v", err) + } + return err + } + } + + natsServer, err := ConnectToNats() + if err != nil { + return err + } + + defer natsServer.Nc.Close() + // send confirm email + if err, _ := natsServer.ConfirmOrder(&emailContent); err != nil { + time.Sleep(1 * time.Second) + err, _ := natsServer.ConfirmOrder(&emailContent) + if err != nil { + order.Ordernotified = false + err = fmt.Errorf("error sending confirm email: %v", err) + } + return err + } else { + order.Ordernotified = true + } + // update order notification to true + updateNotification, err := collection.UpdateOne(context.TODO(), filter, updaterNotification, options) + if err != nil { + err = fmt.Errorf("no document was updated, please send order again") + return err + } + + if updateNotification.ModifiedCount == 0 { + err = fmt.Errorf("notification was not updated") + return err + } + + return nil +} + +func CancelOrder(matchID primitive.ObjectID, order *model.Order) error { + if order.Canceled == true { + return fmt.Errorf("order already canceled") + } + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + // find match with id + matchToFind, err := GetMatchByID(matchID) + if err != nil { + return err + } else { + matchToFind.AvailableTicketAmount = matchToFind.AvailableTicketAmount + order.Amount + } + + client, err := db.GetMongoClient() + if err != nil { + return err + } + + collection := client.Database(db.DB).Collection(db.MATCHES) + + order.Canceled = true + + updaterMatchCancel := bson.D{primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "orders.$[element]", Value: order}, + }}} + + options := options.Update().SetArrayFilters(options.ArrayFilters{ + Filters: []interface{}{ + bson.D{{Key: "element._id", Value: order.ID}}, + }, + }) + // update order to canceled + updateMatchCancel, err := collection.UpdateOne(context.TODO(), filter, updaterMatchCancel, options) + if err != nil { + return err + } + if updateMatchCancel.ModifiedCount == 0 { + return fmt.Errorf("not updated") + + } + // update match with new available tickets + _, errUpdateAmount := UpdateMatch(matchToFind.ID, matchToFind) + if errUpdateAmount != nil { + order.Canceled = false + updateMatchCancel, err := collection.UpdateOne(context.TODO(), filter, updaterMatchCancel, options) + if err != nil { + return err + } + if updateMatchCancel.ModifiedCount == 0 { + return fmt.Errorf("not updated") + + } + return fmt.Errorf("error canceling match internal, please try again %v", err) + } + + natsServer, err := ConnectToNats() + defer natsServer.Nc.Close() + // send notification mail + emailContent := model.EmialContent{Name: order.User.Name, AwayMatch: matchToFind.AwayMatch, Location: matchToFind.Location, Date: matchToFind.Date.String(), Emailadress: order.User.Email, OrderID: order.ID.String()} + if err, _ := natsServer.ConfirmCancel(&emailContent); err != nil { + time.Sleep(1 * time.Second) + err, _ := natsServer.ConfirmOrder(&emailContent) + if err != nil { + order.Ordernotified = false + err = fmt.Errorf("error sending confirm email: %v", err) + } + } else { + order.Cancelnotified = true + } + // update cancel notification + updateMatchCancelNotifi, err := collection.UpdateOne(context.TODO(), filter, updaterMatchCancel, options) + if err != nil { + return err + } + if updateMatchCancelNotifi.ModifiedCount == 0 { + return fmt.Errorf("orderid not in system") + } + return nil + +} + +func GetOrderById(orderID primitive.ObjectID) (*model.Order, error) { + client, err := db.GetMongoClient() + if err != nil { + return nil, err + } + collection := client.Database(db.DB).Collection(db.MATCHES) + + filter := bson.M{"orders._id": orderID} + + var result model.Match + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + return nil, err + } + + for _, order := range result.Orders { + if order.ID == orderID { + return &order, nil + } + } + + return nil, errors.New("Order not found") +} + +func deleteOrder(matchID primitive.ObjectID, orderID primitive.ObjectID) error { + filter := bson.D{primitive.E{Key: "_id", Value: matchID}} + updater := bson.D{primitive.E{Key: "$pull", Value: bson.D{ + primitive.E{Key: "orders", Value: bson.D{ + primitive.E{Key: "_id", Value: orderID}, + }}, + }}} + + client, err := db.GetMongoClient() + if err != nil { + return err + } + + collection := client.Database(db.DB).Collection(db.MATCHES) + + _, err = collection.UpdateOne(context.TODO(), filter, updater) + if err != nil { + return err + } + + return nil +} diff --git a/src/highlanderticketing/service/register.go b/src/highlanderticketing/service/register.go new file mode 100644 index 0000000000000000000000000000000000000000..40ca19207a832a6848a6c5e7cf2f95d74df4c019 --- /dev/null +++ b/src/highlanderticketing/service/register.go @@ -0,0 +1,22 @@ +package service + +import ( + "fmt" + + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" +) + +func Register(accessToken string) error { + user, err := GetUserInfoByToken(accessToken) + if err != nil { + return err + } + err1 := CreateUser(&user) + if err1 != nil { + return err1 + } + var userArray []model.User + userArray, _ = GetAllUsers() + fmt.Println(userArray) + return nil +} diff --git a/src/highlanderticketing/service/user.go b/src/highlanderticketing/service/user.go new file mode 100644 index 0000000000000000000000000000000000000000..c9c703c7e5d9411b5d621439b9dc073935dad4ea --- /dev/null +++ b/src/highlanderticketing/service/user.go @@ -0,0 +1,195 @@ +package service + +import ( + "context" + "fmt" + + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/db" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var isFirstCall = true + +func CreateUser(user *model.User) error { + if isFirstCall == false { + user.IsAdmin = false + } else if isFirstCall == true { + _, err := GetAllUsers() + if err == mongo.ErrNoDocuments { + user.IsAdmin = true + } else if err != nil { + return err + } else { + user.IsAdmin = false + } + isFirstCall = false + } + user.ID = primitive.NewObjectID() + client, err := db.GetMongoClient() + if err != nil { + return err + } + filter := bson.M{"email": user.Email} + update := bson.M{ + "$setOnInsert": bson.M{ + "_id": user.ID, + "email": user.Email, + "name": user.Name, + "family_name": user.FamilyName, + "is_admin": user.IsAdmin, + }, + } + + collection := client.Database(db.DB).Collection(db.USERS) + options := options.FindOneAndUpdate().SetUpsert(true) + + result := collection.FindOneAndUpdate(context.TODO(), filter, update, options) + + if result.Err() == mongo.ErrNoDocuments { + return nil // dokument wurd erstellt + } else if result.Err() != nil { + return result.Err() // fehler beim process an sich + } else { + return fmt.Errorf("Der Benutzer existiert bereits") + } +} + +func UpdateUser(userID primitive.ObjectID, user *model.User) (*model.User, error) { + result := model.User{} + existingUser, err := GetUserByID(userID) + if existingUser == nil || err != nil { + return existingUser, err + } + + filter := bson.D{primitive.E{Key: "_id", Value: userID}} + + updater := bson.D{primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "email", Value: user.Email}, + primitive.E{Key: "name", Value: user.Name}, + primitive.E{Key: "family_name", Value: user.FamilyName}, + primitive.E{Key: "is_admin", Value: user.IsAdmin}, + }}} + + client, err := db.GetMongoClient() + if err != nil { + return nil, err + } + + collection := client.Database(db.DB).Collection(db.MATCHES) + + updateResult, err := collection.UpdateOne(context.TODO(), filter, updater) + if err != nil { + return nil, err + } + + if updateResult.ModifiedCount == 0 { + return nil, fmt.Errorf("no document was updated") + } + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func GetAllUsers() ([]model.User, error) { + filter := bson.D{{}} + users := []model.User{} + + client, err := db.GetMongoClient() + if err != nil { + return users, err + } + + collection := client.Database(db.DB).Collection(db.USERS) + cur, err := collection.Find(context.TODO(), filter) + if err != nil { + return users, err + } + defer cur.Close(context.TODO()) + + for cur.Next(context.TODO()) { + var user model.User + if err := cur.Decode(&user); err != nil { + return users, err + } + users = append(users, user) + } + + if len(users) == 0 { + return users, mongo.ErrNoDocuments + } + + return users, nil +} + +func GetUserByID(userID primitive.ObjectID) (*model.User, error) { + result := model.User{} + filter := bson.D{primitive.E{Key: "_id", Value: userID}} + + client, err := db.GetMongoClient() + if err != nil { + return &result, err + } + + collection := client.Database(db.DB).Collection(db.USERS) + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + return &result, err + } + return &result, nil +} +func GetUserByEmail(email string) (*model.User, error) { + result := model.User{} + filter := bson.D{primitive.E{Key: "email", Value: email}} + + client, err := db.GetMongoClient() + if err != nil { + return &result, err + } + + collection := client.Database(db.DB).Collection(db.USERS) + + err = collection.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + return &result, err + } + return &result, nil +} + +func DeleteUser(UserID primitive.ObjectID) error { + filter := bson.D{primitive.E{Key: "_id", Value: UserID}} + client, err := db.GetMongoClient() + if err != nil { + return err + } + + collection := client.Database(db.DB).Collection(db.USERS) + _, err = collection.DeleteOne(context.TODO(), filter) + if err != nil { + return err + } + return nil +} + +func DeleteAllUsers() error { + selector := bson.D{{}} + client, err := db.GetMongoClient() + if err != nil { + return err + } + + collection := client.Database(db.DB).Collection(db.USERS) + _, err = collection.DeleteMany(context.TODO(), selector) + if err != nil { + return err + } + return nil +} diff --git a/src/highlanderticketing/service/user_test.go b/src/highlanderticketing/service/user_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d3c4404abb4239a6f09e347f522a68317d0be6db --- /dev/null +++ b/src/highlanderticketing/service/user_test.go @@ -0,0 +1,39 @@ +package service_test + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/model" + "gitlab.reutlingen-university.de/ege/highlander-ticketing-go-ss2023/src/highlanderticketing/service" +) + +func TestCreateUserIntegration(t *testing.T) { + user := &model.User{ + Email: "test@example.com", + Name: "John", + FamilyName: "Doe", + IsAdmin: false, + } + + // Testfall 1: der benutzer wird angelegt + err2 := service.CreateUser(user) + + assert.Nil(t, err2) + assert.False(t, user.IsAdmin) + + if !reflect.DeepEqual(t, user) { + t.Errorf("Expected %+v, but got %+v", t, err2) + } + + // Testfall 2: es gibt bereits den Benutzer + err3 := service.CreateUser(user) + + assert.Error(t, err3) + assert.Equal(t, "Der Benutzer existiert bereits", err3.Error()) + + if !reflect.DeepEqual(err2, user) { + t.Errorf("Expected %+v, but got %+v", t, err2) + } +} diff --git a/src/highlanderticketing/wait-for-it.sh b/src/highlanderticketing/wait-for-it.sh new file mode 100644 index 0000000000000000000000000000000000000000..3974640b053e6f84a21c292c01e3674348445831 --- /dev/null +++ b/src/highlanderticketing/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 \ No newline at end of file diff --git a/todo b/todo new file mode 100644 index 0000000000000000000000000000000000000000..6e6c4125f6086c02060056cd323745a22d60c0d2 --- /dev/null +++ b/todo @@ -0,0 +1,11 @@ + - handler , order , travel + - user werden aus google oauth2 erzeugt (Plan) + - services von user travel order + - context definieren + - schauen wann verbindung zu mongodb unterbrechen + - docker von api + - reihenfolge von den docker files (shell von schmollinger verwenden) + - google oauth2 anbinden + - schauen wie thread save machen (evtl. mit abfrage von api) + - api anbinden +