From a61e75c4d85bbfca3ef23f8ee92e337e5b501cc5 Mon Sep 17 00:00:00 2001 From: Janik <10290002+led0nk@users.noreply.github.com> Date: Sun, 21 Apr 2024 00:42:09 +0200 Subject: [PATCH] traces/spans setup ctx feat: traces + spans v1 update getentrybysnippet --- Makefile | 3 + api/v1/authentication.go | 222 ++++++++++++++++--- api/v1/server.go | 163 ++++++++++++-- cmd/server/main.go | 25 +++ go.mod | 26 ++- go.sum | 42 ++++ internal/database/database.go | 36 +-- internal/database/jsondb/entryhandler.go | 50 ++++- internal/database/jsondb/userhandler.go | 77 ++++++- internal/database/jsondb/userhandler_test.go | 4 +- internal/middleware/middleware.go | 35 ++- token/token.go | 43 +++- 12 files changed, 637 insertions(+), 89 deletions(-) diff --git a/Makefile b/Makefile index dc9199d..89ce3b9 100644 --- a/Makefile +++ b/Makefile @@ -49,3 +49,6 @@ build: run: gofmt build ./bin/main +.PHONY: gorun +gorun: + go run cmd/server/main.go diff --git a/api/v1/authentication.go b/api/v1/authentication.go index 9232342..264391d 100644 --- a/api/v1/authentication.go +++ b/api/v1/authentication.go @@ -12,32 +12,53 @@ import ( "github.com/led0nk/guestbook/cmd/utils" "github.com/led0nk/guestbook/internal/database/jsondb" "github.com/led0nk/guestbook/internal/model" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "golang.org/x/crypto/bcrypt" ) func (s *Server) passwordReset() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.passwordReset") + defer span.End() + userID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - user, err := s.userstore.GetUserByID(userID) + user, err := s.userstore.GetUserByID(ctx, userID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } newPW := utils.RandomString(8) user.Password = []byte(newPW) - hashedpassword, _ := bcrypt.GenerateFromPassword([]byte(newPW), 14) + hashedpassword, err := bcrypt.GenerateFromPassword([]byte(newPW), 14) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("password")).Msg(err.Error()) + return + } err = s.mailer.SendPWMail(user, s.templates) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("mailer")).Msg(err.Error()) return } user.Password = hashedpassword - err = s.userstore.UpdateUser(user) + err = s.userstore.UpdateUser(ctx, user) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -47,21 +68,32 @@ func (s *Server) passwordReset() http.HandlerFunc { // login authentication and check if user exists func (s *Server) loginAuth() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.loginAuth") + defer span.End() + email := r.FormValue("email") - user, err := s.userstore.GetUserByEmail(email) + user, err := s.userstore.GetUserByEmail(ctx, email) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) http.Redirect(w, r, "/login", http.StatusFound) return } if err := bcrypt.CompareHashAndPassword(user.Password, []byte(r.FormValue("password"))); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("hashing")).Msg(err.Error()) w.WriteHeader(http.StatusUnauthorized) return } - cookie, err := s.tokenstore.CreateToken("session", user.ID, utils.FormValueBool(r.FormValue("Rememberme"))) + cookie, err := s.tokenstore.CreateToken(ctx, "session", user.ID, utils.FormValueBool(r.FormValue("Rememberme"))) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("token")).Msg(err.Error()) return } @@ -77,19 +109,36 @@ func (s *Server) loginAuth() http.HandlerFunc { // logoutAuth and deleting session-cookie func (s *Server) logoutAuth() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.logoutAuth") + defer span.End() + cookie, err := r.Cookie("session") if err != nil { switch { case errors.Is(err, http.ErrNoCookie): + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) http.Error(w, "cookie not found", http.StatusBadRequest) default: + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("server")).Msg(err.Error()) http.Error(w, "server error", http.StatusInternalServerError) } } - userID, _ := s.tokenstore.GetTokenValue(cookie) - err = s.tokenstore.DeleteToken(userID) + userID, err := s.tokenstore.GetTokenValue(ctx, cookie) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("token")).Msg(err.Error()) + return + } + err = s.tokenstore.DeleteToken(ctx, userID) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("token")).Msg(err.Error()) return } @@ -102,19 +151,34 @@ func (s *Server) logoutAuth() http.HandlerFunc { // signup authentication and validation of user input func (s *Server) signupAuth() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.signupAuth") + defer span.End() + err := r.ParseForm() if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("request")).Msg(err.Error()) return } err = jsondb.ValidateUserInput(r.Form) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) http.Redirect(w, r, "/signup", http.StatusFound) return } joinedName := strings.Join([]string{utils.Capitalize(r.FormValue("firstname")), utils.Capitalize(r.FormValue("lastname"))}, " ") - hashedpassword, _ := bcrypt.GenerateFromPassword([]byte(r.Form.Get("password")), 14) + hashedpassword, err := bcrypt.GenerateFromPassword([]byte(r.Form.Get("password")), 14) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("password")).Msg(err.Error()) + return + } newUser := model.User{ Email: html.EscapeString(r.FormValue("email")), Name: html.EscapeString(joinedName), @@ -124,8 +188,10 @@ func (s *Server) signupAuth() http.HandlerFunc { VerificationCode: utils.RandomString(6), ExpirationTime: time.Now().Add(time.Minute * 5), } - _, usererr := s.userstore.CreateUser(&newUser) - if usererr != nil { + _, err = s.userstore.CreateUser(ctx, &newUser) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) http.Redirect(w, r, "/signup", http.StatusFound) w.WriteHeader(http.StatusUnauthorized) @@ -133,6 +199,8 @@ func (s *Server) signupAuth() http.HandlerFunc { err = s.mailer.SendVerMail(&newUser, s.templates) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("mailer")).Msg(err.Error()) return } @@ -142,24 +210,41 @@ func (s *Server) signupAuth() http.HandlerFunc { func (s *Server) verifyAuth() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.verifyAuth") + defer span.End() + err := r.ParseForm() if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("request")).Msg(err.Error()) return } - session, _ := r.Cookie("session") - userID, err := s.tokenstore.GetTokenValue(session) + session, err := r.Cookie("session") + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("cookie")).Msg(err.Error()) + return + } + userID, err := s.tokenstore.GetTokenValue(ctx, session) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("token")).Msg(err.Error()) return } - ok, err := s.userstore.CodeValidation(userID, r.FormValue("code")) + ok, err := s.userstore.CodeValidation(ctx, userID, r.FormValue("code")) if !ok { http.Redirect(w, r, "/user/verify", http.StatusFound) s.log.Err(errors.New("user")).Msg(err.Error()) return } if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -169,36 +254,54 @@ func (s *Server) verifyAuth() http.HandlerFunc { func (s *Server) deleteUser() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.deleteUser") + defer span.End() ID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - err = s.userstore.DeleteUser(ID) + err = s.userstore.DeleteUser(ctx, ID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } - // http.Redirect(w, r, "/admin/dashboard", http.StatusFound) } } // TODO: User Template with input Form for editing func (s *Server) updateUser() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.updateUser") + defer span.End() + userID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - user, err := s.userstore.GetUserByID(userID) + user, err := s.userstore.GetUserByID(ctx, userID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } err = s.templates.TmplAdminUser.ExecuteTemplate(w, "user-update", &user) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } @@ -208,19 +311,30 @@ func (s *Server) updateUser() http.HandlerFunc { // save updated User data func (s *Server) saveUser() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.saveUser") + defer span.End() + err := r.ParseForm() if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("request")).Msg(err.Error()) return } userID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - user, err := s.userstore.GetUserByID(userID) + user, err := s.userstore.GetUserByID(ctx, userID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -235,13 +349,17 @@ func (s *Server) saveUser() http.HandlerFunc { VerificationCode: user.VerificationCode, ExpirationTime: user.ExpirationTime, } - err = s.userstore.UpdateUser(&updatedUser) + err = s.userstore.UpdateUser(ctx, &updatedUser) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } err = s.templates.TmplAdminUser.ExecuteTemplate(w, "user", &updatedUser) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } @@ -250,13 +368,22 @@ func (s *Server) saveUser() http.HandlerFunc { func (s *Server) resendVer() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.resendVer") + defer span.End() + userID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - user, err := s.userstore.GetUserByID(userID) + user, err := s.userstore.GetUserByID(ctx, userID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -264,16 +391,22 @@ func (s *Server) resendVer() http.HandlerFunc { user.ExpirationTime = time.Now().Add(time.Minute * 5) err = s.mailer.SendVerMail(user, s.templates) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("mailer")).Msg(err.Error()) return } - err = s.userstore.UpdateUser(user) + err = s.userstore.UpdateUser(ctx, user) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } err = s.templates.TmplAdminUser.ExecuteTemplate(w, "user", &user) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } @@ -282,8 +415,15 @@ func (s *Server) resendVer() http.HandlerFunc { func (s *Server) forgotPW() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - user, err := s.userstore.GetUserByEmail(r.FormValue("email")) + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.resendVer") + defer span.End() + + user, err := s.userstore.GetUserByEmail(ctx, r.FormValue("email")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -292,12 +432,16 @@ func (s *Server) forgotPW() http.HandlerFunc { hashedpassword, _ := bcrypt.GenerateFromPassword([]byte(newPW), 14) err = s.mailer.SendPWMail(user, s.templates) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("mailer")).Msg(err.Error()) return } user.Password = hashedpassword - err = s.userstore.UpdateUser(user) + err = s.userstore.UpdateUser(ctx, user) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -307,10 +451,23 @@ func (s *Server) forgotPW() http.HandlerFunc { func (s *Server) search() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.search") + defer span.End() + userName := r.URL.Query().Get("name") - entry, _ := s.bookstore.GetEntryBySnippet(userName) - err := s.templates.TmplSearchResult.ExecuteTemplate(w, "result", &entry) + entry, err := s.bookstore.GetEntryBySnippet(ctx, userName) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("user")).Msg(err.Error()) + return + } + err = s.templates.TmplSearchResult.ExecuteTemplate(w, "result", &entry) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } @@ -319,13 +476,22 @@ func (s *Server) search() http.HandlerFunc { func (s *Server) submitUserData() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.submitUserData") + defer span.End() + userID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - user, err := s.userstore.GetUserByID(userID) + user, err := s.userstore.GetUserByID(ctx, userID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } @@ -339,13 +505,17 @@ func (s *Server) submitUserData() http.HandlerFunc { VerificationCode: user.VerificationCode, ExpirationTime: user.ExpirationTime, } - err = s.userstore.UpdateUser(&updatedUser) + err = s.userstore.UpdateUser(ctx, &updatedUser) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } err = s.templates.TmplDashboardUser.ExecuteTemplate(w, "user", &updatedUser) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } diff --git a/api/v1/server.go b/api/v1/server.go index 8aafe38..48fc3cd 100644 --- a/api/v1/server.go +++ b/api/v1/server.go @@ -12,7 +12,9 @@ import ( "github.com/led0nk/guestbook/internal/middleware" "github.com/led0nk/guestbook/internal/model" "github.com/rs/zerolog" + "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) @@ -57,6 +59,7 @@ func (s *Server) ServeHTTP() { authMiddleware.Use(middleware.Auth(s.tokenstore, s.log)) adminMiddleware.Use(middleware.AdminAuth(s.tokenstore, s.userstore, s.log)) router.Use(middleware.Logger(s.log)) + router.Use(otelmux.Middleware("guestbook")) router.PathPrefix("/user").Handler(authMiddleware) router.PathPrefix("/admin").Handler(adminMiddleware) // routing @@ -87,9 +90,9 @@ func (s *Server) ServeHTTP() { adminMiddleware.HandleFunc("/dashboard/{ID}", s.saveUser()).Methods(http.MethodPut) adminMiddleware.HandleFunc("/dashboard/{ID}/verify", s.resendVer()).Methods(http.MethodPut) adminMiddleware.HandleFunc("/dashboard/{ID}/password-reset", s.passwordReset()).Methods(http.MethodPut) - // log.Info("listening to: ") + //TODO: implement in main log.Info("listening to: ") - s.log.Info().Str("listening to", "").Msg("") + s.log.Info().Str("listening to", s.addr).Msg("") srv := &http.Server{ Addr: s.addr, @@ -108,12 +111,17 @@ func (s *Server) handlePage() http.HandlerFunc { ctx := r.Context() ctx, span = tracer.Start(ctx, "server.ListEntries") defer span.End() + entries, err := s.bookstore.ListEntries(ctx) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Error().Err(errors.New("entry")).Msg(err.Error()) } err = s.templates.TmplHome.Execute(w, &entries) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Error().Err(errors.New("template")).Msg(err.Error()) return } @@ -127,13 +135,18 @@ func (s *Server) searchHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ctx, span = tracer.Start(ctx, "server.searchHandler") defer span.End() + entries, err := s.bookstore.ListEntries(ctx) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } err = s.templates.TmplSearch.Execute(w, entries) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } @@ -141,8 +154,15 @@ func (s *Server) searchHandler(w http.ResponseWriter, r *http.Request) { // show login Form func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + _, span = tracer.Start(ctx, "server.loginHandler") + defer span.End() + err := s.templates.TmplLogin.Execute(w, nil) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -151,8 +171,15 @@ func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) { // show signup Form func (s *Server) signupHandler(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + _, span = tracer.Start(ctx, "server.signupHandler") + defer span.End() + err := s.templates.TmplSignUp.Execute(w, nil) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -161,13 +188,48 @@ func (s *Server) signupHandler(w http.ResponseWriter, r *http.Request) { func (s *Server) dashboardHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - session, _ := r.Cookie("session") - tokenValue, _ := s.tokenstore.GetTokenValue(session) - user, _ := s.userstore.GetUserByID(tokenValue) - user.Entry, _ = s.bookstore.GetEntryByID(tokenValue) + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.dashboardHandler") + defer span.End() - err := s.templates.TmplDashboard.Execute(w, user) + session, err := r.Cookie("session") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + w.WriteHeader(http.StatusBadGateway) + s.log.Err(errors.New("cookie")).Msg(err.Error()) + return + } + tokenValue, err := s.tokenstore.GetTokenValue(ctx, session) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + w.WriteHeader(http.StatusBadGateway) + s.log.Err(errors.New("token")).Msg(err.Error()) + return + } + user, err := s.userstore.GetUserByID(ctx, tokenValue) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + w.WriteHeader(http.StatusBadGateway) + s.log.Err(errors.New("user")).Msg(err.Error()) + return + } + user.Entry, err = s.bookstore.GetEntryByID(ctx, tokenValue) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + w.WriteHeader(http.StatusBadGateway) + s.log.Err(errors.New("entry")).Msg(err.Error()) + return + } + + err = s.templates.TmplDashboard.Execute(w, user) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -177,8 +239,15 @@ func (s *Server) dashboardHandler() http.HandlerFunc { } func (s *Server) createHandler(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + _, span = tracer.Start(ctx, "server.createHandler") + defer span.End() + err := s.templates.TmplCreate.Execute(w, nil) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -186,8 +255,15 @@ func (s *Server) createHandler(w http.ResponseWriter, r *http.Request) { } func (s *Server) verifyHandler(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + _, span = tracer.Start(ctx, "server.verifyHandler") + defer span.End() + err := s.templates.TmplVerification.Execute(w, nil) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -195,9 +271,23 @@ func (s *Server) verifyHandler(w http.ResponseWriter, r *http.Request) { } func (s *Server) adminHandler(w http.ResponseWriter, r *http.Request) { - users, _ := s.userstore.ListUser() - err := s.templates.TmplAdmin.Execute(w, &users) + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.adminHandler") + defer span.End() + + users, err := s.userstore.ListUser(ctx) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + w.WriteHeader(http.StatusBadRequest) + s.log.Err(errors.New("user")).Msg(err.Error()) + return + } + err = s.templates.TmplAdmin.Execute(w, &users) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -205,8 +295,15 @@ func (s *Server) adminHandler(w http.ResponseWriter, r *http.Request) { } func (s *Server) forgotHandler(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + _, span = tracer.Start(ctx, "server.forgotHandler") + defer span.End() + err := s.templates.TmplForgot.Execute(w, nil) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) w.WriteHeader(http.StatusBadGateway) s.log.Err(errors.New("template")).Msg(err.Error()) return @@ -215,18 +312,45 @@ func (s *Server) forgotHandler(w http.ResponseWriter, r *http.Request) { func (s *Server) createEntry() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.createEntry") + defer span.End() + err := r.ParseForm() if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("request")).Msg(err.Error()) return } - session, _ := r.Cookie("session") - userID, _ := s.tokenstore.GetTokenValue(session) - user, _ := s.userstore.GetUserByID(userID) + session, err := r.Cookie("session") + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("cookie")).Msg(err.Error()) + return + } + userID, err := s.tokenstore.GetTokenValue(ctx, session) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("token")).Msg(err.Error()) + return + } + user, err := s.userstore.GetUserByID(ctx, userID) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + s.log.Err(errors.New("user")).Msg(err.Error()) + return + } newEntry := model.GuestbookEntry{Name: user.Name, Message: html.EscapeString(r.FormValue("message")), UserID: user.ID} - _, err = s.bookstore.CreateEntry(&newEntry) + _, err = s.bookstore.CreateEntry(ctx, &newEntry) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("entry")).Msg(err.Error()) return } @@ -236,18 +360,29 @@ func (s *Server) createEntry() http.HandlerFunc { func (s *Server) changeUserData() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "server.changeUserData") + defer span.End() + userID, err := uuid.Parse(mux.Vars(r)["ID"]) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("uuid")).Msg(err.Error()) return } - user, err := s.userstore.GetUserByID(userID) + user, err := s.userstore.GetUserByID(ctx, userID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("user")).Msg(err.Error()) return } err = s.templates.TmplDashboardUser.ExecuteTemplate(w, "user-update", user) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) s.log.Err(errors.New("template")).Msg(err.Error()) return } diff --git a/cmd/server/main.go b/cmd/server/main.go index 1a54556..b93de41 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "net/url" @@ -16,6 +17,11 @@ import ( "github.com/led0nk/guestbook/internal/mailer" "github.com/led0nk/guestbook/token" "github.com/rs/zerolog" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func main() { @@ -77,6 +83,25 @@ func main() { panic("bad mailer env") } + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + grpcOptions := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()} + conn, err := grpc.NewClient("localhost:4317", grpcOptions...) + if err != nil { + logger.Error().Err(err).Msg("") + os.Exit(1) + } + defer conn.Close() + + otelexporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) + if err != nil { + logger.Error().Err(err).Msg("") + os.Exit(1) + } + tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(otelexporter)) + otel.SetTracerProvider(tp) + //in memory tokenStorage, err := token.CreateTokenService(os.Getenv("TOKENSECRET")) if err != nil { diff --git a/go.mod b/go.mod index f8ea90c..aaa2e01 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,36 @@ go 1.21 require ( github.com/golang-jwt/jwt/v5 v5.2.0 - github.com/google/uuid v1.5.0 + github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.32.0 - go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/trace v1.24.0 - golang.org/x/crypto v0.17.0 + go.opentelemetry.io/otel v1.25.0 + go.opentelemetry.io/otel/trace v1.25.0 + golang.org/x/crypto v0.21.0 ) require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/contrib v1.25.0 // indirect + go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.50.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.25.0 // indirect + go.opentelemetry.io/otel/sdk v1.25.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/grpc v1.63.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index fef6adf..827e4fa 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,10 @@ +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -13,8 +17,12 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -30,18 +38,52 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.opentelemetry.io/contrib v1.25.0 h1:lVXfJCcanfNuRN4kJU6+mWToeqXYQWzmpHFSsR+fAi4= +go.opentelemetry.io/contrib v1.25.0/go.mod h1:Tmhw9grdWtmXy6DxZNpIAudzYJqLeEM2P6QTZQSRwU8= +go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.50.0 h1:nJ1MmncpeXRrEWvMQ/3Bkx4+PQAolni/YgZR6RKzOqE= +go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.50.0/go.mod h1:fa/XbNPmX60nZLu/vaFahoZoqZbewhZEhsvDjhc6d+4= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 h1:vOL89uRfOCCNIjkisd0r7SEdJF3ZJFyCNY34fdZs8eU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= +go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= +go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= +google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/internal/database/database.go b/internal/database/database.go index 6f70346..fa82910 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -10,29 +10,29 @@ import ( ) type GuestBookStore interface { - CreateEntry(*model.GuestbookEntry) (uuid.UUID, error) + CreateEntry(context.Context, *model.GuestbookEntry) (uuid.UUID, error) ListEntries(context.Context) ([]*model.GuestbookEntry, error) - DeleteEntry(uuid.UUID) error - GetEntryByName(string) ([]*model.GuestbookEntry, error) - GetEntryByID(uuid.UUID) ([]*model.GuestbookEntry, error) - GetEntryBySnippet(string) ([]*model.GuestbookEntry, error) + DeleteEntry(context.Context, uuid.UUID) error + GetEntryByName(context.Context, string) ([]*model.GuestbookEntry, error) + GetEntryByID(context.Context, uuid.UUID) ([]*model.GuestbookEntry, error) + GetEntryBySnippet(context.Context, string) ([]*model.GuestbookEntry, error) } type UserStore interface { - CreateUser(*model.User) (uuid.UUID, error) - GetUserByEmail(string) (*model.User, error) - GetUserByID(uuid.UUID) (*model.User, error) - UpdateUser(*model.User) error - CreateVerificationCode(uuid.UUID) error - CodeValidation(uuid.UUID, string) (bool, error) - ListUser() ([]*model.User, error) - DeleteUser(uuid.UUID) error + CreateUser(context.Context, *model.User) (uuid.UUID, error) + GetUserByEmail(context.Context, string) (*model.User, error) + GetUserByID(context.Context, uuid.UUID) (*model.User, error) + UpdateUser(context.Context, *model.User) error + CreateVerificationCode(context.Context, uuid.UUID) error + CodeValidation(context.Context, uuid.UUID, string) (bool, error) + ListUser(context.Context) ([]*model.User, error) + DeleteUser(context.Context, uuid.UUID) error } type TokenStore interface { - CreateToken(string, uuid.UUID, bool) (*http.Cookie, error) - DeleteToken(uuid.UUID) error - GetTokenValue(*http.Cookie) (uuid.UUID, error) - Valid(string) (bool, error) - Refresh(string) (*http.Cookie, error) + CreateToken(context.Context, string, uuid.UUID, bool) (*http.Cookie, error) + DeleteToken(context.Context, uuid.UUID) error + GetTokenValue(context.Context, *http.Cookie) (uuid.UUID, error) + Valid(context.Context, string) (bool, error) + Refresh(context.Context, string) (*http.Cookie, error) } diff --git a/internal/database/jsondb/entryhandler.go b/internal/database/jsondb/entryhandler.go index c4655b6..1565239 100644 --- a/internal/database/jsondb/entryhandler.go +++ b/internal/database/jsondb/entryhandler.go @@ -38,9 +38,16 @@ func CreateBookStorage(filename string) (*BookStorage, error) { } // create new entry in GuestStorage -func (b *BookStorage) CreateEntry(entry *model.GuestbookEntry) (uuid.UUID, error) { +func (b *BookStorage) CreateEntry(ctx context.Context, entry *model.GuestbookEntry) (uuid.UUID, error) { + var span trace.Span + _, span = tracer.Start(ctx, "CreateEntry") + defer span.End() + + span.AddEvent("Lock") b.mu.Lock() + defer span.AddEvent("Unlock") defer b.mu.Unlock() + if entry.ID == uuid.Nil { entry.ID = uuid.New() } @@ -108,9 +115,16 @@ func (b *BookStorage) readJSON() error { } // delete Entry from storage and write to JSON -func (b *BookStorage) DeleteEntry(entryID uuid.UUID) error { +func (b *BookStorage) DeleteEntry(ctx context.Context, entryID uuid.UUID) error { + var span trace.Span + _, span = tracer.Start(ctx, "DeleteEntry") + defer span.End() + + span.AddEvent("Lock") b.mu.Lock() + defer span.AddEvent("Unlock") defer b.mu.Unlock() + if entryID == uuid.Nil { return errors.New("requires an entryID") } @@ -119,6 +133,7 @@ func (b *BookStorage) DeleteEntry(entryID uuid.UUID) error { return err } + span.AddEvent("delete entry from entrylist") delete(b.entries, entryID) if err := b.writeJSON(); err != nil { @@ -128,14 +143,22 @@ func (b *BookStorage) DeleteEntry(entryID uuid.UUID) error { return nil } -func (b *BookStorage) GetEntryByName(name string) ([]*model.GuestbookEntry, error) { +func (b *BookStorage) GetEntryByName(ctx context.Context, name string) ([]*model.GuestbookEntry, error) { + var span trace.Span + _, span = tracer.Start(ctx, "GetEntryByName") + defer span.End() + + span.AddEvent("Lock") b.mu.Lock() + defer span.AddEvent("Unlock") defer b.mu.Unlock() + if name == "" { return nil, errors.New("requires a name") } entries := []*model.GuestbookEntry{} + span.AddEvent("create entry slice") for _, entry := range b.entries { if entry.Name == name { entries = append(entries, entry) @@ -145,18 +168,27 @@ func (b *BookStorage) GetEntryByName(name string) ([]*model.GuestbookEntry, erro return nil, errors.New("no entries found for " + name) } + span.AddEvent("sort slice") sort.Slice(entries, func(i, j int) bool { return entries[i].CreatedAt > entries[j].CreatedAt }) return entries, nil } -func (b *BookStorage) GetEntryByID(id uuid.UUID) ([]*model.GuestbookEntry, error) { +func (b *BookStorage) GetEntryByID(ctx context.Context, id uuid.UUID) ([]*model.GuestbookEntry, error) { + var span trace.Span + _, span = tracer.Start(ctx, "GetEntryByID") + defer span.End() + + span.AddEvent("Lock") b.mu.Lock() + defer span.AddEvent("Unlock") defer b.mu.Unlock() + if id == uuid.Nil { return nil, errors.New("requires a uuid") } entries := []*model.GuestbookEntry{} + span.AddEvent("create slice by entryID") for _, entry := range b.entries { if entry.UserID == id { entries = append(entries, entry) @@ -169,11 +201,18 @@ func (b *BookStorage) GetEntryByID(id uuid.UUID) ([]*model.GuestbookEntry, error return entries, nil } -func (b *BookStorage) GetEntryBySnippet(snippet string) ([]*model.GuestbookEntry, error) { +func (b *BookStorage) GetEntryBySnippet(ctx context.Context, snippet string) ([]*model.GuestbookEntry, error) { + var span trace.Span + _, span = tracer.Start(ctx, "GetEntryBySnippet") + defer span.End() + + span.AddEvent("Lock") b.mu.Lock() + defer span.AddEvent("Unlock") defer b.mu.Unlock() entries := []*model.GuestbookEntry{} + span.AddEvent("check entries for snippet") for _, entry := range b.entries { if strings.Contains(entry.Name, snippet) { entries = append(entries, entry) @@ -183,6 +222,7 @@ func (b *BookStorage) GetEntryBySnippet(snippet string) ([]*model.GuestbookEntry return nil, errors.New("no entries found for " + snippet) } + span.AddEvent("sort entry slice") sort.Slice(entries, func(i, j int) bool { return entries[i].CreatedAt > entries[j].CreatedAt }) return entries, nil } diff --git a/internal/database/jsondb/userhandler.go b/internal/database/jsondb/userhandler.go index 10eb9ba..1c528b0 100644 --- a/internal/database/jsondb/userhandler.go +++ b/internal/database/jsondb/userhandler.go @@ -1,6 +1,7 @@ package jsondb import ( + "context" "encoding/json" "errors" "net/mail" @@ -14,6 +15,7 @@ import ( "github.com/google/uuid" "github.com/led0nk/guestbook/cmd/utils" "github.com/led0nk/guestbook/internal/model" + "go.opentelemetry.io/otel/trace" ) type UserStorage struct { @@ -50,6 +52,7 @@ func (u *UserStorage) writeUserJSON() error { // read JSON data from file = filename func (u *UserStorage) readUserJSON() error { + if _, err := os.Stat(u.filename); os.IsNotExist(err) { return errors.New("file does not exist") } @@ -60,8 +63,14 @@ func (u *UserStorage) readUserJSON() error { return json.Unmarshal(data, &u.user) } -func (u *UserStorage) CreateUser(user *model.User) (uuid.UUID, error) { +func (u *UserStorage) CreateUser(ctx context.Context, user *model.User) (uuid.UUID, error) { + var span trace.Span + _, span = tracer.Start(ctx, "CreateUser") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() if user.ID == uuid.Nil { user.ID = uuid.New() @@ -75,9 +84,16 @@ func (u *UserStorage) CreateUser(user *model.User) (uuid.UUID, error) { return user.ID, nil } -func (u *UserStorage) ListUser() ([]*model.User, error) { +func (u *UserStorage) ListUser(ctx context.Context) ([]*model.User, error) { + var span trace.Span + _, span = tracer.Start(ctx, "ListUser") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() + userlist := make([]*model.User, 0, len(u.user)) for _, user := range u.user { userlist = append(userlist, user) @@ -87,8 +103,14 @@ func (u *UserStorage) ListUser() ([]*model.User, error) { } // maybe only for expired ExpTime and Reverification -func (u *UserStorage) CreateVerificationCode(userID uuid.UUID) error { +func (u *UserStorage) CreateVerificationCode(ctx context.Context, userID uuid.UUID) error { + var span trace.Span + _, span = tracer.Start(ctx, "CreateVerificationCode") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() if userID == uuid.Nil { @@ -99,8 +121,14 @@ func (u *UserStorage) CreateVerificationCode(userID uuid.UUID) error { return nil } -func (u *UserStorage) UpdateUser(user *model.User) error { +func (u *UserStorage) UpdateUser(ctx context.Context, user *model.User) error { + var span trace.Span + _, span = tracer.Start(ctx, "UpdateUser") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() u.user[user.ID] = user @@ -110,14 +138,21 @@ func (u *UserStorage) UpdateUser(user *model.User) error { return nil } -func (u *UserStorage) GetUserByEmail(email string) (*model.User, error) { +func (u *UserStorage) GetUserByEmail(ctx context.Context, email string) (*model.User, error) { + var span trace.Span + _, span = tracer.Start(ctx, "GetUserByEmail") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() if email == "" { return nil, errors.New("requires an email input") } users := &model.User{} + span.AddEvent("range over user") for _, user := range u.user { if user.Email == email { users = user @@ -126,13 +161,20 @@ func (u *UserStorage) GetUserByEmail(email string) (*model.User, error) { return users, nil } -func (u *UserStorage) GetUserByID(ID uuid.UUID) (*model.User, error) { +func (u *UserStorage) GetUserByID(ctx context.Context, ID uuid.UUID) (*model.User, error) { + var span trace.Span + _, span = tracer.Start(ctx, "GetUserByID") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() if ID == uuid.Nil { return nil, errors.New("UUID empty") } users := &model.User{} + span.AddEvent("range over user") for _, user := range u.user { if user.ID == ID { users = user @@ -166,13 +208,18 @@ func ValidateUserInput(v url.Values) error { return nil } -func (u *UserStorage) CodeValidation(ID uuid.UUID, code string) (bool, error) { - user, err := u.GetUserByID(ID) +func (u *UserStorage) CodeValidation(ctx context.Context, ID uuid.UUID, code string) (bool, error) { + var span trace.Span + ctx, span = tracer.Start(ctx, "CodeValidation") + defer span.End() + + span.AddEvent("get user") + user, err := u.GetUserByID(ctx, ID) if err != nil { return false, err } if !time.Now().Before(user.ExpirationTime) { - err := u.DeleteUser(ID) + err := u.DeleteUser(ctx, ID) if err != nil { return false, err } @@ -182,7 +229,8 @@ func (u *UserStorage) CodeValidation(ID uuid.UUID, code string) (bool, error) { return false, errors.New("Wrong Verification Code") } user.IsVerified = true - err = u.UpdateUser(user) + span.AddEvent("update user") + err = u.UpdateUser(ctx, user) if err != nil { return false, err } @@ -190,8 +238,14 @@ func (u *UserStorage) CodeValidation(ID uuid.UUID, code string) (bool, error) { } // delete Entry from storage and write to JSON -func (u *UserStorage) DeleteUser(ID uuid.UUID) error { +func (u *UserStorage) DeleteUser(ctx context.Context, ID uuid.UUID) error { + var span trace.Span + _, span = tracer.Start(ctx, "DeleteUser") + defer span.End() + + span.AddEvent("Lock") u.mu.Lock() + defer span.AddEvent("Unlock") defer u.mu.Unlock() if ID == uuid.Nil { return errors.New("requires an userID") @@ -203,6 +257,7 @@ func (u *UserStorage) DeleteUser(ID uuid.UUID) error { delete(u.user, ID) + span.AddEvent("delete user from json") if err := u.writeUserJSON(); err != nil { return err } diff --git a/internal/database/jsondb/userhandler_test.go b/internal/database/jsondb/userhandler_test.go index 27f4763..a1df4b7 100644 --- a/internal/database/jsondb/userhandler_test.go +++ b/internal/database/jsondb/userhandler_test.go @@ -1,6 +1,7 @@ package jsondb_test import ( + "context" "encoding/json" "errors" "net/url" @@ -87,6 +88,7 @@ func TestValidateUserInput(t *testing.T) { func TestCreateUser(t *testing.T) { filename := "test_users.json" + ctx := context.Background() //testUser := &model.User{ // Name: "Jon Doe", @@ -114,7 +116,7 @@ func TestCreateUser(t *testing.T) { } // Create the user - id, err := storage.CreateUser(user) + id, err := storage.CreateUser(ctx, user) if err != nil { t.Fatalf("Error creating user: %v", err) } diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index f9d92e5..1c2b824 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -8,8 +8,13 @@ import ( "github.com/gorilla/mux" db "github.com/led0nk/guestbook/internal/database" "github.com/rs/zerolog" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) +var tracer = otel.GetTracerProvider().Tracer("github.com/led0nk/guestbook/internal/middleware") + type ResponseRecorder struct { http.ResponseWriter StatusCode int @@ -39,22 +44,33 @@ func Logger(logger zerolog.Logger) mux.MiddlewareFunc { func Auth(t db.TokenStore, logger zerolog.Logger) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "middleware.Auth") + defer span.End() + session, err := r.Cookie("session") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) logger.Err(errors.New("cookie")).Msg(err.Error()) http.Redirect(w, r, "/login", http.StatusFound) return } - isValid, err := t.Valid(session.Value) + isValid, err := t.Valid(ctx, session.Value) if !isValid { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) logger.Err(errors.New("token")).Msg(err.Error()) http.Redirect(w, r, "/login", http.StatusFound) return } - cookie, err := t.Refresh(session.Value) + cookie, err := t.Refresh(ctx, session.Value) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) logger.Err(errors.New("token")).Msg(err.Error()) http.Redirect(w, r, "/", http.StatusFound) return @@ -70,22 +86,33 @@ func Auth(t db.TokenStore, logger zerolog.Logger) mux.MiddlewareFunc { func AdminAuth(t db.TokenStore, u db.UserStore, logger zerolog.Logger) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var span trace.Span + ctx := r.Context() + ctx, span = tracer.Start(ctx, "middleware.AdminAuth") + defer span.End() + session, err := r.Cookie("session") if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) logger.Err(errors.New("cookie")).Msg(err.Error()) http.Redirect(w, r, "/login", http.StatusFound) return } - isValid, err := t.Valid(session.Value) + isValid, err := t.Valid(ctx, session.Value) if !isValid { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) logger.Err(errors.New("token")).Msg(err.Error()) http.Redirect(w, r, "/login", http.StatusFound) return } - cookie, err := t.Refresh(session.Value) + cookie, err := t.Refresh(ctx, session.Value) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) logger.Err(errors.New("token")).Msg(err.Error()) http.Redirect(w, r, "/", http.StatusFound) return diff --git a/token/token.go b/token/token.go index 5702356..c2497b1 100644 --- a/token/token.go +++ b/token/token.go @@ -1,6 +1,7 @@ package token import ( + "context" "errors" "net/http" "sync" @@ -8,8 +9,12 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" ) +var tracer = otel.GetTracerProvider().Tracer("github.com/led0nk/guestbook/token") + type Token struct { Token string Expiration time.Time @@ -29,16 +34,24 @@ func CreateTokenService(secret string) (*TokenStorage, error) { return tokenService, nil } -func (t *TokenStorage) CreateToken(session string, ID uuid.UUID, remember bool) (*http.Cookie, error) { +func (t *TokenStorage) CreateToken(ctx context.Context, session string, ID uuid.UUID, remember bool) (*http.Cookie, error) { + var span trace.Span + _, span = tracer.Start(ctx, "CreateToken") + defer span.End() + + span.AddEvent("Lock") t.mu.Lock() + defer span.AddEvent("Unlock") defer t.mu.Unlock() if ID == uuid.Nil { return nil, errors.New("Cannot create Token for empty User ID") } + span.AddEvent("create token") token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": ID.String(), }) + span.AddEvent("sign token") tokenString, _ := token.SignedString([]byte(t.Secret)) //exchange to osenv later expiration := time.Now().Add(15 * time.Minute) tokenStruct := &Token{ @@ -63,8 +76,14 @@ func (t *TokenStorage) CreateToken(session string, ID uuid.UUID, remember bool) return &cookie, nil } -func (t *TokenStorage) DeleteToken(ID uuid.UUID) error { +func (t *TokenStorage) DeleteToken(ctx context.Context, ID uuid.UUID) error { + var span trace.Span + _, span = tracer.Start(ctx, "DeleteToken") + defer span.End() + + span.AddEvent("Lock") t.mu.Lock() + defer span.AddEvent("Unlock") defer t.mu.Unlock() if ID == uuid.Nil { return errors.New("Cannot delete Token for empty User ID") @@ -73,11 +92,16 @@ func (t *TokenStorage) DeleteToken(ID uuid.UUID) error { if _, exists := t.Tokens[ID]; !exists { return errors.New("there is no token existing for this ID") } + span.AddEvent("delete Token") delete(t.Tokens, ID) return nil } -func (t *TokenStorage) GetTokenValue(c *http.Cookie) (uuid.UUID, error) { +func (t *TokenStorage) GetTokenValue(ctx context.Context, c *http.Cookie) (uuid.UUID, error) { + var span trace.Span + _, span = tracer.Start(ctx, "GetTokenValue") + defer span.End() + tokenString := c.Value claims := jwt.MapClaims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { @@ -88,13 +112,18 @@ func (t *TokenStorage) GetTokenValue(c *http.Cookie) (uuid.UUID, error) { } tokenClaims, _ := token.Claims.(jwt.MapClaims) valueString := tokenClaims["id"].(string) + span.AddEvent("parse to uuid") tokenValue, _ := uuid.Parse(valueString) return tokenValue, nil } -func (t *TokenStorage) Valid(val string) (bool, error) { +func (t *TokenStorage) Valid(ctx context.Context, val string) (bool, error) { + var span trace.Span + _, span = tracer.Start(ctx, "Valid") + defer span.End() + span.AddEvent("range over tokens") for _, token := range t.Tokens { if token.Token == val { @@ -108,12 +137,16 @@ func (t *TokenStorage) Valid(val string) (bool, error) { return false, errors.New("Token was not found") } -func (t *TokenStorage) Refresh(val string) (*http.Cookie, error) { +func (t *TokenStorage) Refresh(ctx context.Context, val string) (*http.Cookie, error) { + var span trace.Span + _, span = tracer.Start(ctx, "Refresh") + defer span.End() if val == "" { return nil, errors.New("refresh failed, empty value") } + span.AddEvent("set cookie values") cookie := http.Cookie{ Name: "session", Value: val,