diff --git a/authentication/auth.go b/authentication/auth.go index ef63192..017b97f 100644 --- a/authentication/auth.go +++ b/authentication/auth.go @@ -78,10 +78,10 @@ func LogoutSession(writer http.ResponseWriter, request *http.Request, sessionSto http.Redirect(writer, request, redirectUrl, http.StatusTemporaryRedirect) } -func Middleware(store *sessions.CookieStore, excludedPrefixes []string, redirectUrl string) MiddlewareFunc { +func Middleware(store *sessions.CookieStore, excludedPrefixes []string, redirect LanguagePaths) MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - Debug("Authenticating") + Debug("Authenticating for page", r.URL.Path) if r.Method == http.MethodOptions { next.ServeHTTP(w, r) return @@ -92,16 +92,32 @@ func Middleware(store *sessions.CookieStore, excludedPrefixes []string, redirect return } } - ok, r, err := SetUserIdAndEmailFromSessionStore(r, store) + for _, path := range redirect { + if strings.HasPrefix(r.URL.Path, path) { + next.ServeHTTP(w, r) + return + } + } + ok, newR, err := SetUserIdAndEmailFromSessionStore(r, store) if err != nil { LogError("Failed to set user id and email from session: %v", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } if ok { - next.ServeHTTP(w, r) + next.ServeHTTP(w, newR) } else { - Debug("Redirecting to login because there is no session") + redirectUrl := En.GetPath() + redirect[En] + for lang, path := range redirect { + if strings.HasPrefix(r.URL.Path, lang.GetPath()) { + redirectUrl = lang.GetPath() + path + break + } + } + if redirectUrl == "" { + redirectUrl = "/" + } + Debug("Redirecting to ", redirectUrl, "because there is no session") http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) } }) diff --git a/authentication/login_page.go b/authentication/login_page.go index e3870d5..90b96e3 100644 --- a/authentication/login_page.go +++ b/authentication/login_page.go @@ -23,7 +23,10 @@ func NewLoginPage(e *echo.Echo, createPage ersteller_lib.CreatePageFunc, googleL func (l *LoginPage) Render(c echo.Context) error { return l.createPage(c, ersteller_lib.PageWebsiteMetaData{ - Title: "Login", + Title: ersteller_lib.NewI18nText(map[ersteller_lib.Language]string{ + ersteller_lib.En: "Login", + ersteller_lib.De: "Anmelden", + }), HideNavigation: true, }, l.getLoginPage()) } diff --git a/i18n.go b/i18n.go index b3a4ea6..468793c 100644 --- a/i18n.go +++ b/i18n.go @@ -11,6 +11,10 @@ func (t Language) GetPath() string { return "/" + string(t) } +func (t Language) ToString() string { + return string(t) +} + type GlobalI18nTexts interface { Add(text I18nText) GlobalI18nTexts GetAllTexts() []I18nText diff --git a/starter/about/about.go b/starter/about/about.go index 700d2b2..6fd3ebb 100644 --- a/starter/about/about.go +++ b/starter/about/about.go @@ -8,7 +8,7 @@ import ( ) const AboutPath = "/about" -const AboutPathDe = "/de/ueber-uns" +const AboutPathDe = "/ueber-uns" var aboutTexts *AboutTexts diff --git a/starter/contact/contact.go b/starter/contact/contact.go index 7194bdd..3ec5f2f 100644 --- a/starter/contact/contact.go +++ b/starter/contact/contact.go @@ -8,7 +8,7 @@ import ( ) const ContactPath = "/contact" -const ContactPathDe = "/de/kontakt" +const ContactPathDe = "/kontakt" var contactTexts *ContactTexts diff --git a/starter/db/.gitignore b/starter/db/.gitignore new file mode 100644 index 0000000..98e6ef6 --- /dev/null +++ b/starter/db/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/starter/go.mod b/starter/go.mod index f909913..6161d6f 100644 --- a/starter/go.mod +++ b/starter/go.mod @@ -5,7 +5,7 @@ go 1.25.0 require ( entgo.io/ent v0.14.5 ersteller-lib v0.0.0 - github.com/jackc/pgx/v5 v5.7.6 + github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 golang.org/x/oauth2 v0.30.0 maragu.dev/gomponents v1.2.0 @@ -13,18 +13,25 @@ require ( require ( ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect + github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-openapi/inflect v0.19.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.6.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/hcl/v2 v2.18.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.6 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/labstack/echo/v4 v4.13.4 // indirect github.com/labstack/gommon v0.4.2 // indirect + github.com/markbates/goth v1.81.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect diff --git a/starter/go.sum b/starter/go.sum index 8184f2e..d2231a0 100644 --- a/starter/go.sum +++ b/starter/go.sum @@ -1,7 +1,11 @@ ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 h1:E0wvcUXTkgyN4wy4LGtNzMNGMytJN8afmIWXJVMi4cc= ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4= entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= @@ -11,12 +15,26 @@ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +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/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo= github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -29,10 +47,18 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/markbates/goth v1.81.0 h1:XVcCkeGWokynPV7MXvgb8pd2s3r7DS40P7931w6kdnE= +github.com/markbates/goth v1.81.0/go.mod h1:+6z31QyUms84EHmuBY7iuqYSxyoN3njIgg9iCF/lR1k= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -43,6 +69,10 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/starter/login/login.go b/starter/login/login.go new file mode 100644 index 0000000..2a5bf42 --- /dev/null +++ b/starter/login/login.go @@ -0,0 +1,99 @@ +package login + +import ( + . "ersteller-lib" + + . "maragu.dev/gomponents" + . "maragu.dev/gomponents/html" +) + +const LoginPath = "/login" +const LoginPathDe = "/anmelden" + +var loginTexts *LoginTexts + +type LoginTexts struct { + PageTitle I18nText + PageDescription I18nText + HeroTitle I18nText + HeroDescription I18nText + GoogleLoginBtn I18nText +} + +type Page struct { + createPage CreateHtmxPageFunc + ViewRoute HtmxRoute +} + +func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath) *Page { + if loginTexts == nil { + createLoginTexts() + } + page := &Page{ + createPage: createPage, + } + page.ViewRoute = NewHtmxGetRoute(page.View, LanguagePaths{ + En: LoginPath, + De: LoginPathDe, + }).SetActivePath(path) + page.ViewRoute.Add(server) + return page +} + +func createLoginTexts() { + loginTexts = &LoginTexts{ + PageTitle: NewI18nText(map[Language]string{ + En: "Login", + De: "Anmelden", + }), + PageDescription: NewI18nText(map[Language]string{ + En: "Sign in to your account", + De: "Melden Sie sich bei Ihrem Konto an", + }), + HeroTitle: NewI18nText(map[Language]string{ + En: "Sign In", + De: "Anmelden", + }), + HeroDescription: NewI18nText(map[Language]string{ + En: "Please sign in to access your account.", + De: "Bitte melden Sie sich an, um auf Ihr Konto zuzugreifen.", + }), + GoogleLoginBtn: NewI18nText(map[Language]string{ + En: "Sign in with Google", + De: "Mit Google anmelden", + }), + } +} + +func (p *Page) getMetaData() PageWebsiteMetaData { + return PageWebsiteMetaData{ + Title: loginTexts.PageTitle, + Lang: En, + Description: loginTexts.PageDescription, + HideNavigation: true, + } +} + +func (p *Page) View(c HtmxContext) { + content := LoginContent(c.GetLanguage()) + p.createPage(c, p.getMetaData(), content) +} + +func LoginContent(language Language) Group { + return []Node{ + Div(Class("hero-section login-section"), + H1(Class("hero-title"), Text(loginTexts.HeroTitle.FromLang(language))), + P(Class("hero-description"), Text(loginTexts.HeroDescription.FromLang(language))), + Div(Class("login-buttons"), + A( + Href("/login/google"), + Button( + Class("btn btn-primary google-login-btn"), + Type("button"), + Text(loginTexts.GoogleLoginBtn.FromLang(language)), + ), + ), + ), + ), + } +} diff --git a/starter/routes/routing.go b/starter/routes/routing.go index 8504208..f3afa6f 100644 --- a/starter/routes/routing.go +++ b/starter/routes/routing.go @@ -9,6 +9,7 @@ import ( "ersteller-lib/starter/env" "ersteller-lib/starter/google" "ersteller-lib/starter/index" + "ersteller-lib/starter/login" "ersteller-lib/starter/page" "net/http" @@ -69,9 +70,20 @@ func CreateApi(environment env.Environment, db *ent.Client) http.Handler { _ = about.NewPage(createPageFunc, server, &aboutActivePath) _ = contact.NewPage(createPageFunc, server, &contactActivePath) + // Create Login page + loginPaths := LanguagePaths{ + En: login.LoginPath, + De: login.LoginPathDe, + } + loginActivePath := NewActivePath(map[Language]string{ + En: "Login", + De: "Anmelden", + }, loginPaths) + _ = login.NewPage(createPageFunc, server, &loginActivePath) + serverWithMiddleWare := UseMiddleware(server, LoggingMiddleware, MakeGzipHandler, authentication.Middleware(sessionStore, - []string{"/login", google.GoogleLogin, google.GoogleLoginCallback}, "/")) + []string{"/de" + login.LoginPathDe, "/en" + authentication.LoginPath, google.GoogleLogin, google.GoogleLoginCallback, "/static"}, loginPaths)) return serverWithMiddleWare }