add validation to login form

This commit is contained in:
Ruidy 2024-06-08 13:23:43 +02:00
parent eef6c85fe3
commit 0db50b2268
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
8 changed files with 348 additions and 13 deletions

View file

@ -0,0 +1,181 @@
package server
import (
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
"github.com/rjNemo/rentease/internal/booking"
)
type BookingInfo struct {
Content string `json:"content"`
}
func handleSync() echo.HandlerFunc {
return func(c echo.Context) error {
x := c.Request().Body
body, err := io.ReadAll(x)
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
b, err := parseBooking(string(body))
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
log.Warnf("%+v", b)
return c.String(http.StatusCreated, "👍")
}
}
func parseBooking(jsonString string) (*booking.Booking, error) {
//jsonString = `"{\"content\":\" Date d'arrivée ven.\u00a024\u00a0mai\u00a02024 Date de départ lun.\u00a027\u00a0mai\u00a02024 Durée de séjour\u00a0: 3 nuits Nombre de personnes\u00a0: \\n \\n Nombre d'hébergements \\n 1\\n Montant total € 186 Nom du client\u00a0: \\n Katrin Anschütz\\n \\n mq\\n \\n kansch.199923@guest.booking.com\\n Contactez vos clients\u00a0! Indiquez-leur l'heure à laquelle vous souhaitez les accueillir ou l'endroit où ils récupéreront leurs clés. Un simple appel suffit\u00a0: Afficher le numéro de téléphone Vous pouvez également leur envoyer un e-mail ou un message. Langue préférée \\n allemand\\n Canal\u00a0: Booking.com Code IATA/TIDS\u00a0: \\n PC029090\\n Numéro de réservation\u00a0: \\n 4489841169\\n Montant soumis à commission\u00a0: € 177 Reçu jeu.\u00a016\u00a0mai\u00a02024 Commission\u00a0: € 31,86 Bloc-notes (usage interne) Ajoutez une note ici Heure d'arrivée approximative\u00a0: Entre 16:00 et 17:00 \\n\\n Maison 1 Chambre (T2 - VillaFleurie au bourg du Gosier)\\n € 186 ven.\u00a024\u00a0mai\u00a02024 lun.\u00a027\u00a0mai\u00a02024 Nom du client \\n Katrin Anschütz\\n Occupation maximum 2 adultes, 2 enfants (3 personnes max.) Photo de l'hébergement Date Tarif Tarif par nuit \\n 24 - 25 mai\\n \\n Standard Rate\\n € 59\\n 25 - 26 mai\\n \\n Standard Rate\\n € 59\\n 26 - 27 mai\\n \\n Standard Rate\\n € 59Sous-total € 177\\n Taxe de séjour\\n € 1.50 par personne et par nuit € 9Tarif total de l'hébergement € 186 Le tarif comprend 8.9 % de TVA Conversation avec le client \\n Aucun message\\n \\n Les conversations avec vos clients apparaîtront ici.\\n Booking.com reçoit tous les messages écrits ici et les traite selon sa Charte de confidentialité et informations sur les cookies Conditions \"}"}`
var bookingInfo BookingInfo
err := json.Unmarshal([]byte(jsonString), &bookingInfo)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
return nil, err
}
content := bookingInfo.Content
log.Info(extractData(content))
arrivalDate := extractDate(`Date de d'arrivée `, content)
log.Info(arrivalDate)
departureDate := extractDate(`Date de départ `, content)
log.Info(departureDate)
stayLength := extractInt(`Durée de séjour : (\d+) nuits`, content)
totalAmount := extractFloat(`Montant total € (\d+)`, content)
customerName := extractString(`Nom du client : \\n\s*(\S.+)\\n`, content)
commissionAmount := extractFloat(`Commission : € (\d+,\d+)`, content)
item := extractString(`Maison 1 Chambre \((T2|T3) -`, content)
standardRate := extractFloat(`Standard Rate\\n\s*€ (\d+)`, content)
taxesAmount := extractFloat(`Taxe de séjour\\n\s*€ (\d+,\d+)`, content)
result := map[string]any{
"from": formatDate(arrivalDate),
"to": formatDate(departureDate),
"stay_length": stayLength,
"total_amount": totalAmount,
"customer_name": customerName,
"commission_amount": commissionAmount,
"item": item,
"price": standardRate,
"taxes_amount": taxesAmount,
}
jsonResult, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("error unmarshalling JSON: %w", err)
}
fmt.Println(string(jsonResult))
b := new(booking.Booking)
if err = json.Unmarshal(jsonResult, b); err != nil {
return nil, fmt.Errorf("error unmarshalling JSON: %w", err)
}
return b, nil
}
func extractDate(pattern, content string) string {
cleanedInput := strings.ReplaceAll(content, "\u00a0", " ")
re := regexp.MustCompile(pattern + `\w+\.\s*\d{1,2}\s\w+\.\s\d{4}`)
dateMatch := re.FindString(cleanedInput)
if dateMatch == "" {
fmt.Println("date not found")
return ""
}
// Regular expression to remove the prefix
rePrefix := regexp.MustCompile(pattern + `\w+\.\s*`)
dateString := rePrefix.ReplaceAllString(dateMatch, "")
return dateString
}
func extractInt(pattern, content string) int {
re := regexp.MustCompile(pattern)
match := re.FindStringSubmatch(content)
if len(match) > 1 {
val, err := strconv.Atoi(match[1])
if err == nil {
return val
}
}
return 0
}
func extractFloat(pattern, content string) float64 {
re := regexp.MustCompile(pattern)
match := re.FindStringSubmatch(content)
if len(match) > 1 {
val, err := strconv.ParseFloat(strings.ReplaceAll(match[1], ",", "."), 64)
if err == nil {
return val
}
}
return 0.0
}
func extractString(pattern, content string) string {
re := regexp.MustCompile(pattern)
match := re.FindStringSubmatch(content)
if len(match) > 1 {
return strings.TrimSpace(match[1])
}
return ""
}
func formatDate(date string) string {
months := map[string]string{
"janv.": "01", "fév": "02", "mar": "03", "avr": "04",
"mai": "05", "jun": "06", "jul": "07", "aoû": "08",
"sep": "09", "oct": "10", "nov": "11", "déc": "12",
}
re := regexp.MustCompile(`(\d+)\s([^\s]+)\s(\d{4})`)
match := re.FindStringSubmatch(date)
if len(match) > 3 {
day := match[1]
month := months[match[2]]
year := match[3][2:]
return fmt.Sprintf("%02s/%02s/%s", day, month, year)
}
return ""
}
func extractData(content string) (string, string, string, string, string, string, string, string, string) {
// Define regex patterns for each field
arrivalDatePattern := regexp.MustCompile(`Date d'arrivée ven\\u00a0(\d{2}\\u00a0mai\\u00a020\d{2})`)
departureDatePattern := regexp.MustCompile(`Date de départ lun\\u00a0(\d{2}\\u00a0mai\\u00a020\d{2})`)
commissionPattern := regexp.MustCompile(`Commission\\u00a0: € (\d{2},\d{2})`)
customerNamePattern := regexp.MustCompile(`Nom du client\\u00a0: \\n\s+([\w\s]+)\\n`)
itemNamePattern := regexp.MustCompile(`Maison 1 Chambre $begin:math:text$([^)]+)$end:math:text$`)
itemPricePattern := regexp.MustCompile(`Tarif total de l'hébergement € (\d{3})`)
lengthOfStayPattern := regexp.MustCompile(`Durée de séjour\\u00a0: (\d{1}) nuits`)
amountOfTaxesPattern := regexp.MustCompile(`Taxe de séjour\\n\\u00a0€ (\d\.\d{2})`)
totalPricePattern := regexp.MustCompile(`Montant total € (\d{3})`)
// Extract data using regex
arrivalDate := arrivalDatePattern.FindStringSubmatch(content)
departureDate := departureDatePattern.FindStringSubmatch(content)
commission := commissionPattern.FindStringSubmatch(content)
customerName := customerNamePattern.FindStringSubmatch(content)
itemName := itemNamePattern.FindStringSubmatch(content)
itemPrice := itemPricePattern.FindStringSubmatch(content)
lengthOfStay := lengthOfStayPattern.FindStringSubmatch(content)
amountOfTaxes := amountOfTaxesPattern.FindStringSubmatch(content)
totalPrice := totalPricePattern.FindStringSubmatch(content)
// Return extracted data
return arrivalDate[1], departureDate[1], commission[1], customerName[1], itemName[1], itemPrice[1], lengthOfStay[1], amountOfTaxes[1], totalPrice[1]
}

