initial project structure

This commit is contained in:
Ruidy 2024-02-04 12:48:43 +01:00
commit a1b7c5921b
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
13 changed files with 726 additions and 0 deletions

2
.env Normal file
View file

@ -0,0 +1,2 @@
DATABASE_URL="user=ruidy database=villafleurie"
PORT=8000

26
.gitignore vendored Normal file
View file

@ -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

20
Makefile Normal file
View file

@ -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

18
README.md Normal file
View file

@ -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

32
go.mod Normal file
View file

@ -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
)

65
go.sum Normal file
View file

@ -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=

271
internal/server/handlers.go Normal file
View file

@ -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)
// }
//}

92
internal/server/server.go Normal file
View file

@ -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)
}
}

27
internal/views/base.templ Normal file
View file

@ -0,0 +1,27 @@
package views
templ BaseLayout() {
<!DOCTYPE html>
<html lang="en">
<head>
<title>Dwight | Assistant to the Manager</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/img/favicon.svg"/>
<link rel="stylesheet" href="/static/css/pico.min.css"/>
<link rel="stylesheet" href="/static/css/style.css"/>
</head>
<body>
<nav class="container-fluid">
<ul>
<li><a href="/"><b>🧠 Dwight</b> </a></li>
</ul>
</nav>
<main class="container">
{ children... }
</main>
<footer class="container-fluid">🧠 Dwight &copy 2024</footer>
</body>
</html>
}

View file

@ -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("<!doctype html><html lang=\"en\"><head><title>")
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("</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/img/favicon.svg\"><link rel=\"stylesheet\" href=\"/static/css/pico.min.css\"><link rel=\"stylesheet\" href=\"/static/css/style.css\"></head><body><nav class=\"container-fluid\"><ul><li><a href=\"/\"><b>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var3 := `🧠 Dwight`
_, 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("</b></a></li></ul></nav><main class=\"container\">")
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("</main><footer class=\"container-fluid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Var4 := `🧠 Dwight &copy 2024`
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</footer></body></html>")
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

@ -0,0 +1,7 @@
package views
templ Index() {
@BaseLayout() {
<p>Hi </p>
}
}

View file

@ -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("<p>")
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("</p>")
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
})
}

37
main.go Normal file
View file

@ -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()
}