summaryrefslogtreecommitdiff
path: root/idvoc-2025/CommentsEngine
diff options
context:
space:
mode:
Diffstat (limited to 'idvoc-2025/CommentsEngine')
-rw-r--r--idvoc-2025/CommentsEngine/.gitignore1
-rw-r--r--idvoc-2025/CommentsEngine/Dockerfile22
-rw-r--r--idvoc-2025/CommentsEngine/README.md5
-rw-r--r--idvoc-2025/CommentsEngine/go.mod22
-rw-r--r--idvoc-2025/CommentsEngine/go.sum34
-rw-r--r--idvoc-2025/CommentsEngine/main.go128
-rw-r--r--idvoc-2025/CommentsEngine/redisc/redis.go83
7 files changed, 295 insertions, 0 deletions
diff --git a/idvoc-2025/CommentsEngine/.gitignore b/idvoc-2025/CommentsEngine/.gitignore
new file mode 100644
index 0000000..a18b076
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/.gitignore
@@ -0,0 +1 @@
+CommentsEngine
diff --git a/idvoc-2025/CommentsEngine/Dockerfile b/idvoc-2025/CommentsEngine/Dockerfile
new file mode 100644
index 0000000..b57c112
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/Dockerfile
@@ -0,0 +1,22 @@
+FROM reg.undercloud.cri.epita.fr/docker/golang:1.24.3-alpine
+
+LABEL org.opencontainers.image.authors="martial.simon@epita.fr"
+
+RUN apk --no-cache add curl bash
+
+WORKDIR /usr/local/app
+
+COPY go.mod go.sum . ./
+
+# Setup an app user so the container doesn't run as the root user
+RUN go install && adduser -D app
+USER app
+
+EXPOSE 8000
+
+ENV REDIS_ENDPOINT="redis:6379"
+ENV HOST="0.0.0.0"
+ENV PORT=8000
+
+# Run the app when starting a container
+CMD ["CommentsEngine"]
diff --git a/idvoc-2025/CommentsEngine/README.md b/idvoc-2025/CommentsEngine/README.md
new file mode 100644
index 0000000..4381739
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/README.md
@@ -0,0 +1,5 @@
+## Env variables
+
+`REDIS_ENDPOINT`: The endpoint (host + port) on which redis can be contacted
+`HOST`: The host on which to bind+listen to
+`PORT`: The port on which to bind+listen to
diff --git a/idvoc-2025/CommentsEngine/go.mod b/idvoc-2025/CommentsEngine/go.mod
new file mode 100644
index 0000000..a2d1f24
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/go.mod
@@ -0,0 +1,22 @@
+module CommentsEngine
+
+go 1.21.5
+
+require (
+ github.com/gorilla/handlers v1.5.2
+ github.com/redis/go-redis/v9 v9.3.1
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/felixge/httpsnoop v1.0.3 // indirect
+ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
+ github.com/prometheus/client_golang v1.18.0 // indirect
+ github.com/prometheus/client_model v0.5.0 // indirect
+ github.com/prometheus/common v0.45.0 // indirect
+ github.com/prometheus/procfs v0.12.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/idvoc-2025/CommentsEngine/go.sum b/idvoc-2025/CommentsEngine/go.sum
new file mode 100644
index 0000000..fd15fe6
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/go.sum
@@ -0,0 +1,34 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
+github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
+github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
+github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git a/idvoc-2025/CommentsEngine/main.go b/idvoc-2025/CommentsEngine/main.go
new file mode 100644
index 0000000..9fb8105
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/main.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "CommentsEngine/redisc"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/gorilla/handlers"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "github.com/redis/go-redis/v9"
+ "io"
+ "log"
+ "net/http"
+ "os"
+)
+
+func getenv(key, fallback string) string {
+ value := os.Getenv(key)
+ if len(value) == 0 {
+ return fallback
+ }
+ return value
+}
+
+var (
+ httpHits = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "comments_engine_http_hits_total",
+ Help: "The total number of hits on a given route",
+ }, []string{"route", "method"})
+ commentsReceived = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "comments_engine_comments_received_total",
+ Help: "The total number of comments received",
+ })
+ errorsSent = promauto.NewCounter(prometheus.CounterOpts{
+ Name: "comments_interactor_errors_sent_total",
+ Help: "The total number of errors returned",
+ })
+)
+
+func getRoot(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/", "method": r.Method}).Inc()
+ io.WriteString(w, "CommentsEngine answering here !\n")
+}
+
+func sendError(w http.ResponseWriter, err error, additionalInfo string) {
+ errorsSent.Inc()
+ w.WriteHeader(http.StatusInternalServerError)
+ resp := make(map[string]string)
+ resp["message"] = fmt.Sprint(err)
+ resp["additionalInfo"] = additionalInfo
+ jsonResp, err := json.Marshal(resp)
+ if err != nil {
+ log.Fatalf("Error happened in JSON marshal. Err: %s", err)
+ }
+ w.Write(jsonResp)
+}
+
+func sendMessage(w http.ResponseWriter, message string) {
+ commentsReceived.Inc()
+ resp := make(map[string]string)
+ resp["message"] = message
+ jsonResp, err := json.Marshal(resp)
+ if err != nil {
+ log.Fatalf("Error happened in JSON marshal. Err: %s", err)
+ }
+ w.Write(jsonResp)
+}
+
+func getLatestComments(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/latest", "method": r.Method}).Inc()
+ w.Header().Set("Content-Type", "application/json")
+ messages, err := redisc.GetLatestComments()
+ if err == nil {
+ io.WriteString(w, messages)
+ } else {
+ sendError(w, err, "Maybe there is a problem with redis backend ?")
+ }
+}
+
+type Comment struct {
+ Comment string `json:"comment"`
+}
+
+func sendComment(w http.ResponseWriter, r *http.Request) {
+ httpHits.With(prometheus.Labels{"route": "/comment", "method": r.Method}).Inc()
+ w.Header().Set("Content-Type", "application/json")
+ if r.Method != http.MethodPost {
+ sendError(w, errors.New("POST not used"), "HTTP method was not POST")
+ return
+ }
+ comment := &Comment{}
+ err := json.NewDecoder(r.Body).Decode(comment)
+ if err != nil {
+ sendError(w, err, "The data sent was likely incorrect")
+ return
+ }
+ _, err = redisc.StoreComment(comment.Comment)
+ if err != nil {
+ sendError(w, err, "Problem with redis maybe ?")
+ return
+ }
+ sendMessage(w, "stored !")
+}
+
+func main() {
+ http.HandleFunc("/", getRoot)
+ http.HandleFunc("/latest", getLatestComments)
+ http.HandleFunc("/comment", sendComment)
+ http.Handle("/metrics", promhttp.Handler())
+
+ redisc.Client = redis.NewClient(&redis.Options{
+ Addr: getenv("REDIS_ENDPOINT", "localhost:6379"),
+ Password: "", // no password set
+ DB: 0, // use default DB
+ })
+
+ listeningHostPort := getenv("HOST", "127.0.0.1") + ":" + getenv("PORT", "8000")
+ fmt.Printf("Starting the server on %s\n", listeningHostPort)
+ err := http.ListenAndServe(listeningHostPort, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux))
+ if errors.Is(err, http.ErrServerClosed) {
+ fmt.Printf("server closed\n")
+ } else if err != nil {
+ fmt.Printf("error starting server: %s\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/idvoc-2025/CommentsEngine/redisc/redis.go b/idvoc-2025/CommentsEngine/redisc/redis.go
new file mode 100644
index 0000000..88d9a08
--- /dev/null
+++ b/idvoc-2025/CommentsEngine/redisc/redis.go
@@ -0,0 +1,83 @@
+package redisc
+
+import (
+ "context"
+ "encoding/json"
+ "github.com/redis/go-redis/v9"
+ "log"
+ "strconv"
+)
+
+var Client *redis.Client
+
+func getIndex(ctx context.Context) (int, error) {
+ val, err := Client.Get(ctx, "index").Result()
+ var i int
+ if err != nil && err != redis.Nil {
+ log.Printf("Problem with redis: %s", err)
+ return -1, err
+ } else if err == redis.Nil {
+ i = 0
+ } else {
+ i, err = strconv.Atoi(val)
+ if err != nil {
+ log.Printf("Problem in stored data: %s", err)
+ return -1, err
+ }
+ }
+ return i, nil
+}
+
+func getIndexAndBump(ctx context.Context) (int, error) {
+ i, err := getIndex(ctx)
+ if err != nil {
+ return -1, err
+ }
+ err = Client.Set(ctx, "index", strconv.Itoa(i+1), 0).Err()
+ if err != nil {
+ log.Printf("Problem with redis while trying to store data: %s", err)
+ return -1, err
+ }
+ return i + 1, err
+}
+
+func StoreComment(comment string) (int, error) {
+ ctx := context.Background()
+ i, err := getIndexAndBump(ctx)
+ if err != nil {
+ return -1, err
+ }
+ err = Client.Set(ctx, "comment"+strconv.Itoa(i), comment, 0).Err()
+ if err != nil {
+ log.Printf("Problem with redis while trying to store data: %s", err)
+ return -1, err
+ }
+ return i, err
+}
+
+func GetLatestComments() (string, error) {
+ messages := make([]string, 0)
+ ctx := context.Background()
+ index, err := getIndex(ctx)
+ if err != nil {
+ return "", err
+ }
+ for i := 0; i < 10; i++ {
+ if index-i <= 0 {
+ break
+ }
+ val, err := Client.Get(ctx, "comment"+strconv.Itoa(index-i)).Result()
+ if err != nil && err != redis.Nil {
+ log.Printf("Problem with redis while trying to retrieve data: %s", err)
+ return "", err
+ } else if err == redis.Nil {
+ continue
+ }
+ messages = append(messages, val)
+ }
+ msg, err := json.Marshal(messages)
+ if err != nil {
+ log.Fatalf("Json issue")
+ }
+ return string(msg), err
+}