From a1b7c5921b8055d5f5bd6416b96b824bfdf9ddc6 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Sun, 4 Feb 2024 12:48:43 +0100 Subject: [PATCH] initial project structure --- .env | 2 + .gitignore | 26 ++++ Makefile | 20 +++ README.md | 18 +++ go.mod | 32 ++++ go.sum | 65 ++++++++ internal/server/handlers.go | 271 ++++++++++++++++++++++++++++++++++ internal/server/server.go | 92 ++++++++++++ internal/views/base.templ | 27 ++++ internal/views/base_templ.go | 70 +++++++++ internal/views/index.templ | 7 + internal/views/index_templ.go | 59 ++++++++ main.go | 37 +++++ 13 files changed, 726 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/server/handlers.go create mode 100644 internal/server/server.go create mode 100644 internal/views/base.templ create mode 100644 internal/views/base_templ.go create mode 100644 internal/views/index.templ create mode 100644 internal/views/index_templ.go create mode 100644 main.go diff --git a/.env b/.env new file mode 100644 index 0000000..37341b1 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +DATABASE_URL="user=ruidy database=villafleurie" +PORT=8000 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6d7813 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work +.DS_Store +.AppleDouble +.LSOverride +Icon +._* +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +tmp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d60cf78 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +NAME=rentease +PORT=8000 +DB_USER=ruidy +DB_NAME=villafleurie + +build: templ + @docker build -t ${NAME}:latest . +run: build + @docker run -p ${PORT}:${PORT} -e DATABASE_URL="host=docker.for.mac.host.internal user=${DB_USER} database=${DB_NAME}" -e PORT=${PORT} ${NAME} +dev: templ + @air cmd/main.go +templ: lint + @templ generate +format: + @templ fmt . + @go fmt ./... +lint: format + @golangci-lint run ./... + +.PHONY: build run dev templ format lint diff --git a/README.md b/README.md new file mode 100644 index 0000000..0cc9185 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# RentEase + +Manage your holiday rental + +## To Do + +- [ ] Create a booking +- [ ] Add line items +- [ ] Read from the database +- [ ] Build the pdf invoice + +## Built With + +- Go +- Htmx +- Templ +- PostgreSQL + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d19a9d8 --- /dev/null +++ b/go.mod @@ -0,0 +1,32 @@ +module github.com/rjNemo/rentease + +go 1.21.0 + +require ( + github.com/a-h/templ v0.2.543 + github.com/joho/godotenv v1.5.1 + github.com/labstack/echo/v4 v4.11.4 + gorm.io/driver/postgres v1.5.4 + gorm.io/gorm v1.25.6 +) + +require ( + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.5.3 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..798b586 --- /dev/null +++ b/go.sum @@ -0,0 +1,65 @@ +github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk= +github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA= +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/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= +github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/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= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= +gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/internal/server/handlers.go b/internal/server/handlers.go new file mode 100644 index 0000000..7de8555 --- /dev/null +++ b/internal/server/handlers.go @@ -0,0 +1,271 @@ +package server + +import ( + "net/http" + + "github.com/labstack/echo/v4" + + "github.com/rjNemo/rentease/internal/views" +) + +func (s Server) handleHomePage() echo.HandlerFunc { + return func(ctx echo.Context) error { + component := views.Index() + return s.renderTempl(ctx, http.StatusOK, component) + } +} + +//func (s Server) handleLoginPage() echo.HandlerFunc { +// return func(c echo.Context) error { +// qs := c.QueryParams() +// errs := qs["err"] +// +// component := views.LoginPage(errs) +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleLogin() echo.HandlerFunc { +// return func(c echo.Context) error { +// email := c.FormValue("email") +// pwd := c.FormValue("password") +// +// user, err := s.us.SignIn(email, pwd) +// if err != nil { +// return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s?err=invalid+credentials", constants.RouteLogin)) +// } +// if err = writeCookie(c, user.Id, email, user.PaymentValid); err != nil { +// return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s?err=invalid+credentials", constants.RouteLogin)) +// } +// return c.Redirect(http.StatusFound, constants.RouteHome) +// } +//} +// +//func (s Server) handleLogout() echo.HandlerFunc { +// return func(c echo.Context) error { +// cookie := new(http.Cookie) +// cookie.Name = cookieName +// cookie.Value = "" +// cookie.MaxAge = 0 +// c.SetCookie(cookie) +// return c.Redirect(http.StatusFound, constants.RouteLogin) +// } +//} +// +//func (s Server) handleHomePage() echo.HandlerFunc { +// return func(c echo.Context) error { +// user := c.Get("user").(services.User) +// +// component := views.Home(&user, s.ms.List(user.Id)) +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleCreateMeetingNote() echo.HandlerFunc { +// return func(c echo.Context) error { +// memberId, err := strconv.Atoi(c.FormValue("member_id")) +// if err != nil { +// return err +// } +// +// audioFile, err := c.FormFile("audio_file") +// if err != nil { +// return err +// } +// +// err = s.ms.CreateNote(audioFile, memberId) +// if err != nil { +// return err +// } +// +// return c.Redirect(http.StatusFound, fmt.Sprintf("%s/%d", constants.RouteTeam, memberId)) +// } +//} +// +//func (s Server) handleTeamPage() echo.HandlerFunc { +// return func(c echo.Context) error { +// user := c.Get("user").(services.User) +// +// component := views.Team(&user, s.ms.List(user.Id)) +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleCreateTeamMember() echo.HandlerFunc { +// return func(c echo.Context) error { +// name := c.FormValue("name") +// user := c.Get("user").(services.User) +// if _, err := s.ms.Create(name, user.Id); err != nil { +// return err +// } +// return c.Redirect(http.StatusFound, constants.RouteTeam) +// } +//} +// +//func (s Server) handleUpdateTeamMember() echo.HandlerFunc { +// return func(c echo.Context) error { +// name := c.FormValue("name") +// description := c.FormValue("description") +// memberId, _ := strconv.Atoi(c.Param("id")) +// +// if _, err := s.ms.Update(name, description, memberId); err != nil { +// return err +// } +// return c.Redirect(http.StatusFound, fmt.Sprintf("%s/%d", constants.RouteTeam, memberId)) +// } +//} +// +//func (s Server) handleTeamMemberPage() echo.HandlerFunc { +// return func(c echo.Context) error { +// id, err := strconv.Atoi(c.Param("id")) +// if err != nil { +// return err +// } +// +// user := c.Get("user").(services.User) +// +// component := views.TeamMember(&user, s.ms.One(id)) +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleCheckoutPage() echo.HandlerFunc { +// return func(c echo.Context) error { +// component := views.Checkout(os.Getenv("STRIPE_LOOKUP_KEY")) +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleSuccessPage() echo.HandlerFunc { +// return func(c echo.Context) error { +// stripe.Key = os.Getenv("STRIPE_API_KEY") +// +// ss, _ := session.Get(c.QueryParam("session_id"), nil) +// log.Infof("%+v", ss.Customer) +// +// uid := c.QueryParam("u") +// userId, _ := strconv.Atoi(uid) +// err := s.us.SetPaymentStatus(userId, ss.Customer.ID) +// if err != nil { +// return c.Redirect(302, constants.RouteCancel) +// } +// +// component := views.Success() +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleCancelPage() echo.HandlerFunc { +// return func(c echo.Context) error { +// component := views.Cancel() +// return s.renderTempl(c, http.StatusOK, component) +// } +//} +// +//func (s Server) handleCreateCheckoutSession() echo.HandlerFunc { +// return func(c echo.Context) error { +// stripe.Key = os.Getenv("STRIPE_API_KEY") +// lookupKey := c.FormValue("lookup_key") +// +// params := &stripe.PriceListParams{ +// LookupKeys: stripe.StringSlice([]string{lookupKey}), +// } +// i := price.List(params) +// sp := new(stripe.Price) +// for i.Next() { +// p := i.Price() +// sp = p +// } +// +// domain := os.Getenv("STRIPE_DOMAIN") +// user := c.Get("user").(services.User) +// checkoutParams := &stripe.CheckoutSessionParams{ +// Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)), +// LineItems: []*stripe.CheckoutSessionLineItemParams{ +// {Price: stripe.String(sp.ID), Quantity: stripe.Int64(1)}, +// }, +// SuccessURL: stripe.String(fmt.Sprintf("%s%s?u=%d&session_id={CHECKOUT_SESSION_ID}", domain, constants.RouteSuccess, user.Id)), +// CancelURL: stripe.String(fmt.Sprintf("%s/cancel", domain)), +// } +// s, err := session.New(checkoutParams) +// if err != nil { +// log.Warnf("session.New %v", err) +// } +// +// return c.Redirect(http.StatusSeeOther, s.URL) +// } +//} +// +//func (s Server) handleCreatePortalSession() echo.HandlerFunc { +// return func(c echo.Context) error { +// stripe.Key = os.Getenv("STRIPE_API_KEY") +// u := c.Get("user").(services.User) +// user := s.us.One(u.Id) +// +// params := &stripe.BillingPortalSessionParams{ +// Customer: stripe.String(user.PaymentId), +// ReturnURL: stripe.String(os.Getenv("STRIPE_DOMAIN")), +// } +// ps, _ := portalsession.New(params) +// +// return c.Redirect(http.StatusSeeOther, ps.URL) +// } +//} +// +//func (s Server) handleWebhook() echo.HandlerFunc { +// return func(c echo.Context) error { +// payload, err := io.ReadAll(c.Request().Body) +// if err != nil { +// return c.String(http.StatusBadRequest, "Error reading request body: %v\n") +// } +// +// endpointSecret := os.Getenv("STRIPE_WEBHOOK_SECRET") +// signatureHeader := c.Request().Header.Get("Stripe-Signature") +// event, err := webhook.ConstructEvent(payload, signatureHeader, endpointSecret) +// if err != nil { +// return c.String(http.StatusBadRequest, fmt.Sprintf("⚠️ Webhook signature verification failed. %v\n", err)) +// } +// // Unmarshal the event data into an appropriate struct depending on its Type +// switch event.Type { +// case "customer.subscription.deleted": +// var subscription stripe.Subscription +// err := json.Unmarshal(event.Data.Raw, &subscription) +// if err != nil { +// return c.String(http.StatusBadRequest, "Error parsing webhook JSON: %v\n") +// } +// log.Printf("Subscription deleted for %s.", subscription.ID) +// // Then define and call a func to handle the deleted subscription. +// // handleSubscriptionCanceled(subscription) +// case "customer.subscription.updated": +// var subscription stripe.Subscription +// err := json.Unmarshal(event.Data.Raw, &subscription) +// if err != nil { +// return c.String(http.StatusBadRequest, "Error parsing webhook JSON: %v\n") +// } +// log.Printf("Subscription updated for %s.", subscription.ID) +// // Then define and call a func to handle the successful attachment of a PaymentMethod. +// // handleSubscriptionUpdated(subscription) +// case "customer.subscription.created": +// var subscription stripe.Subscription +// err := json.Unmarshal(event.Data.Raw, &subscription) +// if err != nil { +// return c.String(http.StatusBadRequest, "Error parsing webhook JSON: %v\n") +// } +// log.Printf("Subscription created for %s.", subscription.ID) +// // Then define and call a func to handle the successful attachment of a PaymentMethod. +// // handleSubscriptionCreated(subscription) +// case "customer.subscription.trial_will_end": +// var subscription stripe.Subscription +// err := json.Unmarshal(event.Data.Raw, &subscription) +// if err != nil { +// return c.String(http.StatusBadRequest, "Error parsing webhook JSON: %v\n") +// } +// log.Printf("Subscription trial will end for %s.", subscription.ID) +// // Then define and call a func to handle the successful attachment of a PaymentMethod. +// // handleSubscriptionTrialWillEnd(subscription) +// default: +// return c.String(http.StatusBadRequest, "Unhandled event type: %s\n") +// } +// return c.NoContent(http.StatusOK) +// } +//} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..7d03f70 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,92 @@ +package server + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "os/signal" + "strings" + "time" + + "github.com/a-h/templ" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "gorm.io/gorm" +) + +type Server struct { + Router *echo.Echo + //ms *services.MemberService + //us *services.UserService + addr string +} + +func New(db *gorm.DB) *Server { + return &Server{ + Router: echo.New(), + //ms: services.NewMemberService(db), + //us: services.NewUserService(db), + addr: fmt.Sprintf("0.0.0.0:%s", os.Getenv("PORT")), + } +} + +func (s Server) MountHandlers() { + // config + s.Router.HideBanner = true + s.Router.Debug = strings.ToLower(os.Getenv("DEBUG")) == "true" + s.Router.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + Format: "${time_rfc3339} [${method}: ${status}] ${uri}; ip=${remote_ip}; ${latency_human}; ${user_agent}\n", + })) + s.Router.HTTPErrorHandler = s.customHTTPErrorHandler + // middlewares + s.Router.Use(middleware.Recover()) + s.Router.Use(middleware.Secure()) + // static assets + s.Router.Static("/static", "assets") + // landing page + s.Router.GET("/", s.handleHomePage()) +} + +func (s Server) Start() { + go func() { + if err := s.Router.Start(s.addr); err != nil && !errors.Is(err, http.ErrServerClosed) { + s.Router.Logger.Fatal("shutting down the server") + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := s.Router.Shutdown(ctx); err != nil { + s.Router.Logger.Fatal(err) + } +} + +func (s Server) renderTempl(c echo.Context, status int, t templ.Component) error { + c.Response().Writer.WriteHeader(status) + + err := t.Render(context.Background(), c.Response().Writer) + if err != nil { + return c.String(http.StatusInternalServerError, "failed to render response template") + } + + return nil +} + +func (s Server) customHTTPErrorHandler(err error, c echo.Context) { + code := http.StatusInternalServerError + var he *echo.HTTPError + if errors.As(err, &he) { + code = he.Code + } + s.Router.Logger.Error(err) + + errorPage := fmt.Sprintf("assets/html/HTTP%d.html", code) + if err := c.File(errorPage); err != nil { + c.Logger().Error(err) + } +} diff --git a/internal/views/base.templ b/internal/views/base.templ new file mode 100644 index 0000000..0c4efac --- /dev/null +++ b/internal/views/base.templ @@ -0,0 +1,27 @@ +package views + +templ BaseLayout() { + + + + Dwight | Assistant to the Manager + + + + + + + + + +
+ { children... } +
+ + + +} diff --git a/internal/views/base_templ.go b/internal/views/base_templ.go new file mode 100644 index 0000000..ff5ea68 --- /dev/null +++ b/internal/views/base_templ.go @@ -0,0 +1,70 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: 0.2.476 +package views + +//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" + +func BaseLayout() 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("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var2 := `Dwight | Assistant to the Manager` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + 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 + }) +} diff --git a/internal/views/index.templ b/internal/views/index.templ new file mode 100644 index 0000000..0e02ad5 --- /dev/null +++ b/internal/views/index.templ @@ -0,0 +1,7 @@ +package views + +templ Index() { + @BaseLayout() { +

Hi

+ } +} diff --git a/internal/views/index_templ.go b/internal/views/index_templ.go new file mode 100644 index 0000000..71263ba --- /dev/null +++ b/internal/views/index_templ.go @@ -0,0 +1,59 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: 0.2.476 +package views + +//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" + +func Index() 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_Var2 := 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) + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var3 := `Hi ` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + 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 + }) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3540f08 --- /dev/null +++ b/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "log" + "os" + + "github.com/joho/godotenv" + "gorm.io/driver/postgres" + "gorm.io/gorm" + + "github.com/rjNemo/rentease/internal/server" +) + +func init() { + if os.Getenv("ENV") != "PROD" { + err := godotenv.Load(".env") + if err != nil { + log.Fatalln("Error loading .env file") + } + } +} + +func main() { + db, err := gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")), &gorm.Config{}) + if err != nil { + log.Fatalf("error connecting to the database %s\n", err) + } + + //err = db.AutoMigrate(&services.User{}, &models.Member{}, &models.MeetingNote{}) + //if err != nil { + // log.Fatalf("error migrating the database %s\n", err) + //} + + s := server.New(db) + s.MountHandlers() + s.Start() +}