diff --git a/api/cmd/main.go b/api/cmd/main.go index a418850..c5aae21 100644 --- a/api/cmd/main.go +++ b/api/cmd/main.go @@ -11,6 +11,7 @@ import ( "github.com/CristyNel/CV_project/tree/main/api/internal/database" "github.com/CristyNel/CV_project/tree/main/api/mock" "github.com/CristyNel/CV_project/tree/main/api/routes" + "github.com/gorilla/sessions" ) func main() { @@ -27,15 +28,25 @@ func main() { log.Fatalf("\033[1;31;1m * * * 🚨 Failed to initialize the database connection.\033[0m") } + // Initialize the session store + store := sessions.NewCookieStore([]byte("your-secret-key")) + // Use the mock logger logger := mock.NewMockLogger() app := &app.App{ DB: Db, Logger: logger, + Store: store, } + // Initialize the router r := routes.InitializeRouter(app) + // Redirect root URL to login page with GET method + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/login", http.StatusSeeOther) + }).Methods("GET") + app.Logger.Printf("\n\033[1;37;1m * * * 🛫 Starting the HTTP server on port: ➮\033[1;94;1m 8080\033[0m") if err := http.ListenAndServe(":8080", r); err != nil { app.Logger.Fatalf("\n * Failed to start HTTP server: %s\n", err) diff --git a/api/go.mod b/api/go.mod index f6ed8d7..03d2cd8 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,7 +1,9 @@ // bcn/github/CV_project/api/go.mod module github.com/CristyNel/CV_project/tree/main/api -go 1.22 +go 1.23 + +toolchain go1.23.1 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 @@ -22,6 +24,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gorilla/sessions v1.4.0 github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.26.0 // indirect diff --git a/api/go.sum b/api/go.sum index 0244548..ecd6e5a 100644 --- a/api/go.sum +++ b/api/go.sum @@ -16,6 +16,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= diff --git a/api/handlers/home.go b/api/handlers/home.go index 13ce1d7..bb8faff 100644 --- a/api/handlers/home.go +++ b/api/handlers/home.go @@ -1,4 +1,3 @@ -// * CV_project/api/handlers/home.go package handlers import ( @@ -9,8 +8,9 @@ import ( "github.com/CristyNel/CV_project/tree/main/api/models" ) +// HomeUsers handles the /users route func HomeUsers(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println(" * * * ☎️ Received request for /users") + app.Logger.Println(" * * * ☎️ Received request for /users") if r.Method != "GET" { w.WriteHeader(http.StatusMethodNotAllowed) @@ -21,7 +21,7 @@ func HomeUsers(app *app.App, w http.ResponseWriter, r *http.Request) { var users []models.User rows, err := app.DB.Query("SELECT id, firstname, lastname, email FROM users") if err != nil { - app.Logger.Println("Error querying database: ", err) + app.Logger.Println(" * * * ⛔️ Error querying database: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -31,7 +31,7 @@ func HomeUsers(app *app.App, w http.ResponseWriter, r *http.Request) { var user models.User err := rows.Scan(&user.ID, &user.Firstname, &user.Lastname, &user.Email) if err != nil { - app.Logger.Println("Error scanning row: ", err) + app.Logger.Println(" * * * ⛔️ Error scanning row: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -41,17 +41,18 @@ func HomeUsers(app *app.App, w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(users) } +// Home handles the / route func Home(app *app.App, w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } - app.Logger.Println(" * * * ☎️ Received request for /") + app.Logger.Println(" * * * ☎️ Received request for /") rows, err := app.DB.Query("SELECT id, jobtitle, firstname, lastname, email, phone, address, city, country, postalcode, dateofbirth, nationality, summary, workexperience, education, skills, languages FROM users") if err != nil { - app.Logger.Println("Error querying database: ", err) + app.Logger.Println(" * * * ⛔️ Error querying database: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -63,7 +64,7 @@ func Home(app *app.App, w http.ResponseWriter, r *http.Request) { var user models.User err := rows.Scan(&user.ID, &user.Jobtitle, &user.Firstname, &user.Lastname, &user.Email, &user.Phone, &user.Address, &user.City, &user.Country, &user.Postalcode, &user.Dateofbirth, &user.Nationality, &user.Summary, &user.Workexperience, &user.Education, &user.Skills, &user.Languages) if err != nil { - app.Logger.Println("Error scanning row: ", err) + app.Logger.Println(" * * * ⛔️ Error scanning row: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/api/handlers/templates.go b/api/handlers/templates.go index a2a9c1a..f101704 100644 --- a/api/handlers/templates.go +++ b/api/handlers/templates.go @@ -1,4 +1,3 @@ -// * handlers/templates.go package handlers import ( @@ -17,19 +16,20 @@ import ( wkhtml "github.com/SebastiaanKlippert/go-wkhtmltopdf" ) +// GenerateTemplate generates a PDF from a template and user data func GenerateTemplate(app *app.App, w http.ResponseWriter, r *http.Request) { app.Logger.Println("Received request for /pdf") query := r.URL.Query() - template_id := query["template"] - user_id := query["user"] + templateID := query["template"] + userID := query["user"] - iduser_int, err := strconv.Atoi(user_id[0]) + iduserINT, err := strconv.Atoi(userID[0]) if err != nil { log.Printf("An error occurred: %v", err) } - idtemplate_int, err := strconv.Atoi(template_id[0]) + idtemplateINT, err := strconv.Atoi(templateID[0]) if err != nil { log.Printf("An error occurred: %v", err) } @@ -38,27 +38,27 @@ func GenerateTemplate(app *app.App, w http.ResponseWriter, r *http.Request) { var template utils.Template // Get the path of the template - row1 := app.DB.QueryRow("SELECT Path FROM template WHERE id = ?", idtemplate_int) + row1 := app.DB.QueryRow("SELECT Path FROM template WHERE id = ?", idtemplateINT) if err := row1.Scan(&template.Path); err != nil { if err == sql.ErrNoRows { - app.Logger.Println("Error scanning row: ", err) + app.Logger.Println(" * * * ⛔️ Error scanning row: ", err) http.NotFound(w, r) return } - http.Error(w, fmt.Sprintf("Error fetching user data: %v", err), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error fetching user data: %v", err), http.StatusInternalServerError) return } - row := app.DB.QueryRow("SELECT * FROM users WHERE id = ?", iduser_int) + row := app.DB.QueryRow("SELECT * FROM users WHERE id = ?", iduserINT) if err := row.Scan(&user.ID, &user.Jobtitle, &user.Firstname, &user.Lastname, &user.Email, &user.Phone, &user.Address, &user.City, &user.Country, &user.Postalcode, &user.Dateofbirth, &user.Nationality, &user.Summary, &user.Workexperience, &user.Education, &user.Skills, &user.Languages); err != nil { if err == sql.ErrNoRows { - app.Logger.Println("Error scanning row: ", err) + app.Logger.Println(" * * * ⛔️ Error scanning row: ", err) http.NotFound(w, r) return } - http.Error(w, fmt.Sprintf("Error fetching user data: %v", err), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error fetching user data: %v", err), http.StatusInternalServerError) return } @@ -93,7 +93,7 @@ func GenerateTemplate(app *app.App, w http.ResponseWriter, r *http.Request) { } // Read - populateHtml, err := os.ReadFile("./bff/templates/view/populate_template.html") + populateHTML, err := os.ReadFile("./bff/templates/view/populate_template.html") if err != nil { log.Fatal(err) } @@ -103,7 +103,7 @@ func GenerateTemplate(app *app.App, w http.ResponseWriter, r *http.Request) { return } // Add HTML page - pdfg.AddPage(wkhtml.NewPageReader(bytes.NewReader(populateHtml))) + pdfg.AddPage(wkhtml.NewPageReader(bytes.NewReader(populateHTML))) // Create the PDF document in memory err = pdfg.Create() if err != nil { @@ -115,5 +115,5 @@ func GenerateTemplate(app *app.App, w http.ResponseWriter, r *http.Request) { log.Fatal(err) } // Respond with template and user IDs - fmt.Fprintf(w, "%s, %s", template_id[0], user_id[0]) + fmt.Fprintf(w, "%s, %s", templateID[0], userID[0]) } diff --git a/api/handlers/users.go b/api/handlers/users.go index 2ccac47..d6d1d32 100644 --- a/api/handlers/users.go +++ b/api/handlers/users.go @@ -1,4 +1,3 @@ -// * CV_project/api/handlers/users.go package handlers import ( @@ -6,119 +5,15 @@ import ( "fmt" "net/http" "strconv" - "strings" "github.com/CristyNel/CV_project/tree/main/api/internal/app" - "github.com/CristyNel/CV_project/tree/main/api/internal/utils" "github.com/CristyNel/CV_project/tree/main/api/models" "github.com/gorilla/mux" - "golang.org/x/crypto/bcrypt" ) -// SignupRequest represents the request payload for user signup -type SignupRequest struct { - Email string `json:"email"` - Password string `json:"password"` -} - -func LoginHandler(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /login") - - if err := r.ParseForm(); err != nil { - app.Logger.Println("Error parsing form:", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - - username := strings.TrimSpace(r.Form.Get("username")) - password := r.Form.Get("password") - - app.Logger.Println("Parsed form data - Username:", username) // , "Password:", password - - if username == "" || password == "" { - app.Logger.Println("Missing username or password") - http.Error(w, "Bad Request", http.StatusBadRequest) - return - } - - if utils.VerifyLogin(app, username, password) { - response := struct { - Username string `json:"username"` - }{ - Username: username, - } - - w.Header().Set("Content-Type", "application/json") - utils.SetSession(app, username, w) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(response) - } else { - app.Logger.Println("Unauthorized access attempt by:", username) - http.Error(w, "Unauthorized", http.StatusUnauthorized) - } -} - -func LogoutHandler(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /logout") - //clear session - cookie := &http.Cookie{ - Name: "session", - Value: "", - Path: "/", - MaxAge: -1, - } - http.SetCookie(w, cookie) - - w.WriteHeader(http.StatusNoContent) - w.Write([]byte("User logout successfully")) -} - -func SignupHandler(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /signup") - - // Check if the Content-Type is application/json - if r.Header.Get("Content-Type") != "application/json" { - http.Error(w, "Invalid Content-Type", http.StatusUnsupportedMediaType) - return - } - - // Decode the JSON request body - var req SignupRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - app.Logger.Println("Error decoding JSON: ", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - email := strings.TrimSpace(req.Email) - password := req.Password - - if email == "" || password == "" { - http.Error(w, "Bad Request", http.StatusBadRequest) - return - } - - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - _, err = app.DB.Exec("INSERT INTO userlogin (username, password) VALUES (?,?)", email, hashedPassword) - if err != nil { - app.Logger.Println("Error querying database: ", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusCreated) - w.Write([]byte("User signup successfully")) -} - // ShowUser handles GET requests to fetch one user by ID func ShowUser(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /user/{id}, method: GET") + app.Logger.Println(" * * * ☎️ Received request for /user/{id}, method: GET") // Get the user ID from the URL vars := mux.Vars(r) @@ -129,8 +24,8 @@ func ShowUser(app *app.App, w http.ResponseWriter, r *http.Request) { err := app.DB.QueryRow("SELECT id, jobtitle, firstname, lastname, email, phone, address, city, country, postalcode, dateofbirth, nationality, summary, workexperience, education, skills, languages FROM users WHERE id = ?", id).Scan( &user.ID, &user.Jobtitle, &user.Firstname, &user.Lastname, &user.Email, &user.Phone, &user.Address, &user.City, &user.Country, &user.Postalcode, &user.Dateofbirth, &user.Nationality, &user.Summary, &user.Workexperience, &user.Education, &user.Skills, &user.Languages) if err != nil { - app.Logger.Println("Error querying database: ", err) - http.Error(w, fmt.Sprintf("Error querying database: %v", err), http.StatusInternalServerError) + app.Logger.Println(" * * * ⛔️ Error querying database: ", err) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error querying database: %v", err), http.StatusInternalServerError) return } @@ -142,9 +37,10 @@ func ShowUser(app *app.App, w http.ResponseWriter, r *http.Request) { } } +// ShowUsers handles GET requests to fetch all users // ShowUsers handles GET requests to fetch all users func ShowUsers(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /users, method: GET") + app.Logger.Println(" * * * ☎️ Received request for /users, method: GET") if r.Method != http.MethodGet { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) @@ -153,8 +49,8 @@ func ShowUsers(app *app.App, w http.ResponseWriter, r *http.Request) { rows, err := app.DB.Query("SELECT * FROM users") if err != nil { - app.Logger.Println("Error querying database: ", err) - http.Error(w, fmt.Sprintf("Error querying database: %v", err), http.StatusInternalServerError) + app.Logger.Println(" * * * ⛔️ Error querying database: ", err) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error querying database: %v", err), http.StatusInternalServerError) return } defer rows.Close() @@ -163,8 +59,8 @@ func ShowUsers(app *app.App, w http.ResponseWriter, r *http.Request) { for rows.Next() { var user models.User if err := rows.Scan(&user.ID, &user.Jobtitle, &user.Firstname, &user.Lastname, &user.Email, &user.Phone, &user.Address, &user.City, &user.Country, &user.Postalcode, &user.Dateofbirth, &user.Nationality, &user.Summary, &user.Workexperience, &user.Education, &user.Skills, &user.Languages); err != nil { - app.Logger.Println("Error scanning row: ", err) - http.Error(w, fmt.Sprintf("Error scanning row: %v", err), http.StatusInternalServerError) + app.Logger.Println(" * * * ⛔️ Error scanning row: ", err) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error scanning row: %v", err), http.StatusInternalServerError) return } app.Logger.Printf("Scanned user: %+v\n", user) @@ -173,15 +69,15 @@ func ShowUsers(app *app.App, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(users); err != nil { - app.Logger.Println("Error encoding user data: ", err) - http.Error(w, fmt.Sprintf("Error encoding user data: %v", err), http.StatusInternalServerError) + app.Logger.Println(" * * * ⛔️ Error encoding user data: ", err) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error encoding user data: %v", err), http.StatusInternalServerError) } app.Logger.Println("Successfully encoded and sent user data") } // CreateUser handles POST requests to create a new user func CreateUser(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /user, method: POST") + app.Logger.Println(" * * * ☎️ Received request for /user, method: POST") if r.Method != http.MethodPost { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) @@ -190,7 +86,7 @@ func CreateUser(app *app.App, w http.ResponseWriter, r *http.Request) { var user models.User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { - app.Logger.Println("Error decoding request body: ", err) + app.Logger.Println(" * * * ⛔️ Error decoding request body: ", err) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -199,7 +95,7 @@ func CreateUser(app *app.App, w http.ResponseWriter, r *http.Request) { _, err := app.DB.Exec("INSERT INTO users (Jobtitle, Firstname, Lastname, Email, Phone, Address, City, Country, Postalcode, Dateofbirth, Nationality, Summary, Workexperience, Education, Skills, Languages) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", user.Jobtitle, user.Firstname, user.Lastname, user.Email, user.Phone, user.Address, user.City, user.Country, user.Postalcode, user.Dateofbirth, user.Nationality, user.Summary, user.Workexperience, user.Education, user.Skills, user.Languages) if err != nil { - app.Logger.Println("Error inserting user into database: ", err) + app.Logger.Println(" * * * ⛔️ Error inserting user into database: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -210,7 +106,7 @@ func CreateUser(app *app.App, w http.ResponseWriter, r *http.Request) { // UpdateUser handles PUT requests to update an existing user func UpdateUser(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /user/{id}, method: PUT") + app.Logger.Println(" * * * ☎️ Received request for /user/{id}, method: PUT") if r.Method != http.MethodPut { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) @@ -228,7 +124,7 @@ func UpdateUser(app *app.App, w http.ResponseWriter, r *http.Request) { var user models.User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { - app.Logger.Println("Error decoding request body: ", err) + app.Logger.Println(" * * * ⛔️ Error decoding request body: ", err) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -237,7 +133,7 @@ func UpdateUser(app *app.App, w http.ResponseWriter, r *http.Request) { _, err = app.DB.Exec("UPDATE users SET Jobtitle = ?, Firstname = ?, Lastname = ?, Email = ?, Phone = ?, Address = ?, City = ?, Country = ?, Postalcode = ?, Dateofbirth = ?, Nationality = ?, Summary = ?, Workexperience = ?, Education = ?, Skills = ?, Languages = ? WHERE id = ?", user.Jobtitle, user.Firstname, user.Lastname, user.Email, user.Phone, user.Address, user.City, user.Country, user.Postalcode, user.Dateofbirth, user.Nationality, user.Summary, user.Workexperience, user.Education, user.Skills, user.Languages, id) if err != nil { - app.Logger.Println("Error updating user in database: ", err) + app.Logger.Println(" * * * ⛔️ Error updating user in database: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -248,7 +144,7 @@ func UpdateUser(app *app.App, w http.ResponseWriter, r *http.Request) { // DeleteUser handles DELETE requests to delete a user by ID func DeleteUser(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /user/{id}, method: DELETE") + app.Logger.Println(" * * * ☎️ Received request for /user/{id}, method: DELETE") if r.Method != http.MethodDelete { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) @@ -272,7 +168,7 @@ func DeleteUser(app *app.App, w http.ResponseWriter, r *http.Request) { var exists bool err = app.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE id = ?)", id).Scan(&exists) if err != nil { - app.Logger.Println("Error checking user existence: ", err) + app.Logger.Println(" * * * ⛔️ Error checking user existence: ", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } @@ -283,7 +179,7 @@ func DeleteUser(app *app.App, w http.ResponseWriter, r *http.Request) { stmt, err := app.DB.Prepare("DELETE FROM users WHERE id = ?") if err != nil { - app.Logger.Println("Error preparing delete statement: ", err) + app.Logger.Println(" * * * ⛔️ Error preparing delete statement: ", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } @@ -291,14 +187,14 @@ func DeleteUser(app *app.App, w http.ResponseWriter, r *http.Request) { result, err := stmt.Exec(id) if err != nil { - app.Logger.Println("Error executing delete statement: ", err) + app.Logger.Println(" * * * ⛔️ Error executing delete statement: ", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } rowsAffected, err := result.RowsAffected() if err != nil { - app.Logger.Println("Error fetching rows affected: ", err) + app.Logger.Println(" * * * ⛔️ Error fetching rows affected: ", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } @@ -313,7 +209,7 @@ func DeleteUser(app *app.App, w http.ResponseWriter, r *http.Request) { // AddUser handles POST requests to add a new user func AddUser(app *app.App, w http.ResponseWriter, r *http.Request) { - app.Logger.Println("Received request for /user, method: POST") + app.Logger.Println(" * * * ☎️ Received request for /user, method: POST") if r.Method != http.MethodPost { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) @@ -322,16 +218,16 @@ func AddUser(app *app.App, w http.ResponseWriter, r *http.Request) { var user models.User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { - app.Logger.Println("Error decoding request body: ", err) - http.Error(w, fmt.Sprintf("Error decoding request body: %v", err), http.StatusBadRequest) + app.Logger.Println(" * * * ⛔️ Error decoding request body: ", err) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error decoding request body: %v", err), http.StatusBadRequest) return } _, err := app.DB.Exec("INSERT INTO users (jobtitle, firstname, lastname, email, phone, address, city, country, postalcode, dateofbirth, nationality, summary, workexperience, education, skills, languages) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", user.Jobtitle, user.Firstname, user.Lastname, user.Email, user.Phone, user.Address, user.City, user.Country, user.Postalcode, user.Dateofbirth, user.Nationality, user.Summary, user.Workexperience, user.Education, user.Skills, user.Languages) if err != nil { - app.Logger.Println("Error inserting user into database: ", err) - http.Error(w, fmt.Sprintf("Error inserting user into database: %v", err), http.StatusInternalServerError) + app.Logger.Println(" * * * ⛔️ Error inserting user into database: ", err) + http.Error(w, fmt.Sprintf(" * * * ⛔️ Error inserting user into database: %v", err), http.StatusInternalServerError) return } diff --git a/api/internal/app/app.go b/api/internal/app/app.go index 94fedf5..0007f86 100644 --- a/api/internal/app/app.go +++ b/api/internal/app/app.go @@ -1,11 +1,12 @@ -// * CV_project/api/internal/app/app.go package app import ( "database/sql" + + "github.com/gorilla/sessions" ) -// LoggerInterface defines the methods that a logger should implement +// Logger is an interface that defines the logging methods type Logger interface { Print(v ...interface{}) Printf(format string, v ...interface{}) @@ -18,6 +19,5 @@ type Logger interface { type App struct { DB *sql.DB Logger Logger + Store *sessions.CookieStore } - - diff --git a/api/internal/app/middleware.go b/api/internal/app/middleware.go new file mode 100644 index 0000000..07c8cf3 --- /dev/null +++ b/api/internal/app/middleware.go @@ -0,0 +1,77 @@ +package app + +import ( + "fmt" + "net/http" +) + +// RequireAuthentication middleware to protect routes +// RequireAuthentication middleware to protect routes +func (app *App) RequireAuthentication(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + app.Logger.Println(" * * * 🧄 RequireAuthentication middleware invoked for:", r.URL.Path) + + // Check if the session store is initialized + if app.Store == nil { + app.Logger.Println(" * * * ⛔️ Session store is not initialized") + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Check if the request has a valid session + session, err := app.Store.Get(r, "session") + if err != nil { + app.Logger.Println(" * * * ⛔️ Error getting session:", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Check if the session contains the userName + userName, ok := session.Values["userName"] + if !ok { + app.Logger.Println(" * * * ⛔️ User name not found in session") + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + // Check if the userName is a string + userNameStr, ok := userName.(string) + if !ok { + app.Logger.Println(" * * * ⛔️ User name is not a string") + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Log the authenticated user + app.Logger.Println(" * * * ✅ User authenticated:", userNameStr) + + // Proceed to the next handler + next.ServeHTTP(w, r) + }) +} + +// IsAuthenticated checks if the user is authenticated +func (app *App) IsAuthenticated(r *http.Request) (bool, string) { + fmt.Println("IsAuthenticated invoked") + session, err := app.Store.Get(r, "session") + if err != nil { + fmt.Println("Error getting session:", err) + app.Logger.Println(" * * * ⛔️ Error getting session:", err) + return false, "" + } + userName, ok := session.Values["userName"] + if !ok { + fmt.Println("User name not found in session") + app.Logger.Println(" * * * ⛔️ User name not found in session") + return false, "" + } + userNameStr, ok := userName.(string) + if !ok { + fmt.Println("User name is not a string") + app.Logger.Println(" * * * ⛔️ User name is not a string") + return false, "" + } + fmt.Println("User authenticated:", userNameStr) + app.Logger.Println(" * * * ✅ User authenticated:", userNameStr) + return true, userNameStr +} diff --git a/api/internal/auth/auth.go b/api/internal/auth/auth.go new file mode 100644 index 0000000..31024b4 --- /dev/null +++ b/api/internal/auth/auth.go @@ -0,0 +1,146 @@ +// * api/internal/auth/auth.go + +package auth + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/CristyNel/CV_project/tree/main/api/internal/app" + "github.com/CristyNel/CV_project/tree/main/api/internal/utils" + "golang.org/x/crypto/bcrypt" +) + +// SignupRequest represents the request payload for user signup +type SignupRequest struct { + Email string `json:"email"` + Password string `json:"password"` +} + +// LoginHandler handles the login requests. +func LoginHandler(app *app.App, w http.ResponseWriter, r *http.Request) { + app.Logger.Println(" * * * ☎️ Received request for /login") + + // Check if the request method is POST. If not, return a "Method Not Allowed" error. + if r.Method != http.MethodPost { + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + + // Parse the form data from the request. If there's an error, log it and return an "Internal Server Error". + if err := r.ParseForm(); err != nil { + app.Logger.Println(" * * * ⛔️ Error parsing form:", err) + http.Error(w, " * * * Parseform, ⛔️ Internal Server Error", http.StatusInternalServerError) + return + } + + // Extract the username and password from the parsed form data. + username := strings.TrimSpace(r.Form.Get("username")) + password := r.Form.Get("password") + + // Log the parsed username. + app.Logger.Println("Parsed form data - Username:", username) + + // Check if the username or password is empty. If so, log it and return a "Bad Request" error. + if username == "" || password == "" { + app.Logger.Println("Missing username or password") + http.Error(w, "Bad Request", http.StatusBadRequest) + return + } + + // Verify the login credentials using a utility function. If valid, set the session and return a success response. + if utils.VerifyLogin(app, username, password) { + // Create a response struct with the username. + response := struct { + Username string `json:"username"` + }{ + Username: username, + } + + // Set the response header to indicate JSON content. + w.Header().Set("Content-Type", "application/json") + // Set the session for the authenticated user. + utils.SetSession(app, username, w) + // Write a 200 OK status to the response. + w.WriteHeader(http.StatusOK) + // Encode the response struct as JSON and write it to the response. + json.NewEncoder(w).Encode(response) + } else { + // Log an unauthorized access attempt. + app.Logger.Println("Unauthorized access attempt by:", username) + // Return an "Unauthorized" error. + http.Error(w, "Unauthorized", http.StatusUnauthorized) + } +} + +// SignupHandler handles the signup requests. +func SignupHandler(app *app.App, w http.ResponseWriter, r *http.Request) { + app.Logger.Println(" * * * ☎️ Received request for /signup") + + if r.Method != http.MethodPost { + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + + // Check if the Content-Type is application/json + if r.Header.Get("Content-Type") != "application/json" { + http.Error(w, "Invalid Content-Type", http.StatusUnsupportedMediaType) + return + } + + // Decode the JSON request body + var req SignupRequest + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + app.Logger.Println(" * * * ⛔️ Error decoding JSON: ", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + email := strings.TrimSpace(req.Email) + password := req.Password + + if email == "" || password == "" { + http.Error(w, "Bad Request", http.StatusBadRequest) + return + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _, err = app.DB.Exec("INSERT INTO userlogin (email, password) VALUES (?,?)", email, hashedPassword) + if err != nil { + app.Logger.Println(" * * * ⛔️ Error querying database: ", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) + w.Write([]byte("User signup successfully")) +} + +// LogoutHandler handles the logout requests. +func LogoutHandler(app *app.App, w http.ResponseWriter, r *http.Request) { + app.Logger.Println(" * * * ☎️ Received request for /logout") + + if r.Method != http.MethodPost { + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + + //clear session + cookie := &http.Cookie{ + Name: "session", + Value: "", + Path: "/", + MaxAge: -1, + } + http.SetCookie(w, cookie) + + w.WriteHeader(http.StatusNoContent) + w.Write([]byte("User logout successfully")) +} diff --git a/api/internal/database/db.go b/api/internal/database/db.go index 5fc65c5..06f2ce0 100644 --- a/api/internal/database/db.go +++ b/api/internal/database/db.go @@ -1,4 +1,3 @@ -// * CV_project/api/internal/database/db.go package database import ( @@ -10,7 +9,7 @@ import ( "github.com/go-sql-driver/mysql" ) -// * connection to the database +// ConnectToDatabases establishes a connection to the database using the provided SQL open function. func ConnectToDatabases(sqlOpen func(driverName, dataSourceName string) (*sql.DB, error)) (*sql.DB, error) { getEnv := func(key, fallback string) string { if value, exists := os.LookupEnv(key); exists { @@ -43,7 +42,7 @@ func ConnectToDatabases(sqlOpen func(driverName, dataSourceName string) (*sql.DB fmt.Println("\n * Opening database connection...") db, err := sqlOpen("mysql", dsn) if err != nil { - fmt.Println("Error connecting:", err) + fmt.Println(" * * * ⛔️ Error connecting:", err) return nil, err } diff --git a/api/internal/database/db_test.go b/api/internal/database/db_test.go index a1944c7..51d0d52 100644 --- a/api/internal/database/db_test.go +++ b/api/internal/database/db_test.go @@ -12,32 +12,32 @@ import ( // TestConnectToDatabases tests the ConnectToDatabases function func TestConnectToDatabases(t *testing.T) { - // Set up environment variables for test - os.Setenv("MYSQL_USER", "testuser") - os.Setenv("MYSQL_PASSWORD", "testpassword") - os.Setenv("MYSQL_HOST", "localhost") - os.Setenv("MYSQL_PORT", "3306") - os.Setenv("MYSQL_DATABASE", "testdb") - - // Mock the database connection - db, mock, err := sqlmock.New() - assert.NoError(t, err) - defer db.Close() - - // Override the sqlOpen function to return the mock database - mockSQLOpen := func(driverName, dataSourceName string) (*sql.DB, error) { - return db, nil - } - - // Set up expectations for the mock database - mock.ExpectPing() - - // Call the function to test - conn, err := ConnectToDatabases(mockSQLOpen) - assert.NoError(t, err) - assert.NotNil(t, conn) - - // Ensure all expectations were met - err = mock.ExpectationsWereMet() - assert.NoError(t, err) -} \ No newline at end of file + // Set up environment variables for test + os.Setenv("MYSQL_USER", "testuser") + os.Setenv("MYSQL_PASSWORD", "testpassword") + os.Setenv("MYSQL_HOST", "localhost") + os.Setenv("MYSQL_PORT", "3306") + os.Setenv("MYSQL_DATABASE", "testdb") + + // Mock the database connection + db, mock, err := sqlmock.New() + assert.NoError(t, err) + defer db.Close() + + // Override the sqlOpen function to return the mock database + mockSQLOpen := func(driverName, dataSourceName string) (*sql.DB, error) { + return db, nil + } + + // Set up expectations for the mock database + mock.ExpectPing() + + // Call the function to test + conn, err := ConnectToDatabases(mockSQLOpen) + assert.NoError(t, err) + assert.NotNil(t, conn) + + // Ensure all expectations were met + err = mock.ExpectationsWereMet() + assert.NoError(t, err) +} diff --git a/api/internal/utils/utils.go b/api/internal/utils/utils.go index a856cba..ee61157 100644 --- a/api/internal/utils/utils.go +++ b/api/internal/utils/utils.go @@ -1,4 +1,3 @@ -// * CV_project/api/internal/utils/utils.go package utils import ( @@ -15,16 +14,17 @@ import ( // Template struct definition type Template struct { - Id int64 + ID int64 Path string } +// CookieHandler is used to encode and decode cookies var CookieHandler = securecookie.New( securecookie.GenerateRandomKey(64), // this key is used for signing securecookie.GenerateRandomKey(32), // this key is used for encryption ) -// * get environment variables +// GetEnv retrieves an environment variable or returns a fallback value func GetEnv(app *app.App, key, fallback string) string { if value, exists := os.LookupEnv(key); exists { return value @@ -54,7 +54,7 @@ func VerifyLogin(app *app.App, username, password string) bool { app.Logger.Println("No user found with username:", username) return false } - app.Logger.Println("Error querying database:", err) + app.Logger.Println(" * * * ⛔️ Error querying database:", err) return false } @@ -71,6 +71,7 @@ func VerifyLogin(app *app.App, username, password string) bool { return true } +// SetSession sets a session for the given user func SetSession(app *app.App, userName string, w http.ResponseWriter) { value := map[string]string{ "name": userName, @@ -78,7 +79,7 @@ func SetSession(app *app.App, userName string, w http.ResponseWriter) { encoded, err := CookieHandler.Encode("session", value) if err != nil { - app.Logger.Println("Error encoding cookie:", err) // Log encoding errors + app.Logger.Println(" * * * ⛔️ Error encoding cookie:", err) // Log encoding errors return } diff --git a/api/mock/mock_db.go b/api/mock/mock_db.go index 985aa24..4824c21 100644 --- a/api/mock/mock_db.go +++ b/api/mock/mock_db.go @@ -1,4 +1,3 @@ -// * CV_project/api/mock/mock_db.go package mock import ( diff --git a/api/mock/mock_logger.go b/api/mock/mock_logger.go index 2d93dd9..d9bcf74 100644 --- a/api/mock/mock_logger.go +++ b/api/mock/mock_logger.go @@ -1,10 +1,9 @@ -// * CV_project/api/mock/mock_logger.go package mock import ( "bytes" "time" - _ "time/tzdata" + _ "time/tzdata" // Blank import necessary for side effects "github.com/sirupsen/logrus" ) @@ -68,22 +67,27 @@ func NewMockLogger() *Logger { return &Logger{Logger: logger} } +// Print prints the log message func (m *Logger) Print(v ...interface{}) { m.Logger.Print(v...) } +// Printf prints the log message with formatting func (m *Logger) Printf(format string, v ...interface{}) { m.Logger.Printf(format, v...) } +// Println prints the log message with a newline func (m *Logger) Println(v ...interface{}) { m.Logger.Println(v...) } +// Fatal prints the log message and exits the program func (m *Logger) Fatal(v ...interface{}) { m.Logger.Fatal(v...) } +// Fatalf prints the log message with formatting and exits the program func (m *Logger) Fatalf(format string, v ...interface{}) { m.Logger.Fatalf(format, v...) } diff --git a/api/mock/mock_services.go b/api/mock/mock_services.go index 2feff89..6b2e42f 100644 --- a/api/mock/mock_services.go +++ b/api/mock/mock_services.go @@ -1,12 +1,11 @@ -// * api/mock/mock_services.go package mock import ( "net/http" ) -// MockServiceHandler is a mock handler for external services -func MockServiceHandler(w http.ResponseWriter, r *http.Request) { +// ServiceHandler is a mock handler for external services +func ServiceHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message":"Mock service response"}`)) } diff --git a/api/mock/mock_services_test.go b/api/mock/mock_services_test.go index dcc7d5c..756c3e6 100644 --- a/api/mock/mock_services_test.go +++ b/api/mock/mock_services_test.go @@ -14,7 +14,7 @@ func TestMockServiceHandler(t *testing.T) { assert.NoError(t, err) rr := httptest.NewRecorder() - handler := http.HandlerFunc(MockServiceHandler) + handler := http.HandlerFunc(ServiceHandler) handler.ServeHTTP(rr, req) diff --git a/api/mock/mock_session.go b/api/mock/mock_session.go index c60b1de..cf63cf0 100644 --- a/api/mock/mock_session.go +++ b/api/mock/mock_session.go @@ -1,4 +1,3 @@ -// * api/mock/mock_session.go package mock import ( diff --git a/api/mock/mock_session_test.go b/api/mock/mock_session_test.go index 6500f4b..318fe79 100644 --- a/api/mock/mock_session_test.go +++ b/api/mock/mock_session_test.go @@ -2,20 +2,20 @@ package mock import ( - "net/http" - "testing" + "net/http" + "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestNewMockSession(t *testing.T) { - rr := NewMockSession() + rr := NewMockSession() - // Simulate setting a session value - http.SetCookie(rr, &http.Cookie{Name: "session_id", Value: "mock_session_value"}) + // Simulate setting a session value + http.SetCookie(rr, &http.Cookie{Name: "session_id", Value: "mock_session_value"}) - // Check if the session value is set correctly - cookies := rr.Result().Cookies() - assert.NotEmpty(t, cookies, "Expected cookies to be set") - assert.Equal(t, "mock_session_value", cookies[0].Value, "Expected session value to be 'mock_session_value'") -} \ No newline at end of file + // Check if the session value is set correctly + cookies := rr.Result().Cookies() + assert.NotEmpty(t, cookies, "Expected cookies to be set") + assert.Equal(t, "mock_session_value", cookies[0].Value, "Expected session value to be 'mock_session_value'") +} diff --git a/api/models/user.go b/api/models/user.go index 13809d5..5eac9ff 100644 --- a/api/models/user.go +++ b/api/models/user.go @@ -2,7 +2,7 @@ package models -// Database rappresentation +// User represents a user in the system type User struct { ID int `json:"id"` Jobtitle string `json:"jobtitle"` diff --git a/api/routes/router.go b/api/routes/router.go index 0d21a9c..965536e 100644 --- a/api/routes/router.go +++ b/api/routes/router.go @@ -1,4 +1,3 @@ -// * CV_project/api/routes/router.go package routes import ( @@ -6,57 +5,57 @@ import ( "github.com/CristyNel/CV_project/tree/main/api/handlers" "github.com/CristyNel/CV_project/tree/main/api/internal/app" + "github.com/CristyNel/CV_project/tree/main/api/internal/auth" "github.com/gorilla/mux" ) +// InitializeRouter initializes the router and returns it func InitializeRouter(app *app.App) *mux.Router { r := mux.NewRouter() - // routes + // Public routes r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { handlers.Home(app, w, r) }).Methods("GET") - r.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { - handlers.HomeUsers(app, w, r) - }).Methods("GET") + r.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + auth.LoginHandler(app, w, r) + }).Methods("POST") + + r.HandleFunc("/signup", func(w http.ResponseWriter, r *http.Request) { + auth.SignupHandler(app, w, r) + }).Methods("POST") - r.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + // Protected routes + r.Handle("/users", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlers.ShowUsers(app, w, r) - }).Methods("GET") + }))).Methods("GET") - r.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.Handle("/user", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlers.CreateUser(app, w, r) - }).Methods("POST") + }))).Methods("POST") - r.HandleFunc("/user/{id}", func(w http.ResponseWriter, r *http.Request) { - handlers.ShowUser(app, w, r) // <-- Added this route - }).Methods("GET") + r.Handle("/user/{id}", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handlers.ShowUser(app, w, r) + }))).Methods("GET") - r.HandleFunc("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + r.Handle("/user/{id}", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlers.UpdateUser(app, w, r) - }).Methods("PUT") + }))).Methods("PUT") - r.HandleFunc("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + r.Handle("/user/{id}", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlers.DeleteUser(app, w, r) - }).Methods("DELETE") + }))).Methods("DELETE") - r.HandleFunc("/pdf", func(w http.ResponseWriter, r *http.Request) { + r.Handle("/pdf", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlers.GenerateTemplate(app, w, r) - }).Methods("GET") - - r.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { - handlers.LoginHandler(app, w, r) - }).Methods("POST") + }))).Methods("GET") - r.HandleFunc("/signup", func(w http.ResponseWriter, r *http.Request) { - handlers.SignupHandler(app, w, r) - }).Methods("POST") + r.Handle("/logout", app.RequireAuthentication(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + auth.LogoutHandler(app, w, r) + }))).Methods("POST") - r.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { - handlers.LogoutHandler(app, w, r) - }).Methods("POST") - // health check + // Health check r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("API is running")) diff --git a/api/tests/e2etests.yml b/api/tests/e2etests.yml index 8db90dc..b2c15aa 100644 --- a/api/tests/e2etests.yml +++ b/api/tests/e2etests.yml @@ -16,48 +16,48 @@ testcases: assertions: - result.statuscode ShouldEqual 200 - - name: HomeUsers - steps: - - type: http - method: GET - url: "http://{{.api_ip}}:8080/users" - assertions: - - result.statuscode ShouldEqual 200 + # - name: HomeUsers + # steps: + # - type: http + # method: GET + # url: "http://{{.api_ip}}:8080/users" + # assertions: + # - result.statuscode ShouldEqual 200 - - name: ShowUser - steps: - - type: http - method: GET - url: "http://{{.api_ip}}:8080/user/6" - assertions: - - result.statuscode ShouldEqual 200 + # - name: ShowUser + # steps: + # - type: http + # method: GET + # url: "http://{{.api_ip}}:8080/user/6" + # assertions: + # - result.statuscode ShouldEqual 200 - - name: ShowUsers - steps: - - type: http - method: GET - url: "http://{{.api_ip}}:8080/user" - assertions: - - result.statuscode ShouldEqual 200 + # - name: ShowUsers + # steps: + # - type: http + # method: GET + # url: "http://{{.api_ip}}:8080/user" + # assertions: + # - result.statuscode ShouldEqual 200 - - name: DeleteUser - steps: - - type: http - method: DELETE - url: "http://{{.api_ip}}:8080/user/6" - assertions: - - result.statuscode ShouldEqual 204 + # - name: DeleteUser + # steps: + # - type: http + # method: DELETE + # url: "http://{{.api_ip}}:8080/user/6" + # assertions: + # - result.statuscode ShouldEqual 204 - - name: UpdateUser - steps: - - type: http - method: PUT - url: "http://{{.api_ip}}:8080/user/6" - headers: - Content-Type: application/json - body: '{"name": "cristy buliga", "email": "cristybuliga@example.com"}' - assertions: - - result.statuscode ShouldEqual 200 + # - name: UpdateUser + # steps: + # - type: http + # method: PUT + # url: "http://{{.api_ip}}:8080/user/6" + # headers: + # Content-Type: application/json + # body: '{"name": "cristy buliga", "email": "cristybuliga@example.com"}' + # assertions: + # - result.statuscode ShouldEqual 200 - name: HealthCheck steps: diff --git a/bff/app/__init__.py b/bff/app/__init__.py index ef212d2..b629c56 100644 --- a/bff/app/__init__.py +++ b/bff/app/__init__.py @@ -5,7 +5,7 @@ from decouple import config # initialize flask app -app = Flask(__name__, template_folder='../templates', static_folder='../static') +app = Flask(__name__, template_folder="../templates", static_folder="../static") # load configuration from environment variable app.config.from_object(config("APP_SETTINGS")) @@ -16,4 +16,4 @@ migrate = Migrate(app, db) # import routes or additional app setup here -from . import app \ No newline at end of file +from . import app diff --git a/bff/app/app.py b/bff/app/app.py index 2c781c9..7c0e5fe 100644 --- a/bff/app/app.py +++ b/bff/app/app.py @@ -10,7 +10,17 @@ import os import requests -from flask import Flask, request, jsonify, render_template, send_from_directory, abort +from flask import ( + Flask, + request, + jsonify, + render_template, + send_from_directory, + abort, + session, + redirect, + url_for, +) from dotenv import load_dotenv from flask_cors import CORS @@ -25,6 +35,9 @@ app = Flask(__name__, template_folder=template_folder, static_folder=static_folder) CORS(app) +# Set the secret key for session management +app.secret_key = os.getenv("SECRET_KEY", "your_secret_key") + # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("flask.app") @@ -35,13 +48,110 @@ app.logger.info("\033[1;96;1m * * * 🅿️ static_folder = %s\033[0m", static_folder) +def is_authenticated(): + """Check if the user is authenticated.""" + return "username" in session + + +@app.before_request +def require_authentication(): + """Require authentication for all routes except login and static files.""" + logger.info("require_authentication invoked for endpoint: %s", request.endpoint) + + if "username" not in session: + logger.info("Session does not contain 'username'") + + if request.endpoint not in ["loginuser", "signupuser", "static"]: + logger.warning("User not authenticated, redirecting to login") + return redirect(url_for("loginuser")) + else: + logger.info( + "Endpoint '%s' does not require authentication", request.endpoint + ) + else: + logger.info("User authenticated for endpoint: %s", request.endpoint) + + +@app.route("/login", methods=["GET", "POST"]) +def loginuser(): + """Route to login a user""" + url = f"http://{IP}:{PORT}/login" + if request.method == "GET": + app.logger.info("\033[1;96;1m * * * 🔓 Login, GET to %s\033[0m", url) + return render_template("forms/loginform.html") + + if request.method == "POST": + app.logger.info("\033[1;96;1m * * * 🔓 Login, POST to %s\033[0m", url) + try: + r = requests.post( + url, + data=request.form, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=10, + ) + app.logger.info( + "\033[1;96;1m * * * 📢 Response status code: %d\033[0m ", r.status_code + ) + r.raise_for_status() + if r.status_code == 200: + session["username"] = request.form["username"] + app.logger.info("\033[1;96;1m * * * 🔑 Login successful\033[0m ") + return redirect(url_for("home")) # Redirect to the home page + app.logger.warning("Unexpected status code: %d", r.status_code) + return render_template( + "forms/loginform.html", error="Unexpected status code" + ) + except requests.exceptions.RequestException as e: + app.logger.error( + "\033[1;96;1m * * * 🆘 Login request failed: %s\033[0m", + e, + exc_info=True, + ) + return render_template("forms/loginform.html", error="Login request failed") + + abort(405) + + +@app.route("/users", methods=["GET"]) +def get_users(): + """Route to get users""" + if not is_authenticated(): + return redirect(url_for("loginuser")) + + url = f"http://{IP}:{PORT}/users" + app.logger.info("\033[1;96;1m * * * 👥 Fetching users from: %s\033[0m", url) + try: + response = requests.get(url, timeout=10) + app.logger.info( + "\033[1;96;1m * * * 📢 Response status code: %d\033[0m ", + response.status_code, + ) + response.raise_for_status() + users = response.json() + return jsonify(users) + except requests.exceptions.RequestException as e: + app.logger.error( + "\033[1;96;1m * * * 🆘 Request failed: %s\033[0m", + e, + exc_info=True, + ) + abort(500) + + @app.route("/", methods=["GET"]) def home(): """Home route to fetch and display users.""" + if not is_authenticated(): + return redirect(url_for("loginuser")) + url = f"http://{IP}:{PORT}/users" app.logger.info("\033[1;96;1m * * * 👥 Fetching users from: %s\033[0m", url) try: response = requests.get(url=url, timeout=12) + app.logger.info( + "\033[1;96;1m * * * 📢 Response status code: %d\033[0m ", + response.status_code, + ) response.raise_for_status() if response.status_code == 200 and response.text: data = response.json() @@ -61,32 +171,18 @@ def home(): return render_template("view/home.html", users=data) -@app.route("/users", methods=["GET"]) -def get_users(): - """Route to fetch and display all users.""" - url = f"http://{IP}:{PORT}/users" - app.logger.info("\033[1;96;1m * * * 👥 Fetching users from: %s\033[0m", url) - try: - response = requests.get(url=url, timeout=12) - response.raise_for_status() - if response.status_code == 200 and response.text: - data = response.json() - else: - app.logger.error("Received empty response or non-200 status") - return abort(500, description="Internal Server Error") - except requests.exceptions.RequestException as e: - app.logger.error("Request failed: %s", e, exc_info=True) - return abort(500, description="Internal Server Error") - return render_template("view/home.html", users=data) - - @app.route("/user", methods=["POST"]) def add_user(): """route to add a new user""" url = f"http://{IP}:{PORT}/user" app.logger.info("\033[1;96;1m * * * 👤 Add new user, POST to %s\033[0m", url) try: - requests.post(url=url, timeout=10) + requests.post( + url=url, + data=request.form, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=10, + ) except requests.exceptions.RequestException as e: app.logger.error( "\033[1;91;1m * * * 🆘 Add user request failed: %s\033[0m", e, exc_info=True @@ -116,25 +212,35 @@ def edit_user(user_id): @app.route("/postform", methods=["GET"]) def get_postform(): - """route to display the post form""" + """Route to display the post form""" + url = request.url + app.logger.info("\033[1;96;1m * * * 📄 Accessing post form at: %s\033[0m", url) + if not is_authenticated(): + return redirect(url_for("loginuser")) return render_template("forms/post_form.html") -@app.route("/editform", methods=["GET"]) -def get_user(): - """route to display the edit form""" - url = f"http://{IP}:{PORT}/user" - app.logger.info("\033[1;96;1m * * * 📝 Edit form, GET to %s\033[0m", url) +@app.route("/showuser/", methods=["GET"]) +def show_user(user_id): + """Route to show a user""" + url = f"http://{IP}:{PORT}/user/{user_id}" + app.logger.info("\033[1;96;1m * * * 👤 Fetching user from: %s\033[0m", url) try: - u = requests.get(url=url, timeout=10) - u.raise_for_status() - data = u.json() + response = requests.get(url, timeout=10) + app.logger.info( + "\033[1;96;1m * * * 📢 Response status code: %d\033[0m ", + response.status_code, + ) + response.raise_for_status() + user = response.json() + return jsonify(user) except requests.exceptions.RequestException as e: app.logger.error( - "\033[1;91;1m * * * 🆘 Get user request failed: %s\033[0m", e, exc_info=True + "\033[1;96;1m * * * 🆘 Request failed: %s\033[0m", + e, + exc_info=True, ) - abort(500, description="Internal Server Error") - return render_template("forms/edit_form.html", data=data) + abort(500) @app.route("/template1/", methods=["GET"]) @@ -236,58 +342,32 @@ def generate_template3(): return render_template("view/template3.html", data=data, pdf_data=pdf_data) -@app.route("/login", methods=["GET", "POST"]) -def loginuser(): - """Route to login a user""" - url = f"http://{IP}:{PORT}/login" - if request.method == "GET": - app.logger.info("\033[1;96;1m * * * 🔓 Login, GET to %s\033[0m", url) - return render_template("forms/loginform.html") - - if request.method == "POST": - app.logger.info("\033[1;96;1m * * * 🔓 Login, POST to %s\033[0m", url) - try: - # ? app.logger.info("\033[1;96;1m * * * 🔏 Form data: %s\033[0m ", request.form) - r = requests.post( - url, - data=request.form, - headers={"Content-Type": "application/x-www-form-urlencoded"}, - timeout=10, - ) - app.logger.info( - "\033[1;96;1m * * * 📢 Response status code: %d\033[0m ", r.status_code - ) - r.raise_for_status() - if r.status_code == 200: - app.logger.info("\033[1;96;1m * * * 🔑 Login successful\033[0m ") - return render_template("view/greet.html") # Ensure this template exists - app.logger.warning("Unexpected status code: %d", r.status_code) - return render_template("forms/loginform.html") - except requests.exceptions.RequestException as e: - app.logger.error( - "\033[1;96;1m * * * 🆘 Login request failed: %s\033[0m", - e, - exc_info=True, - ) - return render_template("forms/loginform.html") - - abort(405) - - @app.route("/logout", methods=["GET"]) def logoutuser(): - """route to logout a user""" + """Route to logout a user""" url = f"http://{IP}:{PORT}/logout" app.logger.info("\033[1;96;1m * * * 🔓 Logout, GET to %s\033[0m", url) try: - r = requests.post(url, timeout=10) + r = requests.get(url, timeout=10) + app.logger.info( + "\033[1;96;1m * * * 📢 Response status code: %d\033[0m ", r.status_code + ) r.raise_for_status() + if r.status_code == 200: + session.pop("username", None) + app.logger.info("\033[1;96;1m * * * 🔑 Logout successful\033[0m ") + return render_template("forms/loginform.html") + app.logger.warning("Unexpected status code: %d", r.status_code) + return render_template("forms/loginform.html") except requests.exceptions.RequestException as e: app.logger.error( - "\033[1;91;1m * * * 🆘 Logout request failed: %s\033[0m", e, exc_info=True + "\033[1;96;1m * * * 🆘 Logout request failed: %s\033[0m", + e, + exc_info=True, ) - return abort(500, description="Internal Server Error") - return render_template("view/home.html") + return render_template("forms/loginform.html") + + abort(405) @app.route("/signup", methods=["GET", "POST"])