From fc8b8881984658c30b2987a278c92b01b85ef0ff Mon Sep 17 00:00:00 2001 From: u80864958 Date: Tue, 29 Apr 2025 14:57:48 +0200 Subject: [PATCH] restructuring, config and readme --- backend/README.md | 35 ++++++++ backend/auth/login.go | 102 ---------------------- backend/blog.db | Bin 20480 -> 20480 bytes backend/cmd/main.go | 52 +++++++++++ backend/go.mod | 1 + backend/go.sum | 2 + backend/internal/auth/controller.go | 53 +++++++++++ backend/internal/auth/jwt.go | 51 +++++++++++ backend/internal/auth/resource.go | 26 ++++++ backend/{ => internal}/blog/controller.go | 2 +- backend/{ => internal}/blog/resource.go | 0 backend/internal/config/resource.go | 28 ++++++ backend/internal/initialize/inject.go | 32 +++++++ backend/{ => internal}/model/auth.go | 0 backend/{ => internal}/model/blog.go | 0 backend/internal/model/init.go | 36 ++++++++ backend/main.go | 42 --------- backend/model/init.go | 40 --------- backend/{ => pkg}/cors/cors.go | 0 19 files changed, 317 insertions(+), 185 deletions(-) create mode 100644 backend/README.md delete mode 100644 backend/auth/login.go create mode 100644 backend/cmd/main.go create mode 100644 backend/internal/auth/controller.go create mode 100644 backend/internal/auth/jwt.go create mode 100644 backend/internal/auth/resource.go rename backend/{ => internal}/blog/controller.go (95%) rename backend/{ => internal}/blog/resource.go (100%) create mode 100644 backend/internal/config/resource.go create mode 100644 backend/internal/initialize/inject.go rename backend/{ => internal}/model/auth.go (100%) rename backend/{ => internal}/model/blog.go (100%) create mode 100644 backend/internal/model/init.go delete mode 100644 backend/main.go delete mode 100644 backend/model/init.go rename backend/{ => pkg}/cors/cors.go (100%) diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..bdc5f94 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,35 @@ +# ng-blog Backend + +This is the backend service for the ng-blog project. It's written in Go and provides the API endpoints for the frontend application. + +## Getting Started + +### Prerequisites + +- Go (version 1.24 or higher) + +### Running the Backend + +The backend provides two main commands: `serve` and `help`. + +- **`serve`**: Starts the backend server. It loads configuration from environment variables and listens for incoming requests. + + ```bash + go run ./cmd/main.go serve + ``` + +- **`help`**: Displays usage information about the configuration options. + + ```bash + go run ./cmd/main.go help + ``` + +### Configuration + +The backend is configured using environment variables. The `help` command will display the available configuration options. The configuration is loaded using the `go-simpler.org/env` library. Environment variable names are expected to be in the format `CONFIG_OPTION_NAME`. + +## Directory Structure + +- `cmd/main.go`: The main entry point for the application. Handles command-line arguments and starts the server. +- `internal/config`: Defines the configuration struct and default values. +- `internal/startup`: Responsible for initializing the HTTP multiplexer and other startup tasks. diff --git a/backend/auth/login.go b/backend/auth/login.go deleted file mode 100644 index 46f641c..0000000 --- a/backend/auth/login.go +++ /dev/null @@ -1,102 +0,0 @@ -package auth - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - "time" - - "git.schreifuchs.ch/schreifuchs/ng-blog/backend/model" - "github.com/golang-jwt/jwt/v5" -) - -func Login(username, password string, secret []byte) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - login := model.Login{} - if err := json.NewDecoder(r.Body).Decode(&login); err != nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - if login.Name == username && login.Password == password { - token, err := createJWT(secret) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - err = json.NewEncoder(w).Encode(&model.LoginResponse{ - Token: token, - }) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - w.WriteHeader(http.StatusOK) - return - } - } -} - -func Authenticated(secret []byte) func(http.HandlerFunc) http.Handler { - return func(next http.HandlerFunc) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Our middleware logic goes here... - token, err := extractToken(r) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - err = validateJWT(token, secret) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - next(w, r) - }) - } -} - -func createJWT(secret []byte) (token string, err error) { - return jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{ - "exp": time.Now().Add(time.Hour * 24).Unix(), - }).SignedString(secret) -} - -func validateJWT(tokenString string, secret []byte) (err error) { - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { - // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - - return secret, nil - }) - if err != nil { - return - } - if date, err := token.Claims.GetExpirationTime(); err == nil && date.After(time.Now()) { - return nil - } - return errors.New("JWT not valid") -} - -func extractToken(r *http.Request) (token string, err error) { - tokenHeader := r.Header.Get("Authorization") // Grab the token from the header - - if tokenHeader == "" { - err = errors.New("missing token") - return - } - - token = strings.TrimPrefix(tokenHeader, "Bearer ") - - if token == "" { - err = errors.New("malformed token") - } - return -} diff --git a/backend/blog.db b/backend/blog.db index 0f85c741270309d3865d6b303f64ab5401f36e76..409e56d08fad00d6847e5df67ea3c7b20b05d17d 100644 GIT binary patch delta 404 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 Username: admin +> +> Password: admin +`, + }) + + return db +} diff --git a/backend/main.go b/backend/main.go deleted file mode 100644 index c6bd50b..0000000 --- a/backend/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "net/http" - "os" - - "git.schreifuchs.ch/schreifuchs/ng-blog/backend/auth" - "git.schreifuchs.ch/schreifuchs/ng-blog/backend/blog" - "git.schreifuchs.ch/schreifuchs/ng-blog/backend/cors" - "git.schreifuchs.ch/schreifuchs/ng-blog/backend/model" - "github.com/gorilla/mux" -) - -func main() { - user, ok := os.LookupEnv("USERNAME") - if !ok { - user = "admin" - } - password, ok := os.LookupEnv("PASSWORD") - if !ok { - password = "admin" - } - secret, ok := os.LookupEnv("SECRET") - if !ok { - secret = "Foo" - } - - db := model.Init() - blg := blog.New(db) - r := mux.NewRouter() - r.Use(cors.HandlerForOrigin("*")) - r.Handle("/login", auth.Login(user, password, []byte(secret))).Methods("POST") - r.Handle("/posts", auth.Authenticated([]byte(secret))(blg.SavePost)).Methods("POST") - r.Handle("/posts", auth.Authenticated([]byte(secret))(blg.SavePost)).Methods("PUT") - r.Handle("/posts/{postID}", auth.Authenticated([]byte(secret))(blg.DeletePost)).Methods("DELETE") - r.Handle("/posts", http.HandlerFunc(blg.GetAllPosts)).Methods("GET") - r.Methods("OPTIONS").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // The CORS middleware should set up the headers for you - w.WriteHeader(http.StatusNoContent) - }) - http.ListenAndServe(":8080", r) -} diff --git a/backend/model/init.go b/backend/model/init.go deleted file mode 100644 index 2054405..0000000 --- a/backend/model/init.go +++ /dev/null @@ -1,40 +0,0 @@ -package model - -import ( - "log" - - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -func Init() *gorm.DB { - db, err := gorm.Open(sqlite.Open("./blog.db")) - if err != nil { - log.Panic(err) - } - db.AutoMigrate(&Post{}, &Comment{}) - - db.Save(&Post{ - ID: 1, - Title: "Foo", - TLDR: "Just some Foo Bar", - Content: "fkdj kjfk adjflkjdlö jdslj alsödj fla", - }) - db.Save(&Post{ - ID: 2, - Title: "Bar", - TLDR: "Just some Bar Baz", - Content: ` -# Hello Worls - -- alsödj -- adf adf - -| adsf | asdf | -|------|------| -| adf | adsf | - `, - }) - - return db -} diff --git a/backend/cors/cors.go b/backend/pkg/cors/cors.go similarity index 100% rename from backend/cors/cors.go rename to backend/pkg/cors/cors.go