View file

@ -18,7 +18,7 @@ const (
func handleLoginPage() echo.HandlerFunc {
return func(c echo.Context) error {
return renderTempl(c, http.StatusOK, view.Login())
return renderTempl(c, http.StatusOK, view.Login(view.LoginFormViewModel{}))
}
}
@ -34,8 +34,16 @@ func handleLogin(as *auth.Service) echo.HandlerFunc {
HttpOnly: true,
}
if !as.Authenticate(c.FormValue("email"), c.FormValue("password")) {
return hxRedirect(c, http.StatusTemporaryRedirect, routeLogin)
email := c.FormValue("email")
password := c.FormValue("password")
if !as.Authenticate(email, password) {
lfvm := view.LoginFormViewModel{
Email: email,
Password: password,
Errors: make(map[string]string),
}
lfvm.Errors["credentials"] = "invalid credentials"
return renderTempl(c, http.StatusUnauthorized, view.LoginForm(lfvm))
}
sess.Values["foo"] = "bar"

View file

@ -11,6 +11,16 @@ templ BaseLayout() {
<link rel="icon" href="/static/icons/favicon-main.png"/>
<link rel="stylesheet" href="/static/css/pico.min.css"/>
<script src="/static/js/htmx.js" defer></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("htmx:beforeSwap", (e) => {
if([422,401].includes(e.detail.xhr.status) ){
e.detail.shouldSwap = true
e.detail.isError = false
}
})
})
</script>
</head>
<body hx-boost="true" style="display: flex; flex-direction: column; min-height: 100vh;">
<nav class="container-fluid">

View file

@ -23,7 +23,7 @@ func BaseLayout() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><title>RentEase | Your Property Management System</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"AI assistant to help you improve your management\"><link rel=\"icon\" href=\"/static/icons/favicon-main.png\"><link rel=\"stylesheet\" href=\"/static/css/pico.min.css\"><script src=\"/static/js/htmx.js\" defer></script></head><body hx-boost=\"true\" style=\"display: flex; flex-direction: column; min-height: 100vh;\"><nav class=\"container-fluid\"><ul><li><a href=\"/\"><b>🏨 RentEase </b></a></li></ul><ul><li><a href=\"/bookings\">Bookings</a></li><li><a href=\"/reports\">Reports</a></li><li><a href=\"/bookings/new\" role=\"button\">New Booking</a></li></ul></nav><main class=\"container\" style=\"flex: 1 0 auto;\">")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><title>RentEase | Your Property Management System</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"AI assistant to help you improve your management\"><link rel=\"icon\" href=\"/static/icons/favicon-main.png\"><link rel=\"stylesheet\" href=\"/static/css/pico.min.css\"><script src=\"/static/js/htmx.js\" defer></script><script>\n document.addEventListener(\"DOMContentLoaded\", () => {\n document.addEventListener(\"htmx:beforeSwap\", (e) => {\n if([422,401].includes(e.detail.xhr.status) ){\n e.detail.shouldSwap = true\n e.detail.isError = false\n }\n })\n })\n </script></head><body hx-boost=\"true\" style=\"display: flex; flex-direction: column; min-height: 100vh;\"><nav class=\"container-fluid\"><ul><li><a href=\"/\"><b>🏨 RentEase </b></a></li></ul><ul><li><a href=\"/bookings\">Bookings</a></li><li><a href=\"/reports\">Reports</a></li><li><a href=\"/bookings/new\" role=\"button\">New Booking</a></li></ul></nav><main class=\"container\" style=\"flex: 1 0 auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -4,7 +4,7 @@ import (
"github.com/rjNemo/rentease/internal/view/layout"
)
templ Login() {
templ Login(lfvm LoginFormViewModel) {
@layout.BaseLayout() {
<section>
<article>
@ -12,12 +12,7 @@ templ Login() {
<h1>Welcome</h1>
<h2>Manage your rental property</h2>
</hgroup>
<form hx-post="/">
<input type="email" name="email" placeholder="john@email.com" aria-label="email" autocomplete="email" autofocus required=""/>
<input type="password" name="password" placeholder="p4Ssw0rD" aria-label="password" autocomplete="password" required=""/>
<button type="submit">Log in</button>
<small> Registration is disabled. Please ask an admin to activate it. </small>
</form>
@LoginForm(lfvm)
</article>
</section>
}

View file

@ -0,0 +1,28 @@
package view
type LoginFormViewModel struct {
Email string
Password string
Errors map[string]string
}
func isFormError(data map[string]string, key string) bool {
_, ok := data[key]
return ok
}
templ LoginForm(lfvm LoginFormViewModel) {
<form hx-post="/">
<input type="email" name="email" value={ lfvm.Email } placeholder="john@email.com" aria-label="email" if isFormError(lfvm.Errors, "credentials") {
aria-invalid="true"
} autocomplete="email" autofocus required=""/>
<input type="password" name="password" value={ lfvm.Password } placeholder="p4Ssw0rD" aria-label="password" if isFormError(lfvm.Errors, "credentials") {
aria-invalid="true"
} autocomplete="password" required=""/>
if msg, ok := lfvm.Errors["credentials"]; ok {
<small>Authentication error: { msg }</small>
}
<button type="submit">Log in</button>
<small>Registration is disabled. Please ask an admin to activate it.</small>
</form>
}

View file

@ -0,0 +1,105 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.543
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
type LoginFormViewModel struct {
Email string
Password string
Errors map[string]string
}
func isFormError(data map[string]string, key string) bool {
_, ok := data[key]
return ok
}
func LoginForm(lfvm LoginFormViewModel) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form hx-post=\"/\"><input type=\"email\" name=\"email\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(lfvm.Email))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"john@email.com\" aria-label=\"email\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if isFormError(lfvm.Errors, "credentials") {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" aria-invalid=\"true\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" autocomplete=\"email\" autofocus required=\"\"> <input type=\"password\" name=\"password\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(lfvm.Password))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" placeholder=\"p4Ssw0rD\" aria-label=\"password\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if isFormError(lfvm.Errors, "credentials") {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" aria-invalid=\"true\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" autocomplete=\"password\" required=\"\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if msg, ok := lfvm.Errors["credentials"]; ok {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<small>Authentication error: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(msg)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/login_form.templ`, Line: 22, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</small>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button type=\"submit\">Log in</button> <small>Registration is disabled. Please ask an admin to activate it.</small></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View file

@ -14,7 +14,7 @@ import (
"github.com/rjNemo/rentease/internal/view/layout"
)
func Login() templ.Component {
func Login(lfvm LoginFormViewModel) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@ -33,7 +33,15 @@ func Login() templ.Component {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section><article><hgroup><h1>Welcome</h1><h2>Manage your rental property</h2></hgroup><form hx-post=\"/\"><input type=\"email\" name=\"email\" placeholder=\"john@email.com\" aria-label=\"email\" autocomplete=\"email\" autofocus required=\"\"> <input type=\"password\" name=\"password\" placeholder=\"p4Ssw0rD\" aria-label=\"password\" autocomplete=\"password\" required=\"\"> <button type=\"submit\">Log in</button></form></article></section>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section><article><hgroup><h1>Welcome</h1><h2>Manage your rental property</h2></hgroup>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = LoginForm(lfvm).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}