diff --git a/backend/api/auth.go b/backend/api/auth.go new file mode 100644 index 0000000..7529bb2 --- /dev/null +++ b/backend/api/auth.go @@ -0,0 +1,120 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" + core "fr.latosa-escrima/api/core" +) + +var MySigningKey = []byte("COUCOU") + +type LoginInformation struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type Claims struct { + UserID string `json:"user_id"` + jwt.RegisteredClaims +} + +func HandleLogin(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + log.Fatal("Not post method") + } + + if r.Body == nil { + log.Fatal("No body") + } + + body, err := io.ReadAll(r.Body) + if err != nil { + log.Fatal(err) + } + var login LoginInformation + err = json.Unmarshal(body, &login) + if err != nil { + log.Fatal(err) + } + + var user core.User + err = core.DB.NewSelect(). + Model(&user). + Where("email = ? AND password = ?", login.Email, login.Password). + Limit(1). + Scan(context.Background()) + + if err != nil { + log.Fatal(err) + } + + claims := Claims{ + UserID: user.UserID.String(), + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "latosa-escrima.fr", + Subject: "authentification", + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signed, err := token.SignedString([]byte("hello")) + if err != nil { + log.Fatal(err) + } + + fmt.Println(signed) +} + +func HandleMiddlewareRoute(pattern string, + handler func(w http.ResponseWriter, r *http.Request), + middleware func(http.Handler) http.Handler, + mux *http.ServeMux, +) { + mux.HandleFunc(pattern, handler) + http.Handle(pattern, middleware(mux)) +} + +func AuthJWT(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check if the Authorization header is provided + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + http.Error(w, "Missing Authorization header", http.StatusUnauthorized) + return + } + + // Bearer token is expected, so split the header into "Bearer " + tokenString := strings.TrimPrefix(authHeader, "Bearer ") + if tokenString == authHeader { + http.Error(w, "Invalid Authorization header format", http.StatusUnauthorized) + return + } + + // Parse the token + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Ensure that the token's signing method is valid + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return MySigningKey, nil + }) + + if err != nil || !token.Valid { + http.Error(w, "Invalid token", http.StatusUnauthorized) + return + } + + // Call the next handler if the JWT is valid + next.ServeHTTP(w, r) + }) +} diff --git a/backend/api/blog/new/route.go b/backend/api/blog/new/route.go deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/blog/route.go b/backend/api/blog/route.go deleted file mode 100644 index e69de29..0000000 diff --git a/backend/api/blogs.go b/backend/api/blogs.go new file mode 100644 index 0000000..ac128ba --- /dev/null +++ b/backend/api/blogs.go @@ -0,0 +1,58 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + core "fr.latosa-escrima/api/core" + "github.com/uptrace/bun" +) + +func HandleGetBlog(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Println("salut") + emptyObject := make(map[string]interface{}) + + emptyJSON, json_err := json.Marshal(emptyObject) + if json_err != nil { + fmt.Println("Couldn't create the json object") + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(emptyJSON)) + return + } + + if r.Method != http.MethodGet { + http.Error(w, "Wrong method", http.StatusMethodNotAllowed) + return + } + + blog := &core.Blog{ + BaseModel: bun.BaseModel{}, + BlogID: [16]byte{}, + Slug: "", + Content: "", + Label: "", + AuthorID: [16]byte{}, + Published: time.Time{}, + Summary: "", + Image: "", + Href: "", + Author: &core.User{}, + } + + blog_uuid := r.PathValue("uuid") + fmt.Println(blog_uuid) + ctx := context.Background() + err_db := core.DB.NewSelect().Model(blog).Where("uuid = ?", blog_uuid).Scan(ctx) + if err_db != nil { + log.Fatal(err_db) + http.Error(w, "Can't use select", http.StatusNotFound) + return + } + + w.Write([]byte(`{message: "Successfuly responded to request}`)) +} diff --git a/backend/schemas.go b/backend/api/core/schemas.go similarity index 90% rename from backend/schemas.go rename to backend/api/core/schemas.go index f845c3e..f31d594 100644 --- a/backend/schemas.go +++ b/backend/api/core/schemas.go @@ -1,9 +1,10 @@ -package main +package core import ( "context" "database/sql" "time" + "fmt" "github.com/google/uuid" "github.com/uptrace/bun" @@ -11,6 +12,20 @@ import ( "github.com/uptrace/bun/driver/pgdriver" ) +var DB *bun.DB + +type DSN struct { + Hostname string + Port string + DBName string + User string + Password string +} + +func (dsn *DSN) ToString() string { + return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", dsn.User, dsn.Password, dsn.Hostname, dsn.Port, dsn.DBName) +} + type Role string const ( diff --git a/backend/api/new_blog.go b/backend/api/new_blog.go new file mode 100644 index 0000000..3151747 --- /dev/null +++ b/backend/api/new_blog.go @@ -0,0 +1,52 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + core "fr.latosa-escrima/api/core" + "github.com/uptrace/bun" +) + +func HandleCreateBlog(w http.ResponseWriter, r *http.Request) { + + emptyObject := make(map[string]interface{}) + + emptyJSON, json_err := json.Marshal(emptyObject) + if json_err != nil { + fmt.Println("Couldn't create the json object") + w.Write(emptyJSON) + } + + if r.Method != http.MethodPost { + } + + err := r.ParseForm() + if err != nil { + } + + user := &core.Blog{ + BaseModel: bun.BaseModel{}, + BlogID: [16]byte{}, + Slug: "", + Content: "", + Label: "", + AuthorID: [16]byte{}, + Published: time.Time{}, + Summary: "", + Image: "", + Href: "", + Author: &core.User{}, + } + + _, err_db := core.DB.NewInsert().Model(user).Exec(context.Background()) + if err_db != nil { + log.Fatal(err) + } + + fmt.Println("User inserted successfully") +} diff --git a/backend/api/new_user.go b/backend/api/new_user.go new file mode 100644 index 0000000..615bcec --- /dev/null +++ b/backend/api/new_user.go @@ -0,0 +1,35 @@ +package api + +import ( + "context" + "fmt" + "log" + "net/http" + core "fr.latosa-escrima/api/core" +) + +func handlerCreateUser(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"message": "Resource created successfully"}`)) + } + + user := &core.User{ + FirstName: "John", + LastName: "Doe", + Email: "john.doe@example.com", + Phone: "1234567890", + Password: "1234", + } + + _, err := core.DB.NewInsert().Model(user).Exec(context.Background()) + if err != nil { + log.Fatal(err) + } + + fmt.Println("User inserted successfully") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"message": "Inserted the user"}`)) +} diff --git a/backend/auth.go b/backend/auth.go deleted file mode 100644 index 2e3645e..0000000 --- a/backend/auth.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "time" - - "github.com/golang-jwt/jwt/v5" -) - -type LoginInformation struct { - Email string `json:"email"` - Password string `json:"password"` -} - -type Claims struct { - UserID string `json:"user_id"` - jwt.RegisteredClaims -} - -func HandleLogin(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - log.Fatal("Not post method") - } - - if r.Body == nil { - log.Fatal("No body") - } - - body, err := io.ReadAll(r.Body) - if err != nil { - log.Fatal(err) - } - var login LoginInformation - err = json.Unmarshal(body, &login) - if err != nil { - log.Fatal(err) - } - - var user User - err = DB.NewSelect(). - Model(&user). - Where("email = ? AND password = ?", login.Email, login.Password). - Limit(1). - Scan(context.Background()) - - if err != nil { - log.Fatal(err) - } - - claims := Claims{ - UserID: user.UserID.String(), - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: "latosa-escrima.fr", - Subject: "authentification", - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), - IssuedAt: jwt.NewNumericDate(time.Now()), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signed, err := token.SignedString([]byte("hello")) - if err != nil { - log.Fatal(err) - } - - fmt.Println(signed) -} diff --git a/backend/main.go b/backend/main.go index 5c44786..954cf36 100644 --- a/backend/main.go +++ b/backend/main.go @@ -5,15 +5,12 @@ import ( "log" "net/http" "os" - "time" - "encoding/json" - "strings" "github.com/joho/godotenv" - "context" - _ "github.com/lib/pq" - "github.com/uptrace/bun" + + api "fr.latosa-escrima/api" + schemas "fr.latosa-escrima/api/core" ) func handler(w http.ResponseWriter, r *http.Request) { @@ -25,138 +22,6 @@ type ErrorResponse struct { Message string `json:"message"` } -var DB *bun.DB - -type DSN struct { - Hostname string - Port string - DBName string - User string - Password string -} - -func (dsn *DSN) ToString() string { - return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", dsn.User, dsn.Password, dsn.Hostname, dsn.Port, dsn.DBName) -} - -func handlerCreateUser(w http.ResponseWriter, r *http.Request) { - - if r.Method != http.MethodPost { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"message": "Resource created successfully"}`)) - } - - user := &User{ - FirstName: "John", - LastName: "Doe", - Email: "john.doe@example.com", - Phone: "1234567890", - Password: "1234", - } - - _, err := DB.NewInsert().Model(user).Exec(context.Background()) - if err != nil { - log.Fatal(err) - } - - fmt.Println("User inserted successfully") -} - -func handlerCreateBlog(w http.ResponseWriter, r *http.Request) { - - emptyObject := make(map[string]interface{}) - - emptyJSON, json_err := json.Marshal(emptyObject) - if json_err != nil { - fmt.Println("Couldn't create the json object") - w.Write(emptyJSON) - } - - if r.Method != http.MethodPost { - return string(emptyJSON) - } - - err := r.ParseForm() - if err != nil { - return string(emptyJSON) - } - - user := &Blog{ - BaseModel: bun.BaseModel{}, - BlogID: [16]byte{}, - Slug: "", - Content: "", - Label: "", - AuthorID: [16]byte{}, - Published: time.Time{}, - Summary: "", - Image: "", - Href: "", - Author: &User{}, - } - - _, err_db := DB.NewInsert().Model(user).Exec(context.Background()) - if err_db != nil { - log.Fatal(err) - } - - fmt.Println("User inserted successfully") - return string(emptyJSON) -} - -func handlerGetBlog(w http.ResponseWriter, r *http.Request) (string) { - log.Println("ok") - emptyObject := make(map[string]interface{}) - - emptyJSON, json_err := json.Marshal(emptyObject) - if json_err != nil { - fmt.Println("Couldn't create the json object") - return string(emptyJSON) - } - - if r.Method != http.MethodGet { - http.Error(w, "Wrong method", http.StatusMethodNotAllowed) - return string(emptyJSON) - } - - if !strings.HasPrefix(r.URL.Path, "/blog/") { - http.Redirect(w, r, "/blog", http.StatusFound) - return string(emptyJSON) - } - - blog_uuid := strings.TrimPrefix(r.URL.Path, "/blog/") - if blog_uuid == "" { - http.Error(w, "Slug is required", http.StatusBadRequest) - return string(emptyJSON) - } - - blog := &Blog{ - BaseModel: bun.BaseModel{}, - BlogID: [16]byte{}, - Slug: "", - Content: "", - Label: "", - AuthorID: [16]byte{}, - Published: time.Time{}, - Summary: "", - Image: "", - Href: "", - Author: &User{}, - } - - ctx := context.Background() - err_db := DB.NewSelect().Model(blog).Where("uuid = ", blog_uuid).Scan(ctx) - if err_db != nil { - log.Fatal(err_db) - http.Error(w, "Can't use select", http.StatusNotFound) - return string(emptyJSON) - } - - fmt.Println("Blog inserted successfully") - return string(emptyJSON) -} - func main() { err := godotenv.Load() if err != nil { @@ -168,26 +33,33 @@ func main() { port = os.Getenv("BACKEND_PORT") } - dsn := DSN{ + dsn := schemas.DSN{ Hostname: "localhost", Port: os.Getenv("POSTGRES_PORT"), DBName: os.Getenv("POSTGRES_DB"), User: os.Getenv("POSTGRES_USER"), Password: os.Getenv("POSTGRES_PASSWORD"), } - DB, err = InitDatabase(dsn) + schemas.DB, err = schemas.InitDatabase(dsn) if err != nil { log.Fatal(err) } - http.HandleFunc("/", handler) + mux := http.NewServeMux() - http.HandleFunc("/user/new", handlerCreateUser) - http.HandleFunc("/users/login", HandleLogin) - http.HandleFunc("/blog/", handlerGetBlog) + HandleRoutes(mux, map[string]Handler{ + "/": { handler, nil}, + "/users/login": { api.HandleLogin, nil}, + // "/users/new": { api.HandleCreateUser, api.AuthJWT}, + // "/blogs": { api.HandleGetBlogs, nil}, + "/blogs/new": { api.HandleCreateBlog, nil}, + "/blogs/{uuid}": { api.HandleGetBlog, nil}, + // "/events": { api.HandleGetEvents, api.AuthJWT }, + // "/events/new": { api.HandleCreateEvents, api.AuthJWT } + }) fmt.Printf("Serving on port %s\n", port) - err = http.ListenAndServe(fmt.Sprintf(":%s", port), nil) + err = http.ListenAndServe(fmt.Sprintf(":%s", port), mux) if err != nil { fmt.Printf("Error starting server: %s\n", err) } diff --git a/backend/router.go b/backend/router.go new file mode 100644 index 0000000..0bb167a --- /dev/null +++ b/backend/router.go @@ -0,0 +1,28 @@ +package main + +import ( + "net/http" + api "fr.latosa-escrima/api" +) + +type HandlerFunc func(w http.ResponseWriter, r *http.Request) + +type Handler struct { + Handler HandlerFunc + Middleware func(http.Handler) http.Handler +} + +func HandleRoutes(mux *http.ServeMux, routes map[string]Handler) { + for pattern, handler := range routes { + if handler.Middleware == nil { + mux.HandleFunc(pattern, handler.Handler) + } else { + api.HandleMiddlewareRoute( + pattern, + handler.Handler, + handler.Middleware, + mux, + ) + } + } +} diff --git a/frontend/app/(main)/about/page.tsx b/frontend/app/(main)/about/page.tsx index ea63966..2668b5a 100644 --- a/frontend/app/(main)/about/page.tsx +++ b/frontend/app/(main)/about/page.tsx @@ -1,7 +1,7 @@ "use server"; export default async function About() { - const res = await fetch("http://localhost:8000/api"); + const res = await fetch("api"); console.log(res); return (
diff --git a/frontend/next.config.ts b/frontend/next.config.ts index f695f20..b9b9e42 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -11,8 +11,8 @@ const nextConfig: NextConfig = { } return [ { - source: "/api", - destination: `http://localhost:${process.env.BACKEND_PORT}`, + source: "/api/:path", + destination: `http://localhost:${process.env.BACKEND_PORT}/:path`, permanent: false, }, ];