Compare commits
31 Commits
a6dca00199
...
sqlc
Author | SHA1 | Date | |
---|---|---|---|
dd49a1e687 | |||
459a4e5c7d | |||
cec183b416 | |||
f20f256313 | |||
30c552cf6c | |||
3eab2a3c9f | |||
5d225f0186 | |||
52c3679158 | |||
aca8605870 | |||
6631288843 | |||
99c2eec759 | |||
623291541a | |||
d7f7a2d8d9 | |||
14828fb901 | |||
7894a05daf | |||
b97c6cba5c | |||
a7cf9fe4f0 | |||
5987fadb03 | |||
fcd29cc082 | |||
ca1e72c676 | |||
d4cf44bb22 | |||
845f3a5836 | |||
dafd6f21c0 | |||
f93388a8e9 | |||
b34c217d98 | |||
ea796999d4 | |||
51e3c74950 | |||
dedab91898 | |||
68d9110cd2 | |||
96f81418ec | |||
a0853cf880 |
@ -2,19 +2,23 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go-udemy-web-1/internal/config"
|
"go-udemy-web-1/internal/config"
|
||||||
"go-udemy-web-1/internal/driver"
|
|
||||||
"go-udemy-web-1/internal/handlers"
|
"go-udemy-web-1/internal/handlers"
|
||||||
"go-udemy-web-1/internal/helpers"
|
"go-udemy-web-1/internal/helpers"
|
||||||
"go-udemy-web-1/internal/models"
|
"go-udemy-web-1/internal/models"
|
||||||
"go-udemy-web-1/internal/render"
|
"go-udemy-web-1/internal/render"
|
||||||
|
"go-udemy-web-1/internal/repository"
|
||||||
|
"go-udemy-web-1/internal/repository/driverrepo"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alexedwards/scs/redisstore"
|
||||||
"github.com/alexedwards/scs/v2"
|
"github.com/alexedwards/scs/v2"
|
||||||
|
"github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const portNumber = ":8080"
|
const portNumber = ":8080"
|
||||||
@ -47,12 +51,29 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() (*driver.DB, error) {
|
func run() (*repository.DB, error) {
|
||||||
// what am I going to put in the session
|
// what am I going to put in the session
|
||||||
gob.Register(models.Reservation{})
|
gob.Register(models.Reservation{})
|
||||||
gob.Register(models.User{})
|
gob.Register(models.User{})
|
||||||
gob.Register(models.Room{})
|
gob.Register(models.Room{})
|
||||||
gob.Register(models.Restriction{})
|
gob.Register(models.Restriction{})
|
||||||
|
gob.Register(map[string]int{})
|
||||||
|
|
||||||
|
// read flags
|
||||||
|
inProduction := flag.Bool("production", true, "Application is in production")
|
||||||
|
useCache := flag.Bool("cache", true, "Use template cache")
|
||||||
|
dbHost := flag.String("dbhost", "localhost", "Database host")
|
||||||
|
dbName := flag.String("dbname", "bookings", "Database name")
|
||||||
|
dbUser := flag.String("dbuser", os.Getenv("PGUSER"), "Database user")
|
||||||
|
dbPass := flag.String("dbpass", os.Getenv("PGPWD"), "Database password")
|
||||||
|
dbPort := flag.String("dbport", "5432", "Database port")
|
||||||
|
dbSSL := flag.String("dbssl", "disable", "Database ssl settings (disable, prefer, require)")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
if *dbUser == "" {
|
||||||
|
fmt.Println("Missing required flags")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
mailChan := make(chan models.MailData)
|
mailChan := make(chan models.MailData)
|
||||||
app.MailChan = mailChan
|
app.MailChan = mailChan
|
||||||
@ -60,9 +81,18 @@ func run() (*driver.DB, error) {
|
|||||||
listenForMail()
|
listenForMail()
|
||||||
|
|
||||||
// change this to true when in production
|
// change this to true when in production
|
||||||
app.InProduction = false
|
app.InProduction = *inProduction
|
||||||
|
|
||||||
|
// Establish connection pool to Redis.
|
||||||
|
pool := &redis.Pool{
|
||||||
|
MaxIdle: 10,
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
return redis.Dial("tcp", "localhost:6379")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
session = scs.New()
|
session = scs.New()
|
||||||
|
session.Store = redisstore.New(pool)
|
||||||
session.Lifetime = 24 * time.Hour
|
session.Lifetime = 24 * time.Hour
|
||||||
session.Cookie.Persist = true
|
session.Cookie.Persist = true
|
||||||
session.Cookie.SameSite = http.SameSiteLaxMode
|
session.Cookie.SameSite = http.SameSiteLaxMode
|
||||||
@ -72,8 +102,10 @@ func run() (*driver.DB, error) {
|
|||||||
|
|
||||||
// connect to database
|
// connect to database
|
||||||
log.Println("Connecting to database...")
|
log.Println("Connecting to database...")
|
||||||
dsn := fmt.Sprintf("host=localhost port=5432 dbname=bookings user=%s password=%s", os.Getenv("PGUSER"), os.Getenv("PGPWD"))
|
dsn := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=%s",
|
||||||
db, err := driver.ConnectSQL(dsn)
|
*dbHost, *dbPort, *dbName, *dbUser, *dbPass, *dbSSL)
|
||||||
|
dbdriver := driverrepo.NewSqlcRepo()
|
||||||
|
db, err := dbdriver.ConnectSQL(dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Cannot connect to database! Dying...")
|
log.Fatal("Cannot connect to database! Dying...")
|
||||||
}
|
}
|
||||||
@ -85,7 +117,7 @@ func run() (*driver.DB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
app.TemplateCahce = tc
|
app.TemplateCahce = tc
|
||||||
app.UseCache = false
|
app.UseCache = *useCache
|
||||||
|
|
||||||
infoLog = log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
|
infoLog = log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
|
||||||
app.InfoLog = infoLog
|
app.InfoLog = infoLog
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go-udemy-web-1/internal/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/justinas/nosurf"
|
"github.com/justinas/nosurf"
|
||||||
@ -34,3 +35,15 @@ func NoSurf(next http.Handler) http.Handler {
|
|||||||
func SessionLoad(next http.Handler) http.Handler {
|
func SessionLoad(next http.Handler) http.Handler {
|
||||||
return session.LoadAndSave(next)
|
return session.LoadAndSave(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Auth(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !helpers.IsAuthenticated(r) {
|
||||||
|
session.Put(r.Context(), "error", "Log in first!")
|
||||||
|
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -32,9 +32,27 @@ func routes(app *config.AppConfig) http.Handler {
|
|||||||
mux.Get("/book-room", handlers.Repo.BookRoom)
|
mux.Get("/book-room", handlers.Repo.BookRoom)
|
||||||
mux.Get("/user/login", handlers.Repo.ShowLogin)
|
mux.Get("/user/login", handlers.Repo.ShowLogin)
|
||||||
mux.Post("/user/login", handlers.Repo.PostShowLogin)
|
mux.Post("/user/login", handlers.Repo.PostShowLogin)
|
||||||
|
mux.Get("/user/logout", handlers.Repo.Logout)
|
||||||
|
|
||||||
fileServer := http.FileServer(http.Dir("./static/"))
|
fileServer := http.FileServer(http.Dir("./static/"))
|
||||||
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
||||||
|
|
||||||
|
mux.Route("/admin", func(mux chi.Router) {
|
||||||
|
if app.InProduction {
|
||||||
|
mux.Use(Auth)
|
||||||
|
}
|
||||||
|
mux.Get("/dashboard", handlers.Repo.AdminDashboard)
|
||||||
|
|
||||||
|
mux.Get("/reservations-new", handlers.Repo.AdminNewReservations)
|
||||||
|
mux.Get("/reservations-all", handlers.Repo.AdminAllReservations)
|
||||||
|
mux.Get("/reservations-calendar", handlers.Repo.AdminReservationsCalendar)
|
||||||
|
mux.Post("/reservations-calendar", handlers.Repo.AdminPostReservationsCalendar)
|
||||||
|
mux.Get("/process-reservation/{src}/{id}/do", handlers.Repo.AdminProcessReservation)
|
||||||
|
mux.Get("/delete-reservation/{src}/{id}/do", handlers.Repo.AdminDeleteReservation)
|
||||||
|
|
||||||
|
mux.Get("/reservations/{src}/{id}/show", handlers.Repo.AdminShowReservation)
|
||||||
|
mux.Post("/reservations/{src}/{id}", handlers.Repo.AdminPostShowReservation)
|
||||||
|
})
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
32
docker/docker-compose-example.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Use postgres/postgres user/password credentials
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
# set shared memory limit when using docker-compose
|
||||||
|
shm_size: 128mb
|
||||||
|
# or set shared memory limit when deploy via swarm stack
|
||||||
|
#volumes:
|
||||||
|
# - type: tmpfs
|
||||||
|
# target: /dev/shm
|
||||||
|
# tmpfs:
|
||||||
|
# size: 134217728 # 128*2^20 bytes = 128Mb
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8081:8080
|
||||||
|
|
||||||
|
cache:
|
||||||
|
image: redis
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
command: redis-server --save 60 1 --loglevel warning
|
||||||
|
volumes:
|
||||||
|
- cache:/data
|
@ -23,3 +23,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8081:8080
|
- 8081:8080
|
||||||
|
|
||||||
|
cache:
|
||||||
|
image: redis
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
command: redis-server --save 60 1 --loglevel warning
|
7
go.mod
@ -2,11 +2,12 @@ module go-udemy-web-1
|
|||||||
|
|
||||||
go 1.21.0
|
go 1.21.0
|
||||||
|
|
||||||
// github.com/CloudyKit/jet --> Check this later
|
// Template: github.com/CloudyKit/jet --> Check this later
|
||||||
//
|
//
|
||||||
// ORM packages:
|
// ORM packages:
|
||||||
// upper/db https://github.com/upper/db
|
// upper/db https://github.com/upper/db
|
||||||
// gorm.io/gorm
|
// gorm.io/gorm
|
||||||
|
// TODO Checkout sqlc and sqlx
|
||||||
|
|
||||||
// TODO Use default http package to rewrite the project with go 1.22.1
|
// TODO Use default http package to rewrite the project with go 1.22.1
|
||||||
|
|
||||||
@ -17,10 +18,13 @@ require github.com/justinas/nosurf v1.1.1
|
|||||||
require github.com/alexedwards/scs/v2 v2.8.0
|
require github.com/alexedwards/scs/v2 v2.8.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alexedwards/scs/redisstore v0.0.0-20240316134038-7e11d57e8885
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||||
|
github.com/gomodule/redigo v1.9.2
|
||||||
github.com/jackc/pgconn v1.14.3
|
github.com/jackc/pgconn v1.14.3
|
||||||
github.com/jackc/pgx/v5 v5.6.0
|
github.com/jackc/pgx/v5 v5.6.0
|
||||||
github.com/xhit/go-simple-mail v2.2.2+incompatible
|
github.com/xhit/go-simple-mail v2.2.2+incompatible
|
||||||
|
golang.org/x/crypto v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -30,7 +34,6 @@ require (
|
|||||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
)
|
)
|
||||||
|
10
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/alexedwards/scs/redisstore v0.0.0-20240316134038-7e11d57e8885 h1:UdHeICe7BgRbDq5yjA/yjCyJnohROtyD8PpJjhdAvF8=
|
||||||
|
github.com/alexedwards/scs/redisstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:ceKFatoD+hfHWWeHOAYue1J+XgOJjE7dw8l3JtIRTGY=
|
||||||
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
||||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
@ -7,6 +9,9 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
|
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
|
||||||
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/gomodule/redigo v1.8.0/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
|
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||||
|
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
@ -33,9 +38,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/xhit/go-simple-mail v2.2.2+incompatible h1:Hm2VGfLqiQJ/NnC8SYsrPOPyVYIlvP2kmnotP4RIV74=
|
github.com/xhit/go-simple-mail v2.2.2+incompatible h1:Hm2VGfLqiQJ/NnC8SYsrPOPyVYIlvP2kmnotP4RIV74=
|
||||||
github.com/xhit/go-simple-mail v2.2.2+incompatible/go.mod h1:I8Ctg6vIJZ+Sv7k/22M6oeu/tbFumDY0uxBuuLbtU7Y=
|
github.com/xhit/go-simple-mail v2.2.2+incompatible/go.mod h1:I8Ctg6vIJZ+Sv7k/22M6oeu/tbFumDY0uxBuuLbtU7Y=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go-udemy-web-1/internal/config"
|
"go-udemy-web-1/internal/config"
|
||||||
"go-udemy-web-1/internal/driver"
|
|
||||||
"go-udemy-web-1/internal/forms"
|
"go-udemy-web-1/internal/forms"
|
||||||
|
"go-udemy-web-1/internal/helpers"
|
||||||
"go-udemy-web-1/internal/models"
|
"go-udemy-web-1/internal/models"
|
||||||
"go-udemy-web-1/internal/render"
|
"go-udemy-web-1/internal/render"
|
||||||
"go-udemy-web-1/internal/repository"
|
"go-udemy-web-1/internal/repository"
|
||||||
@ -15,6 +15,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repo the repository used by the handlers
|
// Repo the repository used by the handlers
|
||||||
@ -27,10 +29,10 @@ type Repository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRepo creates a new repository
|
// NewRepo creates a new repository
|
||||||
func NewRepo(a *config.AppConfig, db *driver.DB) *Repository {
|
func NewRepo(a *config.AppConfig, db *repository.DB) *Repository {
|
||||||
return &Repository{
|
return &Repository{
|
||||||
App: a,
|
App: a,
|
||||||
DB: dbrepo.NewPostgresRepo(db.SQL, a),
|
DB: dbrepo.NewPgcRepo(db.PG, a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,14 +82,14 @@ func (m *Repository) MakeReservation(w http.ResponseWriter, r *http.Request) {
|
|||||||
res, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
|
res, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
|
||||||
if !ok {
|
if !ok {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't get reservation from session")
|
m.App.Session.Put(r.Context(), "error", "can't get reservation from session")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
room, err := m.DB.GetRoomById(res.RoomID)
|
room, err := m.DB.GetRoomById(res.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't find room")
|
m.App.Session.Put(r.Context(), "error", "can't find room")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,13 +117,13 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
|
|||||||
reservation, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
|
reservation, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
|
||||||
if !ok {
|
if !ok {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't get reservation from session")
|
m.App.Session.Put(r.Context(), "error", "can't get reservation from session")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't parse form")
|
m.App.Session.Put(r.Context(), "error", "can't parse form")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,15 +138,20 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
|
|||||||
reservation.Email = form.Get("email")
|
reservation.Email = form.Get("email")
|
||||||
reservation.Phone = form.Get("phone")
|
reservation.Phone = form.Get("phone")
|
||||||
|
|
||||||
// TODO: Should I check the validity of reservation.StartDate / EndDate?
|
|
||||||
|
|
||||||
if !form.Valid() {
|
if !form.Valid() {
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]interface{})
|
||||||
data["reservation"] = reservation
|
data["reservation"] = reservation
|
||||||
|
|
||||||
|
sd := reservation.StartDate.Format("2006-01-02")
|
||||||
|
ed := reservation.EndDate.Format("2006-01-02")
|
||||||
|
stringMap := make(map[string]string)
|
||||||
|
stringMap["start_date"] = sd
|
||||||
|
stringMap["end_date"] = ed
|
||||||
|
|
||||||
render.Template(w, r, "make-reservation.page.tmpl", &models.TemplateData{
|
render.Template(w, r, "make-reservation.page.tmpl", &models.TemplateData{
|
||||||
Data: data,
|
Data: data,
|
||||||
Form: form,
|
Form: form,
|
||||||
|
StringMap: stringMap,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -152,7 +159,7 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
|
|||||||
newReservationID, err := m.DB.InsertReservation(reservation)
|
newReservationID, err := m.DB.InsertReservation(reservation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't insert reservation into database")
|
m.App.Session.Put(r.Context(), "error", "can't insert reservation into database")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +177,7 @@ func (m *Repository) PostMakeReservation(w http.ResponseWriter, r *http.Request)
|
|||||||
err = m.DB.InsertRoomRestriction(restriction)
|
err = m.DB.InsertRoomRestriction(restriction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't insert room restriction into database")
|
m.App.Session.Put(r.Context(), "error", "can't insert room restriction into database")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +228,7 @@ func (m *Repository) ReservationSummary(w http.ResponseWriter, r *http.Request)
|
|||||||
if !ok {
|
if !ok {
|
||||||
m.App.ErrorLog.Println("connot get item from session")
|
m.App.ErrorLog.Println("connot get item from session")
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't get reservation from session")
|
m.App.Session.Put(r.Context(), "error", "Can't get reservation from session")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.App.Session.Remove(r.Context(), "reservation")
|
m.App.Session.Remove(r.Context(), "reservation")
|
||||||
@ -251,7 +258,7 @@ func (m *Repository) Availability(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) {
|
func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "can't parse form")
|
m.App.Session.Put(r.Context(), "error", "can't parse form")
|
||||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
start := r.Form.Get("start")
|
start := r.Form.Get("start")
|
||||||
@ -261,20 +268,20 @@ func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) {
|
|||||||
startDate, err := time.Parse(layout, start)
|
startDate, err := time.Parse(layout, start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't parse start date")
|
m.App.Session.Put(r.Context(), "error", "Can't parse start date")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
endDate, err := time.Parse(layout, end)
|
endDate, err := time.Parse(layout, end)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't parse end date")
|
m.App.Session.Put(r.Context(), "error", "Can't parse end date")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rooms, err := m.DB.SearchAvailabilityForAllRooms(startDate, endDate)
|
rooms, err := m.DB.SearchAvailabilityForAllRooms(startDate, endDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't connect to database")
|
m.App.Session.Put(r.Context(), "error", "Can't connect to database")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,11 +313,11 @@ func (m *Repository) PostAvailability(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type jsonResponse struct {
|
type jsonResponse struct {
|
||||||
OK bool `json:"ok"`
|
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
StartDate string `json:"start_date"`
|
StartDate string `json:"start_date"`
|
||||||
EndDate string `json:"end_date"`
|
EndDate string `json:"end_date"`
|
||||||
|
OK bool `json:"ok"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvailabilityJSON is the search for availability page handler
|
// AvailabilityJSON is the search for availability page handler
|
||||||
@ -399,7 +406,7 @@ func (m *Repository) ChooseRoom(w http.ResponseWriter, r *http.Request) {
|
|||||||
roomID, err := strconv.Atoi(exploded[2])
|
roomID, err := strconv.Atoi(exploded[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't parse roomID")
|
m.App.Session.Put(r.Context(), "error", "Can't parse roomID")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.App.Session.Get(r.Context(), "reservation")
|
m.App.Session.Get(r.Context(), "reservation")
|
||||||
@ -407,7 +414,7 @@ func (m *Repository) ChooseRoom(w http.ResponseWriter, r *http.Request) {
|
|||||||
res, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
|
res, ok := m.App.Session.Get(r.Context(), "reservation").(models.Reservation)
|
||||||
if !ok {
|
if !ok {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't get reservation from session")
|
m.App.Session.Put(r.Context(), "error", "Can't get reservation from session")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,19 +436,19 @@ func (m *Repository) BookRoom(w http.ResponseWriter, r *http.Request) {
|
|||||||
startDate, err := time.Parse(layout, sd)
|
startDate, err := time.Parse(layout, sd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't parse start date")
|
m.App.Session.Put(r.Context(), "error", "Can't parse start date")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
endDate, err := time.Parse(layout, ed)
|
endDate, err := time.Parse(layout, ed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't parse end date")
|
m.App.Session.Put(r.Context(), "error", "Can't parse end date")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
room, err := m.DB.GetRoomById(roomID)
|
room, err := m.DB.GetRoomById(roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.App.Session.Put(r.Context(), "error", "Can't parse roomId")
|
m.App.Session.Put(r.Context(), "error", "Can't parse roomId")
|
||||||
http.Redirect(w, r, "/availability", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/availability", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.RoomID = roomID
|
res.RoomID = roomID
|
||||||
@ -453,6 +460,7 @@ func (m *Repository) BookRoom(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, "/make-reservation", http.StatusSeeOther)
|
http.Redirect(w, r, "/make-reservation", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShowLogin shows the login screen
|
||||||
func (m *Repository) ShowLogin(w http.ResponseWriter, r *http.Request) {
|
func (m *Repository) ShowLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
render.Template(w, r, "login.page.tmpl", &models.TemplateData{
|
render.Template(w, r, "login.page.tmpl", &models.TemplateData{
|
||||||
Form: forms.New(nil),
|
Form: forms.New(nil),
|
||||||
@ -474,9 +482,11 @@ func (m *Repository) PostShowLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
password := r.Form.Get("password")
|
password := r.Form.Get("password")
|
||||||
form := forms.New(r.PostForm)
|
form := forms.New(r.PostForm)
|
||||||
form.Required("email", "password")
|
form.Required("email", "password")
|
||||||
|
form.IsEmail("email")
|
||||||
if !form.Valid() {
|
if !form.Valid() {
|
||||||
// TODO
|
render.Template(w, r, "login.page.tmpl", &models.TemplateData{
|
||||||
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
|
Form: form,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,5 +500,321 @@ func (m *Repository) PostShowLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
m.App.Session.Put(r.Context(), "user_id", id)
|
m.App.Session.Put(r.Context(), "user_id", id)
|
||||||
m.App.Session.Put(r.Context(), "flash", "Logged in successfully")
|
m.App.Session.Put(r.Context(), "flash", "Logged in successfully")
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/admin/dashboard", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout logs a user out
|
||||||
|
func (m *Repository) Logout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
m.App.Session.Destroy(r.Context())
|
||||||
|
m.App.Session.RenewToken(r.Context())
|
||||||
|
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Repository) AdminDashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Template(w, r, "admin-dashboard.page.tmpl", &models.TemplateData{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminNewReservations shows all new reservations in admin tool
|
||||||
|
func (m *Repository) AdminNewReservations(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reservations, err := m.DB.AllNewReservations()
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
data["reservations"] = reservations
|
||||||
|
|
||||||
|
render.Template(w, r, "admin-new-reservations.page.tmpl", &models.TemplateData{
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminNewReservations shows all reservations in admin tool
|
||||||
|
func (m *Repository) AdminAllReservations(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reservations, err := m.DB.AllReservations()
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
data["reservations"] = reservations
|
||||||
|
|
||||||
|
render.Template(w, r, "admin-all-reservations.page.tmpl", &models.TemplateData{
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminShowReservation shows the detail of a reservation
|
||||||
|
func (m *Repository) AdminShowReservation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
exploded := strings.Split(r.RequestURI, "/")
|
||||||
|
id, err := strconv.Atoi(exploded[4])
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := exploded[3]
|
||||||
|
|
||||||
|
stringMap := make(map[string]string)
|
||||||
|
stringMap["src"] = src
|
||||||
|
|
||||||
|
year := r.URL.Query().Get("y")
|
||||||
|
month := r.URL.Query().Get("m")
|
||||||
|
|
||||||
|
stringMap["month"] = month
|
||||||
|
stringMap["year"] = year
|
||||||
|
|
||||||
|
// get reservation from the database
|
||||||
|
res, err := m.DB.GetReservationByID(id)
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
|
data["reservation"] = res
|
||||||
|
|
||||||
|
render.Template(w, r, "admin-reservations-show.page.tmpl", &models.TemplateData{
|
||||||
|
StringMap: stringMap,
|
||||||
|
Data: data,
|
||||||
|
Form: forms.New(nil),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminShowReservation shows the detail of a reservation
|
||||||
|
func (m *Repository) AdminPostShowReservation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exploded := strings.Split(r.RequestURI, "/")
|
||||||
|
id, err := strconv.Atoi(exploded[4])
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := exploded[3]
|
||||||
|
|
||||||
|
stringMap := make(map[string]string)
|
||||||
|
stringMap["src"] = src
|
||||||
|
|
||||||
|
// get reservation from the database
|
||||||
|
res, err := m.DB.GetReservationByID(id)
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FirstName = r.Form.Get("first_name")
|
||||||
|
res.LastName = r.Form.Get("last_name")
|
||||||
|
res.Email = r.Form.Get("email")
|
||||||
|
res.Phone = r.Form.Get("phone")
|
||||||
|
|
||||||
|
// TODO error checking
|
||||||
|
|
||||||
|
err = m.DB.UpdateReservation(res)
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
month := r.Form.Get("month")
|
||||||
|
year := r.Form.Get("year")
|
||||||
|
|
||||||
|
m.App.Session.Put(r.Context(), "flash", "Changes saved")
|
||||||
|
if year == "" {
|
||||||
|
log.Println(year, month)
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-%s", src), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-calendar?y=%s&m=%s", year, month), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminReservationsCalendar displays the reservation calendar
|
||||||
|
func (m *Repository) AdminReservationsCalendar(w http.ResponseWriter, r *http.Request) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if r.URL.Query().Get("y") != "" {
|
||||||
|
year, _ := strconv.Atoi(r.URL.Query().Get("y"))
|
||||||
|
month, _ := strconv.Atoi(r.URL.Query().Get("m"))
|
||||||
|
now = time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
data["now"] = now
|
||||||
|
|
||||||
|
next := now.AddDate(0, 1, 0)
|
||||||
|
last := now.AddDate(0, -1, 0)
|
||||||
|
|
||||||
|
nextMonth := next.Format("01")
|
||||||
|
nextMonthYear := next.Format("2006")
|
||||||
|
|
||||||
|
lastMonth := last.Format("01")
|
||||||
|
lastMonthYear := last.Format("2006")
|
||||||
|
|
||||||
|
stringMap := make(map[string]string)
|
||||||
|
stringMap["next_month"] = nextMonth
|
||||||
|
stringMap["next_month_year"] = nextMonthYear
|
||||||
|
stringMap["last_month"] = lastMonth
|
||||||
|
stringMap["last_month_year"] = lastMonthYear
|
||||||
|
stringMap["this_month"] = now.Format("01")
|
||||||
|
stringMap["this_month_year"] = now.Format("2006")
|
||||||
|
|
||||||
|
// get the first and last days from the month
|
||||||
|
currentYear, currentMonth, _ := now.Date()
|
||||||
|
currentLocation := now.Location()
|
||||||
|
firstOfMonth := time.Date(currentYear, currentMonth, 1, 0, 0, 0, 0, currentLocation)
|
||||||
|
lastOfMonth := firstOfMonth.AddDate(0, 1, -1)
|
||||||
|
|
||||||
|
intMap := make(map[string]int)
|
||||||
|
intMap["days_in_month"] = lastOfMonth.Day()
|
||||||
|
|
||||||
|
rooms, err := m.DB.AllRooms()
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["rooms"] = rooms
|
||||||
|
|
||||||
|
for _, x := range rooms {
|
||||||
|
// create maps
|
||||||
|
reservationMap := make(map[string]int)
|
||||||
|
blockMap := make(map[string]int)
|
||||||
|
|
||||||
|
for d := firstOfMonth; !d.After(lastOfMonth); d = d.AddDate(0, 0, 1) {
|
||||||
|
reservationMap[d.Format("2006-01-2")] = 0
|
||||||
|
blockMap[d.Format("2006-01-2")] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all the restrictions for the current room
|
||||||
|
restrictions, err := m.DB.GetRestrictionsForRoomByDate(x.ID, firstOfMonth, lastOfMonth)
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, y := range restrictions {
|
||||||
|
if y.ReservationID > 0 {
|
||||||
|
// it's a reservation
|
||||||
|
for d := y.StartDate; !d.After(y.EndDate); d = d.AddDate(0, 0, 1) {
|
||||||
|
reservationMap[d.Format("2006-01-2")] = y.ReservationID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// it's a block.
|
||||||
|
// NOTE:A block can only be set day by day
|
||||||
|
blockMap[y.StartDate.Format("2006-01-2")] = y.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data[fmt.Sprintf("reservation_map_%d", x.ID)] = reservationMap
|
||||||
|
data[fmt.Sprintf("block_map_%d", x.ID)] = blockMap
|
||||||
|
|
||||||
|
m.App.Session.Put(r.Context(), fmt.Sprintf("block_map_%d", x.ID), blockMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Template(w, r, "admin-reservations-calendar.page.tmpl",
|
||||||
|
&models.TemplateData{
|
||||||
|
StringMap: stringMap,
|
||||||
|
Data: data,
|
||||||
|
IntMap: intMap,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminProcessReservation marks a reservation as processed
|
||||||
|
func (m *Repository) AdminProcessReservation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, _ := strconv.Atoi(chi.URLParam(r, "id"))
|
||||||
|
src := chi.URLParam(r, "src")
|
||||||
|
|
||||||
|
_ = m.DB.UpdateProcessedForReservation(id, 1)
|
||||||
|
|
||||||
|
year := r.URL.Query().Get("y")
|
||||||
|
month := r.URL.Query().Get("m")
|
||||||
|
|
||||||
|
m.App.Session.Put(r.Context(), "flash", "Reservation marked as processed")
|
||||||
|
|
||||||
|
if year == "" {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-%s", src), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-calendar?y=%s&m=%s", year, month), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminDeleteReservation deletes a reservation
|
||||||
|
func (m *Repository) AdminDeleteReservation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, _ := strconv.Atoi(chi.URLParam(r, "id"))
|
||||||
|
src := chi.URLParam(r, "src")
|
||||||
|
|
||||||
|
_ = m.DB.DeleteReservation(id)
|
||||||
|
|
||||||
|
year := r.URL.Query().Get("y")
|
||||||
|
month := r.URL.Query().Get("m")
|
||||||
|
|
||||||
|
m.App.Session.Put(r.Context(), "flash", fmt.Sprintf("Reservation %d deleted", id))
|
||||||
|
|
||||||
|
if year == "" {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-%s", src), http.StatusSeeOther)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-calendar?y=%s&m=%s", year, month), http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminPostReservationsCalendar handles post of reservation calendar
|
||||||
|
func (m *Repository) AdminPostReservationsCalendar(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
year, _ := strconv.Atoi(r.Form.Get("y"))
|
||||||
|
month, _ := strconv.Atoi(r.Form.Get("m"))
|
||||||
|
|
||||||
|
// process blocks
|
||||||
|
rooms, err := m.DB.AllRooms()
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
form := forms.New(r.PostForm)
|
||||||
|
for _, x := range rooms {
|
||||||
|
// Get the block map from the session. Loop through entire map, if we
|
||||||
|
// have an entry in the map taht does not exist in our posted data,
|
||||||
|
// and if the restriction id > 0, then it is a block we need to remove.
|
||||||
|
curMap, _ := m.App.Session.Get(r.Context(), fmt.Sprintf("block_map_%d", x.ID)).(map[string]int)
|
||||||
|
// TODO check session get ok
|
||||||
|
for name, value := range curMap {
|
||||||
|
// ok will be false if the value is not in the map
|
||||||
|
if val, ok := curMap[name]; ok {
|
||||||
|
// only pay attention to values > 0, and that are not in the form post
|
||||||
|
// the rest are just placeholders for days without blocks
|
||||||
|
if val > 0 {
|
||||||
|
if !form.Has(fmt.Sprintf("remove_block_%d_%s", x.ID, name)) {
|
||||||
|
err := m.DB.DeleteBlockByID(value)
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle new blocks
|
||||||
|
for name := range r.PostForm {
|
||||||
|
if strings.HasPrefix(name, "add_block") {
|
||||||
|
exploded := strings.Split(name, "_")
|
||||||
|
roomID, _ := strconv.Atoi(exploded[2])
|
||||||
|
startDate, _ := time.Parse("2006-01-2", exploded[3])
|
||||||
|
// insert a new block
|
||||||
|
err := m.DB.InsertBlockForRoom(roomID, startDate)
|
||||||
|
if err != nil {
|
||||||
|
helpers.ServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.App.Session.Put(r.Context(), "flash", "Changes saved")
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-calendar?y=%d&m=%d", year, month), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"go-udemy-web-1/internal/driver"
|
|
||||||
"go-udemy-web-1/internal/models"
|
"go-udemy-web-1/internal/models"
|
||||||
|
"go-udemy-web-1/internal/repository"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -24,7 +24,7 @@ type postData struct {
|
|||||||
// {{{ Test NewRepo
|
// {{{ Test NewRepo
|
||||||
|
|
||||||
func Test_NewRepo(t *testing.T) {
|
func Test_NewRepo(t *testing.T) {
|
||||||
var db driver.DB
|
var db repository.DB
|
||||||
repo := NewRepo(&app, &db)
|
repo := NewRepo(&app, &db)
|
||||||
|
|
||||||
if reflect.TypeOf(repo).String() != "*handlers.Repository" {
|
if reflect.TypeOf(repo).String() != "*handlers.Repository" {
|
||||||
@ -47,6 +47,13 @@ var theTests = []struct {
|
|||||||
{"ms", "/majors-suite", "GET", http.StatusOK},
|
{"ms", "/majors-suite", "GET", http.StatusOK},
|
||||||
{"sa", "/availability", "GET", http.StatusOK},
|
{"sa", "/availability", "GET", http.StatusOK},
|
||||||
{"contact", "/contact", "GET", http.StatusOK},
|
{"contact", "/contact", "GET", http.StatusOK},
|
||||||
|
{"non-existant", "/some/link", "GET", http.StatusNotFound},
|
||||||
|
{"login", "/user/login", "GET", http.StatusOK},
|
||||||
|
{"logout", "/user/logout", "GET", http.StatusOK},
|
||||||
|
{"dashboard", "/admin/dashboard", "GET", http.StatusOK},
|
||||||
|
{"all reservations", "/admin/reservations-all", "GET", http.StatusOK},
|
||||||
|
{"new reservations", "/admin/reservations-new", "GET", http.StatusOK},
|
||||||
|
{"show reservation", "/admin/reservations/new/1/show", "GET", http.StatusOK},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlers(t *testing.T) {
|
func TestHandlers(t *testing.T) {
|
||||||
@ -75,8 +82,8 @@ var makeReservationTests = []struct {
|
|||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
}{
|
}{
|
||||||
{"ok", 1, http.StatusOK},
|
{"ok", 1, http.StatusOK},
|
||||||
{"no session", 0, http.StatusTemporaryRedirect},
|
{"no session", 0, http.StatusSeeOther},
|
||||||
{"non-existant room", 100, http.StatusTemporaryRedirect},
|
{"non-existant room", 100, http.StatusSeeOther},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepository_MakeReservation(t *testing.T) {
|
func TestRepository_MakeReservation(t *testing.T) {
|
||||||
@ -141,9 +148,9 @@ var postMakeReservationTests = []struct {
|
|||||||
{key: "start_date", value: "2050-01-01"},
|
{key: "start_date", value: "2050-01-01"},
|
||||||
{key: "end_date", value: "2050-01-02"},
|
{key: "end_date", value: "2050-01-02"},
|
||||||
},
|
},
|
||||||
http.StatusTemporaryRedirect,
|
http.StatusSeeOther,
|
||||||
},
|
},
|
||||||
{"no_post_data", []postData{}, http.StatusTemporaryRedirect},
|
{"no_post_data", []postData{}, http.StatusSeeOther},
|
||||||
{
|
{
|
||||||
"missing first name",
|
"missing first name",
|
||||||
[]postData{
|
[]postData{
|
||||||
@ -193,7 +200,7 @@ var postMakeReservationTests = []struct {
|
|||||||
{key: "start_date", value: "2050-01-01"},
|
{key: "start_date", value: "2050-01-01"},
|
||||||
{key: "end_date", value: "2050-01-02"},
|
{key: "end_date", value: "2050-01-02"},
|
||||||
},
|
},
|
||||||
http.StatusTemporaryRedirect,
|
http.StatusSeeOther,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"insert room restriction error",
|
"insert room restriction error",
|
||||||
@ -206,7 +213,7 @@ var postMakeReservationTests = []struct {
|
|||||||
{key: "start_date", value: "2050-01-01"},
|
{key: "start_date", value: "2050-01-01"},
|
||||||
{key: "end_date", value: "2050-01-02"},
|
{key: "end_date", value: "2050-01-02"},
|
||||||
},
|
},
|
||||||
http.StatusTemporaryRedirect,
|
http.StatusSeeOther,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +381,7 @@ var reservationSummaryTests = []struct {
|
|||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
}{
|
}{
|
||||||
{"ok", true, http.StatusOK},
|
{"ok", true, http.StatusOK},
|
||||||
{"nok", false, http.StatusTemporaryRedirect},
|
{"nok", false, http.StatusSeeOther},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ReservationSummary(t *testing.T) {
|
func Test_ReservationSummary(t *testing.T) {
|
||||||
@ -426,7 +433,7 @@ var postAvailabilityTests = []struct {
|
|||||||
{"database error", []postData{
|
{"database error", []postData{
|
||||||
{key: "start", value: "2050-01-03"},
|
{key: "start", value: "2050-01-03"},
|
||||||
{key: "end", value: "2050-01-04"},
|
{key: "end", value: "2050-01-04"},
|
||||||
}, http.StatusTemporaryRedirect},
|
}, http.StatusSeeOther},
|
||||||
{"no availability", []postData{
|
{"no availability", []postData{
|
||||||
{key: "start", value: "2050-01-05"},
|
{key: "start", value: "2050-01-05"},
|
||||||
{key: "end", value: "2050-01-06"},
|
{key: "end", value: "2050-01-06"},
|
||||||
@ -434,12 +441,12 @@ var postAvailabilityTests = []struct {
|
|||||||
{"wrong start date", []postData{
|
{"wrong start date", []postData{
|
||||||
{key: "start", value: "2050-05"},
|
{key: "start", value: "2050-05"},
|
||||||
{key: "end", value: "2050-01-06"},
|
{key: "end", value: "2050-01-06"},
|
||||||
}, http.StatusTemporaryRedirect},
|
}, http.StatusSeeOther},
|
||||||
{"wrong end date", []postData{
|
{"wrong end date", []postData{
|
||||||
{key: "start", value: "2050-01-05"},
|
{key: "start", value: "2050-01-05"},
|
||||||
{key: "end", value: "01-06"},
|
{key: "end", value: "01-06"},
|
||||||
}, http.StatusTemporaryRedirect},
|
}, http.StatusSeeOther},
|
||||||
{"wrong end date", []postData{}, http.StatusTemporaryRedirect},
|
{"wrong end date", []postData{}, http.StatusSeeOther},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_PostAvailability(t *testing.T) {
|
func Test_PostAvailability(t *testing.T) {
|
||||||
@ -481,8 +488,8 @@ var chooseRoomTests = []struct {
|
|||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
}{
|
}{
|
||||||
{"ok", "/choose-room/1", true, http.StatusSeeOther},
|
{"ok", "/choose-room/1", true, http.StatusSeeOther},
|
||||||
{"wrong room id", "/choose-room/1wrong", true, http.StatusTemporaryRedirect},
|
{"wrong room id", "/choose-room/1wrong", true, http.StatusSeeOther},
|
||||||
{"no session", "/choose-room/1", false, http.StatusTemporaryRedirect},
|
{"no session", "/choose-room/1", false, http.StatusSeeOther},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ChooseRoom(t *testing.T) {
|
func Test_ChooseRoom(t *testing.T) {
|
||||||
@ -531,17 +538,17 @@ var bookRoomTests = []struct {
|
|||||||
{key: "id", value: "1"},
|
{key: "id", value: "1"},
|
||||||
{key: "s", value: "20-01-01"},
|
{key: "s", value: "20-01-01"},
|
||||||
{key: "e", value: "2050-01-02"},
|
{key: "e", value: "2050-01-02"},
|
||||||
}, http.StatusTemporaryRedirect},
|
}, http.StatusSeeOther},
|
||||||
{"wrong end date", []postData{
|
{"wrong end date", []postData{
|
||||||
{key: "id", value: "1"},
|
{key: "id", value: "1"},
|
||||||
{key: "s", value: "2050-01-01"},
|
{key: "s", value: "2050-01-01"},
|
||||||
{key: "e", value: "2050-0-02"},
|
{key: "e", value: "2050-0-02"},
|
||||||
}, http.StatusTemporaryRedirect},
|
}, http.StatusSeeOther},
|
||||||
{"wrong room id", []postData{
|
{"wrong room id", []postData{
|
||||||
{key: "id", value: "w"},
|
{key: "id", value: "w"},
|
||||||
{key: "s", value: "2050-01-01"},
|
{key: "s", value: "2050-01-01"},
|
||||||
{key: "e", value: "2050-01-02"},
|
{key: "e", value: "2050-01-02"},
|
||||||
}, http.StatusTemporaryRedirect},
|
}, http.StatusSeeOther},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BookRoom(t *testing.T) {
|
func Test_BookRoom(t *testing.T) {
|
||||||
@ -567,6 +574,64 @@ func Test_BookRoom(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
// {{{ Login
|
||||||
|
|
||||||
|
var loginTests = []struct {
|
||||||
|
name string
|
||||||
|
email string
|
||||||
|
expectedStatusCode int
|
||||||
|
expectedHTML string
|
||||||
|
expectedLocation string
|
||||||
|
}{
|
||||||
|
{"valid-credentials", "a@b.c", http.StatusSeeOther, "", "/admin/dashboard"},
|
||||||
|
{"invalid-credentials", "invalid@b.c", http.StatusSeeOther, "", "/user/login"},
|
||||||
|
{"invalid-data", "a@b", http.StatusOK, `action="/user/login"`, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogin(t *testing.T) {
|
||||||
|
// range through all tests
|
||||||
|
for _, e := range loginTests {
|
||||||
|
postedData := url.Values{}
|
||||||
|
postedData.Add("email", e.email)
|
||||||
|
postedData.Add("password", "password")
|
||||||
|
|
||||||
|
// create request
|
||||||
|
req, _ := http.NewRequest("POST", "/user/login", strings.NewReader(postedData.Encode()))
|
||||||
|
ctx := getCtx(req)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
// set the header
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// call the handler
|
||||||
|
handler := http.HandlerFunc(Repo.PostShowLogin)
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != e.expectedStatusCode {
|
||||||
|
t.Errorf("failed %s: expected code %d, but got %d", e.name, e.expectedStatusCode, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.expectedLocation != "" {
|
||||||
|
// get the URL from test
|
||||||
|
actualLoc, _ := rr.Result().Location()
|
||||||
|
if actualLoc.String() != e.expectedLocation {
|
||||||
|
t.Errorf("failed %s: expected location %s, but got location %s", e.name, e.expectedLocation, actualLoc.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking for expected values in HTML
|
||||||
|
if e.expectedHTML != "" {
|
||||||
|
// read the response body into a string
|
||||||
|
html := rr.Body.String()
|
||||||
|
if !strings.Contains(html, e.expectedHTML) {
|
||||||
|
t.Errorf("failed %s: expected html contains %s, but got html %s", e.name, e.expectedHTML, html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Test Helpers
|
// {{{ Test Helpers
|
||||||
|
|
||||||
|
@ -19,7 +19,12 @@ import (
|
|||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
var functions = template.FuncMap{}
|
var functions = template.FuncMap{
|
||||||
|
"humanDate": render.HumanDate,
|
||||||
|
"formatDate": render.FormatDate,
|
||||||
|
"iterate": render.Iterate,
|
||||||
|
"add": render.Add,
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
app config.AppConfig
|
app config.AppConfig
|
||||||
@ -28,6 +33,10 @@ var (
|
|||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
gob.Register(models.Reservation{})
|
gob.Register(models.Reservation{})
|
||||||
|
gob.Register(models.User{})
|
||||||
|
gob.Register(models.Room{})
|
||||||
|
gob.Register(models.Restriction{})
|
||||||
|
gob.Register(map[string]int{})
|
||||||
// change this to true when in production
|
// change this to true when in production
|
||||||
app.InProduction = false
|
app.InProduction = false
|
||||||
|
|
||||||
@ -85,6 +94,25 @@ func getRoutes() http.Handler {
|
|||||||
mux.Post("/make-reservation", Repo.PostMakeReservation)
|
mux.Post("/make-reservation", Repo.PostMakeReservation)
|
||||||
mux.Get("/reservation-summary", Repo.ReservationSummary)
|
mux.Get("/reservation-summary", Repo.ReservationSummary)
|
||||||
|
|
||||||
|
mux.Get("/choose-room/{id}", Repo.ChooseRoom)
|
||||||
|
mux.Get("/book-room", Repo.BookRoom)
|
||||||
|
|
||||||
|
mux.Get("/user/login", Repo.ShowLogin)
|
||||||
|
mux.Post("/user/login", Repo.PostShowLogin)
|
||||||
|
mux.Get("/user/logout", Repo.Logout)
|
||||||
|
|
||||||
|
mux.Get("/admin/dashboard", Repo.AdminDashboard)
|
||||||
|
|
||||||
|
mux.Get("/admin/reservations-new", Repo.AdminNewReservations)
|
||||||
|
mux.Get("/admin/reservations-all", Repo.AdminAllReservations)
|
||||||
|
mux.Get("/admin/reservations-calendar", Repo.AdminReservationsCalendar)
|
||||||
|
mux.Post("/admin/reservations-calendar", Repo.AdminPostReservationsCalendar)
|
||||||
|
mux.Get("/admin/process-reservation/{src}/{id}/do", Repo.AdminProcessReservation)
|
||||||
|
mux.Get("/admin/delete-reservation/{src}/{id}/do", Repo.AdminDeleteReservation)
|
||||||
|
|
||||||
|
mux.Get("/admin/reservations/{src}/{id}/show", Repo.AdminShowReservation)
|
||||||
|
mux.Post("/admin/reservations/{src}/{id}", Repo.AdminPostShowReservation)
|
||||||
|
|
||||||
fileServer := http.FileServer(http.Dir("./static/"))
|
fileServer := http.FileServer(http.Dir("./static/"))
|
||||||
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
||||||
|
|
||||||
|
@ -24,3 +24,7 @@ func ServerError(w http.ResponseWriter, err error) {
|
|||||||
app.ErrorLog.Println(trace)
|
app.ErrorLog.Println(trace)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsAuthenticated(r *http.Request) bool {
|
||||||
|
return app.Session.Exists(r.Context(), "user_id")
|
||||||
|
}
|
||||||
|
@ -45,6 +45,7 @@ type Reservation struct {
|
|||||||
Room Room
|
Room Room
|
||||||
ID int
|
ID int
|
||||||
RoomID int
|
RoomID int
|
||||||
|
Processed int
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomRestriction is the room restriction model
|
// RoomRestriction is the room restriction model
|
||||||
|
@ -4,13 +4,14 @@ import "go-udemy-web-1/internal/forms"
|
|||||||
|
|
||||||
// TemplateData holds data sent from handlers to templates
|
// TemplateData holds data sent from handlers to templates
|
||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
StringMap map[string]string
|
StringMap map[string]string
|
||||||
IntMap map[string]int
|
IntMap map[string]int
|
||||||
FloatMap map[string]float32
|
FloatMap map[string]float32
|
||||||
Data map[string]interface{}
|
Data map[string]interface{}
|
||||||
CSRFToken string
|
Form *forms.Form
|
||||||
Flash string
|
CSRFToken string
|
||||||
Warning string
|
Flash string
|
||||||
Error string
|
Warning string
|
||||||
Form *forms.Form
|
Error string
|
||||||
|
IsAuthenticated int
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,17 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/justinas/nosurf"
|
"github.com/justinas/nosurf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var functions = template.FuncMap{}
|
var functions = template.FuncMap{
|
||||||
|
"humanDate": HumanDate,
|
||||||
|
"formatDate": FormatDate,
|
||||||
|
"iterate": Iterate,
|
||||||
|
"add": Add,
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
app *config.AppConfig
|
app *config.AppConfig
|
||||||
@ -26,12 +32,38 @@ func NewRenderer(a *config.AppConfig) {
|
|||||||
app = a
|
app = a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HumanDate returns formatted time
|
||||||
|
func HumanDate(t time.Time) string {
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatDate(t time.Time, f string) string {
|
||||||
|
return t.Format(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate returns a slice of ints, starting at 1, going to count
|
||||||
|
func Iterate(count int) []int {
|
||||||
|
var i int
|
||||||
|
var items []int
|
||||||
|
for i = 0; i < count; i++ {
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func Add(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
// AddDefaultData adds default template data
|
// AddDefaultData adds default template data
|
||||||
func AddDefaultData(td *models.TemplateData, r *http.Request) *models.TemplateData {
|
func AddDefaultData(td *models.TemplateData, r *http.Request) *models.TemplateData {
|
||||||
td.Flash = app.Session.PopString(r.Context(), "flash")
|
td.Flash = app.Session.PopString(r.Context(), "flash")
|
||||||
td.Warning = app.Session.PopString(r.Context(), "warning")
|
td.Warning = app.Session.PopString(r.Context(), "warning")
|
||||||
td.Error = app.Session.PopString(r.Context(), "error")
|
td.Error = app.Session.PopString(r.Context(), "error")
|
||||||
td.CSRFToken = nosurf.Token(r)
|
td.CSRFToken = nosurf.Token(r)
|
||||||
|
if app.Session.Exists(r.Context(), "user_id") {
|
||||||
|
td.IsAuthenticated = 1
|
||||||
|
}
|
||||||
return td
|
return td
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,9 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"go-udemy-web-1/internal/config"
|
"go-udemy-web-1/internal/config"
|
||||||
"go-udemy-web-1/internal/repository"
|
"go-udemy-web-1/internal/repository"
|
||||||
|
"go-udemy-web-1/internal/repository/sqlc"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type postgresDBRepo struct {
|
type postgresDBRepo struct {
|
||||||
@ -11,6 +14,11 @@ type postgresDBRepo struct {
|
|||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pgcDBRepo struct {
|
||||||
|
App *config.AppConfig
|
||||||
|
Q *sqlc.Queries
|
||||||
|
}
|
||||||
|
|
||||||
type testDBRepo struct {
|
type testDBRepo struct {
|
||||||
App *config.AppConfig
|
App *config.AppConfig
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
@ -28,3 +36,11 @@ func NewTestingRepo(a *config.AppConfig) repository.DatabaseRepo {
|
|||||||
App: a,
|
App: a,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPgcRepo(conn *pgx.Conn, a *config.AppConfig) repository.DatabaseRepo {
|
||||||
|
q := sqlc.New(conn)
|
||||||
|
return &pgcDBRepo{
|
||||||
|
App: a,
|
||||||
|
Q: q,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
416
internal/repository/dbrepo/pgc.go
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
package dbrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"go-udemy-web-1/internal/models"
|
||||||
|
"go-udemy-web-1/internal/repository/sqlc"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *pgcDBRepo) AllUsers() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertReservation inserts a reservation into the database
|
||||||
|
func (m *pgcDBRepo) InsertReservation(res models.Reservation) (int, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var newId int32
|
||||||
|
newId, err := m.Q.InsertReservation(ctx, sqlc.InsertReservationParams{
|
||||||
|
FirstName: res.FirstName,
|
||||||
|
LastName: res.LastName,
|
||||||
|
Email: res.Email,
|
||||||
|
Phone: res.Phone,
|
||||||
|
StartDate: pgtype.Date{Time: res.StartDate, Valid: true},
|
||||||
|
EndDate: pgtype.Date{Time: res.EndDate, Valid: true},
|
||||||
|
RoomID: int32(res.RoomID),
|
||||||
|
CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
m.App.ErrorLog.Println(err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(newId), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertRoomRestriction inserts a room restriction into the database
|
||||||
|
func (m *pgcDBRepo) InsertRoomRestriction(r models.RoomRestriction) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.InsertRoomRestriction(ctx, sqlc.InsertRoomRestrictionParams{
|
||||||
|
StartDate: pgtype.Date{Time: r.StartDate, Valid: true},
|
||||||
|
EndDate: pgtype.Date{Time: r.EndDate, Valid: true},
|
||||||
|
RoomID: int32(r.RoomID),
|
||||||
|
ReservationID: pgtype.Int4{Int32: int32(r.ReservationID), Valid: true},
|
||||||
|
RestrictionID: int32(r.RestrictionID),
|
||||||
|
CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
m.App.ErrorLog.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchAvailabilityByDatesByRoomID returns true if availability exists for roomID, and false if no availability
|
||||||
|
func (m *pgcDBRepo) SearchAvailabilityByDatesByRoomID(start, end time.Time, roomID int) (bool, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
numRows, err := m.Q.SearchAvailabilityByDatesByRoomID(ctx, sqlc.SearchAvailabilityByDatesByRoomIDParams{
|
||||||
|
RoomID: int32(roomID),
|
||||||
|
EndDate: pgtype.Date{Time: start, Valid: true},
|
||||||
|
StartDate: pgtype.Date{Time: end, Valid: false},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if numRows == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchAvailabilityForAllRooms returns a slice of rooms, if any, for given date range
|
||||||
|
func (m *pgcDBRepo) SearchAvailabilityForAllRooms(start, end time.Time) ([]models.Room, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var rooms []models.Room
|
||||||
|
|
||||||
|
rows, err := m.Q.SearchAvailabilityForAllRooms(ctx, sqlc.SearchAvailabilityForAllRoomsParams{
|
||||||
|
EndDate: pgtype.Date{Time: start, Valid: true},
|
||||||
|
StartDate: pgtype.Date{Time: start, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
room := models.Room{
|
||||||
|
RoomName: row.RoomName,
|
||||||
|
ID: int(row.ID),
|
||||||
|
}
|
||||||
|
rooms = append(rooms, room)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomById gets a room by id
|
||||||
|
func (m *pgcDBRepo) GetRoomById(id int) (models.Room, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var room models.Room
|
||||||
|
|
||||||
|
row, err := m.Q.GetRoomById(ctx, int32(id))
|
||||||
|
if err != nil {
|
||||||
|
return room, err
|
||||||
|
}
|
||||||
|
room = models.Room{
|
||||||
|
ID: int(row.ID),
|
||||||
|
RoomName: row.RoomName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return room, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByID gets a user by id
|
||||||
|
func (m *pgcDBRepo) GetUserByID(id int) (models.User, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var u models.User
|
||||||
|
|
||||||
|
row, err := m.Q.GetUserByID(ctx, int32(id))
|
||||||
|
if err != nil {
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u = models.User{
|
||||||
|
ID: int(row.ID),
|
||||||
|
FirstName: row.FirstName,
|
||||||
|
LastName: row.LastName,
|
||||||
|
Email: row.Email,
|
||||||
|
Password: row.Password,
|
||||||
|
AccessLevel: int(row.AccessLevel),
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates a user in the database
|
||||||
|
func (m *pgcDBRepo) UpdateUser(u models.User) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.UpdateUser(ctx, sqlc.UpdateUserParams{
|
||||||
|
FirstName: u.FirstName,
|
||||||
|
LastName: u.LastName,
|
||||||
|
Email: u.Email,
|
||||||
|
AccessLevel: int32(u.AccessLevel),
|
||||||
|
UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate authenticates a user
|
||||||
|
func (m *pgcDBRepo) Authenticate(email, testPassword string) (int, string, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var id int
|
||||||
|
var hashedPassword string
|
||||||
|
|
||||||
|
row, err := m.Q.GetUserCred(ctx, email)
|
||||||
|
if err != nil {
|
||||||
|
return id, "", err
|
||||||
|
}
|
||||||
|
id = int(row.ID)
|
||||||
|
hashedPassword = row.Password
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(testPassword))
|
||||||
|
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||||
|
return 0, "", errors.New("incorrect password")
|
||||||
|
} else if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, hashedPassword, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllReservations returns a slice of all reservations
|
||||||
|
func (m *pgcDBRepo) AllReservations() ([]models.Reservation, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var reservations []models.Reservation
|
||||||
|
|
||||||
|
rows, err := m.Q.AllReservations(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
r := models.Reservation{
|
||||||
|
StartDate: row.StartDate.Time,
|
||||||
|
EndDate: row.EndDate.Time,
|
||||||
|
FirstName: row.FirstName,
|
||||||
|
LastName: row.LastName,
|
||||||
|
Email: row.Email,
|
||||||
|
Phone: row.Phone,
|
||||||
|
Room: models.Room{ID: int(row.RoomID), RoomName: row.RoomName.String},
|
||||||
|
ID: int(row.ID),
|
||||||
|
RoomID: int(row.RoomID),
|
||||||
|
Processed: int(row.Processed),
|
||||||
|
}
|
||||||
|
|
||||||
|
reservations = append(reservations, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllNewReservations returns a slice of all new reservations
|
||||||
|
func (m *pgcDBRepo) AllNewReservations() ([]models.Reservation, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var reservations []models.Reservation
|
||||||
|
|
||||||
|
rows, err := m.Q.AllNewReservations(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
r := models.Reservation{
|
||||||
|
StartDate: row.StartDate.Time,
|
||||||
|
EndDate: row.EndDate.Time,
|
||||||
|
FirstName: row.FirstName,
|
||||||
|
LastName: row.LastName,
|
||||||
|
Email: row.Email,
|
||||||
|
Phone: row.Phone,
|
||||||
|
Room: models.Room{ID: int(row.RoomID), RoomName: row.RoomName.String},
|
||||||
|
ID: int(row.ID),
|
||||||
|
RoomID: int(row.RoomID),
|
||||||
|
Processed: int(row.Processed),
|
||||||
|
}
|
||||||
|
|
||||||
|
reservations = append(reservations, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReservationByID returns one reservation by ID
|
||||||
|
func (m *pgcDBRepo) GetReservationByID(id int) (models.Reservation, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var res models.Reservation
|
||||||
|
|
||||||
|
row, err := m.Q.GetReservationByID(ctx, int32(id))
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = models.Reservation{
|
||||||
|
StartDate: row.StartDate.Time,
|
||||||
|
EndDate: row.EndDate.Time,
|
||||||
|
FirstName: row.FirstName,
|
||||||
|
LastName: row.LastName,
|
||||||
|
Email: row.Email,
|
||||||
|
Phone: row.Phone,
|
||||||
|
Room: models.Room{ID: int(row.RoomID), RoomName: row.RoomName.String},
|
||||||
|
ID: int(row.ID),
|
||||||
|
RoomID: int(row.RoomID),
|
||||||
|
Processed: int(row.Processed),
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReservation updates a user in the database
|
||||||
|
func (m *pgcDBRepo) UpdateReservation(r models.Reservation) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.UpdateReservation(ctx, sqlc.UpdateReservationParams{
|
||||||
|
ID: int32(r.ID),
|
||||||
|
FirstName: r.FirstName,
|
||||||
|
LastName: r.LastName,
|
||||||
|
Email: r.Email,
|
||||||
|
Phone: r.Phone,
|
||||||
|
UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pgcDBRepo) DeleteReservation(id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.DeleteReservation(ctx, int32(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProcessedForReservation set processed for a reservation
|
||||||
|
func (m *pgcDBRepo) UpdateProcessedForReservation(id, processed int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.UpdateProcessedForReservation(ctx, sqlc.UpdateProcessedForReservationParams{
|
||||||
|
Processed: int32(processed),
|
||||||
|
ID: int32(id),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *pgcDBRepo) AllRooms() ([]models.Room, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var rooms []models.Room
|
||||||
|
|
||||||
|
rows, err := m.Q.AllRooms(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
room := models.Room{
|
||||||
|
RoomName: row.RoomName,
|
||||||
|
ID: int(row.ID),
|
||||||
|
}
|
||||||
|
rooms = append(rooms, room)
|
||||||
|
}
|
||||||
|
return rooms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRestrictionsForRoomByDate returns restrictions for a room by date range
|
||||||
|
func (m *pgcDBRepo) GetRestrictionsForRoomByDate(roomId int, start, end time.Time) ([]models.RoomRestriction, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var restrictions []models.RoomRestriction
|
||||||
|
|
||||||
|
rows, err := m.Q.GetRestrictionsForRoomByDate(ctx, sqlc.GetRestrictionsForRoomByDateParams{
|
||||||
|
EndDate: pgtype.Date{Time: end, Valid: true},
|
||||||
|
StartDate: pgtype.Date{Time: start, Valid: true},
|
||||||
|
RoomID: int32(roomId),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return restrictions, err
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
r := models.RoomRestriction{
|
||||||
|
StartDate: row.StartDate.Time,
|
||||||
|
EndDate: row.EndDate.Time,
|
||||||
|
ID: int(row.ID),
|
||||||
|
RoomID: int(row.RoomID),
|
||||||
|
ReservationID: int(row.ReservationID),
|
||||||
|
RestrictionID: int(row.RestrictionID),
|
||||||
|
}
|
||||||
|
restrictions = append(restrictions, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return restrictions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBlockForRoom inserts a room restriction
|
||||||
|
func (m *pgcDBRepo) InsertBlockForRoom(id int, startDate time.Time) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.InsertBlockForRoom(ctx, sqlc.InsertBlockForRoomParams{
|
||||||
|
StartDate: pgtype.Date{Time: startDate, Valid: true},
|
||||||
|
EndDate: pgtype.Date{Time: startDate.AddDate(0, 0, 1)},
|
||||||
|
RoomID: int32(id),
|
||||||
|
RestrictionID: 2,
|
||||||
|
CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBlockByID deletes a block by ID
|
||||||
|
func (m *pgcDBRepo) DeleteBlockByID(id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := m.Q.DeleteBlockByID(ctx, int32(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"go-udemy-web-1/internal/models"
|
"go-udemy-web-1/internal/models"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@ -113,6 +114,8 @@ func (m *postgresDBRepo) SearchAvailabilityForAllRooms(start, end time.Time) ([]
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var room models.Room
|
var room models.Room
|
||||||
|
|
||||||
@ -211,3 +214,281 @@ func (m *postgresDBRepo) Authenticate(email, testPassword string) (int, string,
|
|||||||
}
|
}
|
||||||
return id, hashedPassword, nil
|
return id, hashedPassword, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllReservations returns a slice of all reservations
|
||||||
|
func (m *postgresDBRepo) AllReservations() ([]models.Reservation, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var reservations []models.Reservation
|
||||||
|
|
||||||
|
query := `select r.id, r.first_name, r.last_name, r.email, r.phone,
|
||||||
|
r.start_date, r.end_date, r.room_id, r.created_at,
|
||||||
|
r.updated_at, r.processed, rm.id, rm.room_name
|
||||||
|
from reservations r
|
||||||
|
left join rooms rm on (r.room_id = rm.id)
|
||||||
|
order by r.start_date asc`
|
||||||
|
|
||||||
|
rows, err := m.DB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return reservations, err
|
||||||
|
}
|
||||||
|
defer rows.Close() // To avoid memory leak
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var i models.Reservation
|
||||||
|
err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.Phone,
|
||||||
|
&i.StartDate,
|
||||||
|
&i.EndDate,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Processed,
|
||||||
|
&i.Room.ID,
|
||||||
|
&i.Room.RoomName,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return reservations, err
|
||||||
|
}
|
||||||
|
reservations = append(reservations, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllNewReservations returns a slice of all new reservations
|
||||||
|
func (m *postgresDBRepo) AllNewReservations() ([]models.Reservation, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var reservations []models.Reservation
|
||||||
|
|
||||||
|
query := `select r.id, r.first_name, r.last_name, r.email, r.phone,
|
||||||
|
r.start_date, r.end_date, r.room_id, r.created_at,
|
||||||
|
r.updated_at, r.processed, rm.id, rm.room_name
|
||||||
|
from reservations r
|
||||||
|
left join rooms rm on (r.room_id = rm.id)
|
||||||
|
where r.processed = 0
|
||||||
|
order by r.start_date asc`
|
||||||
|
|
||||||
|
rows, err := m.DB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return reservations, err
|
||||||
|
}
|
||||||
|
defer rows.Close() // To avoid memory leak
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var i models.Reservation
|
||||||
|
err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.Phone,
|
||||||
|
&i.StartDate,
|
||||||
|
&i.EndDate,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Processed,
|
||||||
|
&i.Room.ID,
|
||||||
|
&i.Room.RoomName,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return reservations, err
|
||||||
|
}
|
||||||
|
reservations = append(reservations, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReservationByID returns one reservation by ID
|
||||||
|
func (m *postgresDBRepo) GetReservationByID(id int) (models.Reservation, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var res models.Reservation
|
||||||
|
|
||||||
|
query := `select r.id, r.first_name, r.last_name, r.email, r.phone,
|
||||||
|
r.start_date, r.end_date, r.room_id, r.created_at,
|
||||||
|
r.updated_at, r.processed, rm.id, rm.room_name
|
||||||
|
from reservations r
|
||||||
|
left join rooms rm on (r.room_id = rm.id)
|
||||||
|
where r.id = $1`
|
||||||
|
|
||||||
|
row := m.DB.QueryRowContext(ctx, query, id)
|
||||||
|
err := row.Scan(
|
||||||
|
&res.ID,
|
||||||
|
&res.FirstName,
|
||||||
|
&res.LastName,
|
||||||
|
&res.Email,
|
||||||
|
&res.Phone,
|
||||||
|
&res.StartDate,
|
||||||
|
&res.EndDate,
|
||||||
|
&res.RoomID,
|
||||||
|
&res.CreatedAt,
|
||||||
|
&res.UpdatedAt,
|
||||||
|
&res.Processed,
|
||||||
|
&res.Room.ID,
|
||||||
|
&res.Room.RoomName,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReservation updates a user in the database
|
||||||
|
func (m *postgresDBRepo) UpdateReservation(r models.Reservation) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `update reservations set first_name = $1, last_name = $2, email = $3, phone = $4, updated_at = $5
|
||||||
|
where id = $6`
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, query, r.FirstName, r.LastName, r.Email,
|
||||||
|
r.Phone, time.Now(), r.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteReservation deletes one reservation by ID
|
||||||
|
func (m *postgresDBRepo) DeleteReservation(id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `delete from reservations where id = $1`
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProcessedForReservation set processed for a reservation
|
||||||
|
func (m *postgresDBRepo) UpdateProcessedForReservation(id, processed int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `update reservations set processed = $1 where id = $2`
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, query, processed, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllRooms gets all rooms
|
||||||
|
func (m *postgresDBRepo) AllRooms() ([]models.Room, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
var rooms []models.Room
|
||||||
|
|
||||||
|
query := `select id, room_name, created_at, updated_at from rooms order by room_name`
|
||||||
|
|
||||||
|
rows, err := m.DB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return rooms, err
|
||||||
|
}
|
||||||
|
defer rows.Close() // To avoid memory leak
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var rm models.Room
|
||||||
|
err := rows.Scan(
|
||||||
|
&rm.ID,
|
||||||
|
&rm.RoomName,
|
||||||
|
&rm.CreatedAt,
|
||||||
|
&rm.UpdatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return rooms, err
|
||||||
|
}
|
||||||
|
rooms = append(rooms, rm)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return rooms, err
|
||||||
|
}
|
||||||
|
return rooms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRestrictionsForRoomByDate returns restrictions for a room by date range
|
||||||
|
func (m *postgresDBRepo) GetRestrictionsForRoomByDate(roomId int, start, end time.Time) ([]models.RoomRestriction, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var restrictions []models.RoomRestriction
|
||||||
|
|
||||||
|
// coalesce use 0 if null
|
||||||
|
query := `select id, coalesce(reservation_id, 0), restriction_id, room_id, start_date, end_date
|
||||||
|
from room_restrictions where $1 < end_date and $2 >= start_date
|
||||||
|
and room_id = $3`
|
||||||
|
|
||||||
|
rows, err := m.DB.QueryContext(ctx, query, start, end, roomId)
|
||||||
|
if err != nil {
|
||||||
|
return restrictions, err
|
||||||
|
}
|
||||||
|
defer rows.Close() // To avoid memory leak
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var r models.RoomRestriction
|
||||||
|
err := rows.Scan(
|
||||||
|
&r.ID,
|
||||||
|
&r.ReservationID,
|
||||||
|
&r.RestrictionID,
|
||||||
|
&r.RoomID,
|
||||||
|
&r.StartDate,
|
||||||
|
&r.EndDate,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return restrictions, err
|
||||||
|
}
|
||||||
|
restrictions = append(restrictions, r)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return restrictions, err
|
||||||
|
}
|
||||||
|
return restrictions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBlockForRoom inserts a room restriction
|
||||||
|
func (m *postgresDBRepo) InsertBlockForRoom(id int, startDate time.Time) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `insert into room_restrictions (start_date, end_date, room_id, restriction_id,
|
||||||
|
created_at, updated_at)
|
||||||
|
values ($1, $2, $3, $4, $5, $6)`
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, query, startDate, startDate.AddDate(0, 0, 1), id, 2, time.Now(), time.Now())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBlockByID deletes a block by ID
|
||||||
|
func (m *postgresDBRepo) DeleteBlockByID(id int) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
query := `delete from room_restrictions where id = $1`
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -79,5 +79,65 @@ func (m *testDBRepo) UpdateUser(u models.User) error {
|
|||||||
|
|
||||||
// Authenticate authenticates a user
|
// Authenticate authenticates a user
|
||||||
func (m *testDBRepo) Authenticate(email, testPassword string) (int, string, error) {
|
func (m *testDBRepo) Authenticate(email, testPassword string) (int, string, error) {
|
||||||
return 1, "", nil
|
if email == "a@b.c" {
|
||||||
|
return 1, "", nil
|
||||||
|
}
|
||||||
|
return 0, "", errors.New("deliberate errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllReservations returns a slice of all reservations
|
||||||
|
func (m *testDBRepo) AllReservations() ([]models.Reservation, error) {
|
||||||
|
var reservations []models.Reservation
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllNewReservations returns a slice of all new reservations
|
||||||
|
func (m *testDBRepo) AllNewReservations() ([]models.Reservation, error) {
|
||||||
|
var reservations []models.Reservation
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReservationByID returns one reservation by ID
|
||||||
|
func (m *testDBRepo) GetReservationByID(id int) (models.Reservation, error) {
|
||||||
|
var res models.Reservation
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReservation updates a user in the database
|
||||||
|
func (m *testDBRepo) UpdateReservation(r models.Reservation) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testDBRepo) DeleteReservation(id int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProcessedForReservation set processed for a reservation
|
||||||
|
func (m *testDBRepo) UpdateProcessedForReservation(id, processed int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testDBRepo) AllRooms() ([]models.Room, error) {
|
||||||
|
var rooms []models.Room
|
||||||
|
|
||||||
|
return rooms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRestrictionsForRoomByDate returns restrictions for a room by date range
|
||||||
|
func (m *testDBRepo) GetRestrictionsForRoomByDate(roomId int, start, end time.Time) ([]models.RoomRestriction, error) {
|
||||||
|
var restrictions []models.RoomRestriction
|
||||||
|
return restrictions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBlockForRoom inserts a room restriction
|
||||||
|
func (m *testDBRepo) InsertBlockForRoom(id int, startDate time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBlockByID deletes a block by ID
|
||||||
|
func (m *testDBRepo) DeleteBlockByID(id int) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
20
internal/repository/driverrepo/driverrepo.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package driverrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go-udemy-web-1/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
sqlDriverRepo struct{}
|
||||||
|
sqlcDriverRepo struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
var dbConn = &repository.DB{}
|
||||||
|
|
||||||
|
func NewSqlRepo() repository.DatabaseDriverRepo {
|
||||||
|
return &sqlDriverRepo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSqlcRepo() repository.DatabaseDriverRepo {
|
||||||
|
return &sqlcDriverRepo{}
|
||||||
|
}
|
48
internal/repository/driverrepo/sqlcdriver.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package driverrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go-udemy-web-1/internal/repository"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *sqlcDriverRepo) ConnectSQL(dsn string) (*repository.DB, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
c, err := s.newConn(ctx, dsn)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConn.PG = c
|
||||||
|
|
||||||
|
err = s.testDB(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dbConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// testDB tries to ping the database
|
||||||
|
func (s *sqlcDriverRepo) testDB(ctx context.Context, c *pgx.Conn) error {
|
||||||
|
err := c.Ping(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDatabase creates a new database for the application
|
||||||
|
func (s *sqlcDriverRepo) newConn(ctx context.Context, dsn string) (*pgx.Conn, error) {
|
||||||
|
conn, err := pgx.Connect(ctx, dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping test the conn
|
||||||
|
if err = conn.Ping(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, err
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package driver
|
package driverrepo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"go-udemy-web-1/internal/repository"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/jackc/pgconn"
|
_ "github.com/jackc/pgconn"
|
||||||
@ -9,13 +10,6 @@ import (
|
|||||||
_ "github.com/jackc/pgx/v5/stdlib"
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB holds the database connection pool
|
|
||||||
type DB struct {
|
|
||||||
SQL *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbConn = &DB{}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxOpenDbConn = 10
|
maxOpenDbConn = 10
|
||||||
maxIdleDbConn = 5
|
maxIdleDbConn = 5
|
||||||
@ -23,8 +17,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ConnectSQL creates SQL pool for Postgres
|
// ConnectSQL creates SQL pool for Postgres
|
||||||
func ConnectSQL(dsn string) (*DB, error) {
|
func (s *sqlDriverRepo) ConnectSQL(dsn string) (*repository.DB, error) {
|
||||||
d, err := NewDatabase(dsn)
|
d, err := s.newDatabase(dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -35,7 +29,7 @@ func ConnectSQL(dsn string) (*DB, error) {
|
|||||||
|
|
||||||
dbConn.SQL = d
|
dbConn.SQL = d
|
||||||
|
|
||||||
err = testDB(d)
|
err = s.testDB(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -43,7 +37,7 @@ func ConnectSQL(dsn string) (*DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// testDB tries to ping the database
|
// testDB tries to ping the database
|
||||||
func testDB(d *sql.DB) error {
|
func (s *sqlDriverRepo) testDB(d *sql.DB) error {
|
||||||
err := d.Ping()
|
err := d.Ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -51,8 +45,8 @@ func testDB(d *sql.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase creates a new datavase for the application
|
// newDatabase creates a new database for the application
|
||||||
func NewDatabase(dsn string) (*sql.DB, error) {
|
func (s *sqlDriverRepo) newDatabase(dsn string) (*sql.DB, error) {
|
||||||
db, err := sql.Open("pgx", dsn)
|
db, err := sql.Open("pgx", dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
@ -1,8 +1,11 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"go-udemy-web-1/internal/models"
|
"go-udemy-web-1/internal/models"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseRepo interface {
|
type DatabaseRepo interface {
|
||||||
@ -16,4 +19,24 @@ type DatabaseRepo interface {
|
|||||||
GetUserByID(id int) (models.User, error)
|
GetUserByID(id int) (models.User, error)
|
||||||
UpdateUser(u models.User) error
|
UpdateUser(u models.User) error
|
||||||
Authenticate(email, testPassword string) (int, string, error)
|
Authenticate(email, testPassword string) (int, string, error)
|
||||||
|
AllReservations() ([]models.Reservation, error)
|
||||||
|
AllNewReservations() ([]models.Reservation, error)
|
||||||
|
GetReservationByID(id int) (models.Reservation, error)
|
||||||
|
UpdateReservation(r models.Reservation) error
|
||||||
|
DeleteReservation(id int) error
|
||||||
|
UpdateProcessedForReservation(id, processed int) error
|
||||||
|
AllRooms() ([]models.Room, error)
|
||||||
|
GetRestrictionsForRoomByDate(roomId int, start, end time.Time) ([]models.RoomRestriction, error)
|
||||||
|
InsertBlockForRoom(id int, startDate time.Time) error
|
||||||
|
DeleteBlockByID(id int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB holds the database connection pool
|
||||||
|
type DB struct {
|
||||||
|
SQL *sql.DB
|
||||||
|
PG *pgx.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatabaseDriverRepo interface {
|
||||||
|
ConnectSQL(dsn string) (*DB, error)
|
||||||
}
|
}
|
||||||
|
32
internal/repository/sqlc/db.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.26.0
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||||
|
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||||
|
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
63
internal/repository/sqlc/models.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.26.0
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reservation struct {
|
||||||
|
ID int32
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
Processed int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Restriction struct {
|
||||||
|
ID int32
|
||||||
|
RestrictionName string
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
ID int32
|
||||||
|
RoomName string
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoomRestriction struct {
|
||||||
|
ID int32
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
ReservationID pgtype.Int4
|
||||||
|
RestrictionID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchemaMigration struct {
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int32
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
AccessLevel int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
685
internal/repository/sqlc/query.sql.go
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.26.0
|
||||||
|
// source: query.sql
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const allNewReservations = `-- name: AllNewReservations :many
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.first_name,
|
||||||
|
r.last_name,
|
||||||
|
r.email,
|
||||||
|
r.phone,
|
||||||
|
r.start_date,
|
||||||
|
r.end_date,
|
||||||
|
r.room_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.processed,
|
||||||
|
rm.id,
|
||||||
|
rm.room_name
|
||||||
|
FROM
|
||||||
|
reservations r
|
||||||
|
LEFT JOIN rooms rm ON (r.room_id = rm.id)
|
||||||
|
WHERE
|
||||||
|
r.processed = 0
|
||||||
|
ORDER BY
|
||||||
|
r.start_date ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type AllNewReservationsRow struct {
|
||||||
|
ID int32
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
Processed int32
|
||||||
|
ID_2 pgtype.Int4
|
||||||
|
RoomName pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AllNewReservations(ctx context.Context) ([]AllNewReservationsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, allNewReservations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []AllNewReservationsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i AllNewReservationsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.Phone,
|
||||||
|
&i.StartDate,
|
||||||
|
&i.EndDate,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Processed,
|
||||||
|
&i.ID_2,
|
||||||
|
&i.RoomName,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const allReservations = `-- name: AllReservations :many
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.first_name,
|
||||||
|
r.last_name,
|
||||||
|
r.email,
|
||||||
|
r.phone,
|
||||||
|
r.start_date,
|
||||||
|
r.end_date,
|
||||||
|
r.room_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.processed,
|
||||||
|
rm.id,
|
||||||
|
rm.room_name
|
||||||
|
FROM
|
||||||
|
reservations r
|
||||||
|
LEFT JOIN rooms rm ON (r.room_id = rm.id)
|
||||||
|
ORDER BY
|
||||||
|
r.start_date ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type AllReservationsRow struct {
|
||||||
|
ID int32
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
Processed int32
|
||||||
|
ID_2 pgtype.Int4
|
||||||
|
RoomName pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AllReservations(ctx context.Context) ([]AllReservationsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, allReservations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []AllReservationsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i AllReservationsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.Phone,
|
||||||
|
&i.StartDate,
|
||||||
|
&i.EndDate,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Processed,
|
||||||
|
&i.ID_2,
|
||||||
|
&i.RoomName,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const allRooms = `-- name: AllRooms :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
room_name,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
rooms
|
||||||
|
ORDER BY
|
||||||
|
room_name
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) AllRooms(ctx context.Context) ([]Room, error) {
|
||||||
|
rows, err := q.db.Query(ctx, allRooms)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Room
|
||||||
|
for rows.Next() {
|
||||||
|
var i Room
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RoomName,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBlockByID = `-- name: DeleteBlockByID :exec
|
||||||
|
DELETE FROM room_restrictions
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteBlockByID(ctx context.Context, id int32) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteBlockByID, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteReservation = `-- name: DeleteReservation :exec
|
||||||
|
DELETE FROM reservations
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteReservation(ctx context.Context, id int32) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteReservation, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getReservationByID = `-- name: GetReservationByID :one
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.first_name,
|
||||||
|
r.last_name,
|
||||||
|
r.email,
|
||||||
|
r.phone,
|
||||||
|
r.start_date,
|
||||||
|
r.end_date,
|
||||||
|
r.room_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.processed,
|
||||||
|
rm.id,
|
||||||
|
rm.room_name
|
||||||
|
FROM
|
||||||
|
reservations r
|
||||||
|
LEFT JOIN rooms rm ON (r.room_id = rm.id)
|
||||||
|
WHERE
|
||||||
|
r.id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetReservationByIDRow struct {
|
||||||
|
ID int32
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
Processed int32
|
||||||
|
ID_2 pgtype.Int4
|
||||||
|
RoomName pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetReservationByID(ctx context.Context, id int32) (GetReservationByIDRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getReservationByID, id)
|
||||||
|
var i GetReservationByIDRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.Phone,
|
||||||
|
&i.StartDate,
|
||||||
|
&i.EndDate,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.Processed,
|
||||||
|
&i.ID_2,
|
||||||
|
&i.RoomName,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRestrictionsForRoomByDate = `-- name: GetRestrictionsForRoomByDate :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
coalesce(reservation_id, 0),
|
||||||
|
restriction_id,
|
||||||
|
room_id,
|
||||||
|
start_date,
|
||||||
|
end_date
|
||||||
|
FROM
|
||||||
|
room_restrictions
|
||||||
|
WHERE
|
||||||
|
$1 < end_date
|
||||||
|
AND $2 >= start_date
|
||||||
|
AND room_id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetRestrictionsForRoomByDateParams struct {
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRestrictionsForRoomByDateRow struct {
|
||||||
|
ID int32
|
||||||
|
ReservationID int32
|
||||||
|
RestrictionID int32
|
||||||
|
RoomID int32
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetRestrictionsForRoomByDate(ctx context.Context, arg GetRestrictionsForRoomByDateParams) ([]GetRestrictionsForRoomByDateRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getRestrictionsForRoomByDate, arg.StartDate, arg.EndDate, arg.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetRestrictionsForRoomByDateRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetRestrictionsForRoomByDateRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ReservationID,
|
||||||
|
&i.RestrictionID,
|
||||||
|
&i.RoomID,
|
||||||
|
&i.StartDate,
|
||||||
|
&i.EndDate,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoomById = `-- name: GetRoomById :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
room_name,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
rooms
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetRoomById(ctx context.Context, id int32) (Room, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getRoomById, id)
|
||||||
|
var i Room
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.RoomName,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserByID = `-- name: GetUserByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
access_level,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
users
|
||||||
|
WHERE
|
||||||
|
id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByID(ctx context.Context, id int32) (User, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserByID, id)
|
||||||
|
var i User
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.Password,
|
||||||
|
&i.AccessLevel,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserCred = `-- name: GetUserCred :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
password
|
||||||
|
FROM
|
||||||
|
users
|
||||||
|
WHERE
|
||||||
|
email = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserCredRow struct {
|
||||||
|
ID int32
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserCred(ctx context.Context, email string) (GetUserCredRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserCred, email)
|
||||||
|
var i GetUserCredRow
|
||||||
|
err := row.Scan(&i.ID, &i.Password)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertBlockForRoom = `-- name: InsertBlockForRoom :exec
|
||||||
|
INSERT INTO
|
||||||
|
room_restrictions (
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
room_id,
|
||||||
|
restriction_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5, $6)
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertBlockForRoomParams struct {
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
RestrictionID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertBlockForRoom(ctx context.Context, arg InsertBlockForRoomParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, insertBlockForRoom,
|
||||||
|
arg.StartDate,
|
||||||
|
arg.EndDate,
|
||||||
|
arg.RoomID,
|
||||||
|
arg.RestrictionID,
|
||||||
|
arg.CreatedAt,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertReservation = `-- name: InsertReservation :one
|
||||||
|
INSERT INTO
|
||||||
|
reservations (
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
room_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
|
RETURNING
|
||||||
|
id
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertReservationParams struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertReservation(ctx context.Context, arg InsertReservationParams) (int32, error) {
|
||||||
|
row := q.db.QueryRow(ctx, insertReservation,
|
||||||
|
arg.FirstName,
|
||||||
|
arg.LastName,
|
||||||
|
arg.Email,
|
||||||
|
arg.Phone,
|
||||||
|
arg.StartDate,
|
||||||
|
arg.EndDate,
|
||||||
|
arg.RoomID,
|
||||||
|
arg.CreatedAt,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
)
|
||||||
|
var id int32
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertRoomRestriction = `-- name: InsertRoomRestriction :exec
|
||||||
|
INSERT INTO
|
||||||
|
room_restrictions (
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
room_id,
|
||||||
|
reservation_id,
|
||||||
|
restriction_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertRoomRestrictionParams struct {
|
||||||
|
StartDate pgtype.Date
|
||||||
|
EndDate pgtype.Date
|
||||||
|
RoomID int32
|
||||||
|
ReservationID pgtype.Int4
|
||||||
|
RestrictionID int32
|
||||||
|
CreatedAt pgtype.Timestamp
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertRoomRestriction(ctx context.Context, arg InsertRoomRestrictionParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, insertRoomRestriction,
|
||||||
|
arg.StartDate,
|
||||||
|
arg.EndDate,
|
||||||
|
arg.RoomID,
|
||||||
|
arg.ReservationID,
|
||||||
|
arg.RestrictionID,
|
||||||
|
arg.CreatedAt,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchAvailabilityByDatesByRoomID = `-- name: SearchAvailabilityByDatesByRoomID :one
|
||||||
|
SELECT
|
||||||
|
count(id)
|
||||||
|
FROM
|
||||||
|
room_restrictions
|
||||||
|
WHERE
|
||||||
|
room_id = $1
|
||||||
|
AND $2 < end_date
|
||||||
|
AND $3 > start_date
|
||||||
|
`
|
||||||
|
|
||||||
|
type SearchAvailabilityByDatesByRoomIDParams struct {
|
||||||
|
RoomID int32
|
||||||
|
EndDate pgtype.Date
|
||||||
|
StartDate pgtype.Date
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SearchAvailabilityByDatesByRoomID(ctx context.Context, arg SearchAvailabilityByDatesByRoomIDParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, searchAvailabilityByDatesByRoomID, arg.RoomID, arg.EndDate, arg.StartDate)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchAvailabilityForAllRooms = `-- name: SearchAvailabilityForAllRooms :many
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.room_name
|
||||||
|
FROM
|
||||||
|
rooms r
|
||||||
|
WHERE
|
||||||
|
r.id NOT IN (
|
||||||
|
SELECT
|
||||||
|
room_id
|
||||||
|
FROM
|
||||||
|
room_restrictions rr
|
||||||
|
WHERE
|
||||||
|
$1 < rr.end_date
|
||||||
|
AND $2 > rr.start_date
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type SearchAvailabilityForAllRoomsParams struct {
|
||||||
|
EndDate pgtype.Date
|
||||||
|
StartDate pgtype.Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchAvailabilityForAllRoomsRow struct {
|
||||||
|
ID int32
|
||||||
|
RoomName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SearchAvailabilityForAllRooms(ctx context.Context, arg SearchAvailabilityForAllRoomsParams) ([]SearchAvailabilityForAllRoomsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, searchAvailabilityForAllRooms, arg.EndDate, arg.StartDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []SearchAvailabilityForAllRoomsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i SearchAvailabilityForAllRoomsRow
|
||||||
|
if err := rows.Scan(&i.ID, &i.RoomName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProcessedForReservation = `-- name: UpdateProcessedForReservation :exec
|
||||||
|
UPDATE reservations
|
||||||
|
SET
|
||||||
|
processed = $1
|
||||||
|
WHERE
|
||||||
|
id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateProcessedForReservationParams struct {
|
||||||
|
Processed int32
|
||||||
|
ID int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateProcessedForReservation(ctx context.Context, arg UpdateProcessedForReservationParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateProcessedForReservation, arg.Processed, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateReservation = `-- name: UpdateReservation :exec
|
||||||
|
UPDATE reservations
|
||||||
|
SET
|
||||||
|
first_name = $1,
|
||||||
|
last_name = $2,
|
||||||
|
email = $3,
|
||||||
|
phone = $4,
|
||||||
|
updated_at = $5
|
||||||
|
WHERE
|
||||||
|
id = $6
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateReservationParams struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
Phone string
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
ID int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateReservation(ctx context.Context, arg UpdateReservationParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateReservation,
|
||||||
|
arg.FirstName,
|
||||||
|
arg.LastName,
|
||||||
|
arg.Email,
|
||||||
|
arg.Phone,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUser = `-- name: UpdateUser :exec
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
first_name = $1,
|
||||||
|
last_name = $2,
|
||||||
|
email = $3,
|
||||||
|
access_level = $4,
|
||||||
|
updated_at = $5
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateUserParams struct {
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email string
|
||||||
|
AccessLevel int32
|
||||||
|
UpdatedAt pgtype.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateUser,
|
||||||
|
arg.FirstName,
|
||||||
|
arg.LastName,
|
||||||
|
arg.Email,
|
||||||
|
arg.AccessLevel,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
drop_column("reservations", "processed")
|
@ -0,0 +1 @@
|
|||||||
|
add_column("reservations", "processed", "integer", {"default": 0})
|
2
run.sh
@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
go build -o bookings cmd/web/*go && ./bookings
|
go build -o bookings cmd/web/*go && ./bookings -cache=false -production=false
|
||||||
|
234
sql/query.sql
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
-- name: InsertReservation :one
|
||||||
|
INSERT INTO
|
||||||
|
reservations (
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
room_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
|
RETURNING
|
||||||
|
id;
|
||||||
|
|
||||||
|
-- name: InsertRoomRestriction :exec
|
||||||
|
INSERT INTO
|
||||||
|
room_restrictions (
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
room_id,
|
||||||
|
reservation_id,
|
||||||
|
restriction_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5, $6, $7);
|
||||||
|
|
||||||
|
-- name: SearchAvailabilityByDatesByRoomID :one
|
||||||
|
SELECT
|
||||||
|
count(id)
|
||||||
|
FROM
|
||||||
|
room_restrictions
|
||||||
|
WHERE
|
||||||
|
room_id = $1
|
||||||
|
AND $2 < end_date
|
||||||
|
AND $3 > start_date;
|
||||||
|
|
||||||
|
-- name: SearchAvailabilityForAllRooms :many
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.room_name
|
||||||
|
FROM
|
||||||
|
rooms r
|
||||||
|
WHERE
|
||||||
|
r.id NOT IN (
|
||||||
|
SELECT
|
||||||
|
room_id
|
||||||
|
FROM
|
||||||
|
room_restrictions rr
|
||||||
|
WHERE
|
||||||
|
$1 < rr.end_date
|
||||||
|
AND $2 > rr.start_date
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: GetRoomById :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
room_name,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
rooms
|
||||||
|
WHERE
|
||||||
|
id = $1;
|
||||||
|
|
||||||
|
-- name: GetUserByID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
access_level,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
users
|
||||||
|
WHERE
|
||||||
|
id = $1;
|
||||||
|
|
||||||
|
-- name: UpdateUser :exec
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
first_name = $1,
|
||||||
|
last_name = $2,
|
||||||
|
email = $3,
|
||||||
|
access_level = $4,
|
||||||
|
updated_at = $5;
|
||||||
|
|
||||||
|
-- name: GetUserCred :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
password
|
||||||
|
FROM
|
||||||
|
users
|
||||||
|
WHERE
|
||||||
|
email = $1;
|
||||||
|
|
||||||
|
-- name: AllReservations :many
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.first_name,
|
||||||
|
r.last_name,
|
||||||
|
r.email,
|
||||||
|
r.phone,
|
||||||
|
r.start_date,
|
||||||
|
r.end_date,
|
||||||
|
r.room_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.processed,
|
||||||
|
rm.id,
|
||||||
|
rm.room_name
|
||||||
|
FROM
|
||||||
|
reservations r
|
||||||
|
LEFT JOIN rooms rm ON (r.room_id = rm.id)
|
||||||
|
ORDER BY
|
||||||
|
r.start_date ASC;
|
||||||
|
|
||||||
|
-- name: AllNewReservations :many
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.first_name,
|
||||||
|
r.last_name,
|
||||||
|
r.email,
|
||||||
|
r.phone,
|
||||||
|
r.start_date,
|
||||||
|
r.end_date,
|
||||||
|
r.room_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.processed,
|
||||||
|
rm.id,
|
||||||
|
rm.room_name
|
||||||
|
FROM
|
||||||
|
reservations r
|
||||||
|
LEFT JOIN rooms rm ON (r.room_id = rm.id)
|
||||||
|
WHERE
|
||||||
|
r.processed = 0
|
||||||
|
ORDER BY
|
||||||
|
r.start_date ASC;
|
||||||
|
|
||||||
|
-- name: GetReservationByID :one
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.first_name,
|
||||||
|
r.last_name,
|
||||||
|
r.email,
|
||||||
|
r.phone,
|
||||||
|
r.start_date,
|
||||||
|
r.end_date,
|
||||||
|
r.room_id,
|
||||||
|
r.created_at,
|
||||||
|
r.updated_at,
|
||||||
|
r.processed,
|
||||||
|
rm.id,
|
||||||
|
rm.room_name
|
||||||
|
FROM
|
||||||
|
reservations r
|
||||||
|
LEFT JOIN rooms rm ON (r.room_id = rm.id)
|
||||||
|
WHERE
|
||||||
|
r.id = $1;
|
||||||
|
|
||||||
|
-- name: UpdateReservation :exec
|
||||||
|
UPDATE reservations
|
||||||
|
SET
|
||||||
|
first_name = $1,
|
||||||
|
last_name = $2,
|
||||||
|
email = $3,
|
||||||
|
phone = $4,
|
||||||
|
updated_at = $5
|
||||||
|
WHERE
|
||||||
|
id = $6;
|
||||||
|
|
||||||
|
-- name: DeleteReservation :exec
|
||||||
|
DELETE FROM reservations
|
||||||
|
WHERE
|
||||||
|
id = $1;
|
||||||
|
|
||||||
|
-- name: UpdateProcessedForReservation :exec
|
||||||
|
UPDATE reservations
|
||||||
|
SET
|
||||||
|
processed = $1
|
||||||
|
WHERE
|
||||||
|
id = $2;
|
||||||
|
|
||||||
|
-- name: AllRooms :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
room_name,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
rooms
|
||||||
|
ORDER BY
|
||||||
|
room_name;
|
||||||
|
|
||||||
|
-- name: GetRestrictionsForRoomByDate :many
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
coalesce(reservation_id, 0),
|
||||||
|
restriction_id,
|
||||||
|
room_id,
|
||||||
|
start_date,
|
||||||
|
end_date
|
||||||
|
FROM
|
||||||
|
room_restrictions
|
||||||
|
WHERE
|
||||||
|
@start_date < end_date
|
||||||
|
AND @end_date >= start_date
|
||||||
|
AND room_id = @room_id;
|
||||||
|
|
||||||
|
-- name: InsertBlockForRoom :exec
|
||||||
|
INSERT INTO
|
||||||
|
room_restrictions (
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
room_id,
|
||||||
|
restriction_id,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5, $6);
|
||||||
|
|
||||||
|
-- name: DeleteBlockByID :exec
|
||||||
|
DELETE FROM room_restrictions
|
||||||
|
WHERE
|
||||||
|
id = $1;
|
96
sql/schema.sql
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
-- Adminer 4.8.1 PostgreSQL 16.3 (Debian 16.3-1.pgdg120+1) dump
|
||||||
|
|
||||||
|
CREATE SEQUENCE reservations_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE "public"."reservations" (
|
||||||
|
"id" integer DEFAULT nextval('reservations_id_seq') NOT NULL,
|
||||||
|
"first_name" character varying(255) DEFAULT '' NOT NULL,
|
||||||
|
"last_name" character varying(255) DEFAULT '' NOT NULL,
|
||||||
|
"email" character varying(255) NOT NULL,
|
||||||
|
"phone" character varying(255) DEFAULT '' NOT NULL,
|
||||||
|
"start_date" date NOT NULL,
|
||||||
|
"end_date" date NOT NULL,
|
||||||
|
"room_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
"processed" integer DEFAULT '0' NOT NULL,
|
||||||
|
CONSTRAINT "reservations_pkey" PRIMARY KEY ("id")
|
||||||
|
) WITH (oids = false);
|
||||||
|
|
||||||
|
CREATE INDEX "reservations_email_idx" ON "public"."reservations" USING btree ("email");
|
||||||
|
|
||||||
|
CREATE INDEX "reservations_last_name_idx" ON "public"."reservations" USING btree ("last_name");
|
||||||
|
|
||||||
|
|
||||||
|
CREATE SEQUENCE restrictions_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE "public"."restrictions" (
|
||||||
|
"id" integer DEFAULT nextval('restrictions_id_seq') NOT NULL,
|
||||||
|
"restriction_name" character varying(255) NOT NULL,
|
||||||
|
"created_at" timestamp NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
CONSTRAINT "restrictions_pkey" PRIMARY KEY ("id")
|
||||||
|
) WITH (oids = false);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE SEQUENCE room_restrictions_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE "public"."room_restrictions" (
|
||||||
|
"id" integer DEFAULT nextval('room_restrictions_id_seq') NOT NULL,
|
||||||
|
"start_date" date NOT NULL,
|
||||||
|
"end_date" date NOT NULL,
|
||||||
|
"room_id" integer NOT NULL,
|
||||||
|
"reservation_id" integer,
|
||||||
|
"restriction_id" integer NOT NULL,
|
||||||
|
"created_at" timestamp NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
CONSTRAINT "room_restrictions_pkey" PRIMARY KEY ("id")
|
||||||
|
) WITH (oids = false);
|
||||||
|
|
||||||
|
CREATE INDEX "room_restrictions_reservation_id_idx" ON "public"."room_restrictions" USING btree ("reservation_id");
|
||||||
|
|
||||||
|
CREATE INDEX "room_restrictions_room_id_idx" ON "public"."room_restrictions" USING btree ("room_id");
|
||||||
|
|
||||||
|
CREATE INDEX "room_restrictions_start_date_end_date_idx" ON "public"."room_restrictions" USING btree ("start_date", "end_date");
|
||||||
|
|
||||||
|
|
||||||
|
CREATE SEQUENCE rooms_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE "public"."rooms" (
|
||||||
|
"id" integer DEFAULT nextval('rooms_id_seq') NOT NULL,
|
||||||
|
"room_name" character varying(255) NOT NULL,
|
||||||
|
"created_at" timestamp NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
CONSTRAINT "rooms_pkey" PRIMARY KEY ("id")
|
||||||
|
) WITH (oids = false);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE "public"."schema_migration" (
|
||||||
|
"version" character varying(14) NOT NULL,
|
||||||
|
CONSTRAINT "schema_migration_pkey" PRIMARY KEY ("version"),
|
||||||
|
CONSTRAINT "schema_migration_version_idx" UNIQUE ("version")
|
||||||
|
) WITH (oids = false);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE SEQUENCE users_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE "public"."users" (
|
||||||
|
"id" integer DEFAULT nextval('users_id_seq') NOT NULL,
|
||||||
|
"first_name" character varying(255) DEFAULT '' NOT NULL,
|
||||||
|
"last_name" character varying(255) DEFAULT '' NOT NULL,
|
||||||
|
"email" character varying(255) NOT NULL,
|
||||||
|
"password" character varying(60) NOT NULL,
|
||||||
|
"access_level" integer DEFAULT '1' NOT NULL,
|
||||||
|
"created_at" timestamp NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
CONSTRAINT "users_email_idx" UNIQUE ("email"),
|
||||||
|
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||||
|
) WITH (oids = false);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY "public"."reservations" ADD CONSTRAINT "reservations_rooms_id_fk" FOREIGN KEY (room_id) REFERENCES rooms(id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY "public"."room_restrictions" ADD CONSTRAINT "room_restrictions_reservations_id_fk" FOREIGN KEY (reservation_id) REFERENCES reservations(id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE;
|
||||||
|
ALTER TABLE ONLY "public"."room_restrictions" ADD CONSTRAINT "room_restrictions_restrictions_id_fk" FOREIGN KEY (restriction_id) REFERENCES restrictions(id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE;
|
||||||
|
ALTER TABLE ONLY "public"."room_restrictions" ADD CONSTRAINT "room_restrictions_rooms_id_fk" FOREIGN KEY (room_id) REFERENCES rooms(id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE;
|
||||||
|
|
10
sqlc.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: "2"
|
||||||
|
sql:
|
||||||
|
- engine: "postgresql"
|
||||||
|
queries: "sql/query.sql"
|
||||||
|
schema: "sql/schema.sql"
|
||||||
|
gen:
|
||||||
|
go:
|
||||||
|
package: "sqlc"
|
||||||
|
out: "internal/repository/sqlc"
|
||||||
|
sql_package: "pgx/v5"
|
BIN
static/admin/css/.DS_Store
vendored
Normal file
1
static/admin/css/maps/style.css.map
Normal file
22631
static/admin/css/style.css
Normal file
BIN
static/admin/documentation/.DS_Store
vendored
Normal file
137
static/admin/documentation/documentation.html
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>RoyalUI Admin Dashboard</title>
|
||||||
|
<!-- plugins:css -->
|
||||||
|
<link rel="stylesheet" href="../vendors/ti-icons/css/themify-icons.css">
|
||||||
|
<link rel="stylesheet" href="../vendors/base/vendor.bundle.base.css">
|
||||||
|
<!-- endinject -->
|
||||||
|
<!-- plugin css for this page -->
|
||||||
|
<!-- End plugin css for this page -->
|
||||||
|
<!-- inject:css -->
|
||||||
|
<link rel="stylesheet" href="../css/style.css">
|
||||||
|
<!-- endinject -->
|
||||||
|
<link rel="shortcut icon" href="../images/favicon.png" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container-scroller">
|
||||||
|
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||||
|
<div class="main-panel w-100 documentation">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 doc-header">
|
||||||
|
<a class="btn btn-success" href="../index.html"><i class="mdi mdi-home me-2"></i>Back to home</a>
|
||||||
|
<h1 class="text-primary mt-4">Documentation</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row doc-content">
|
||||||
|
<div class="col-12 col-md-10 offset-md-1">
|
||||||
|
<div class="col-12 grid-margin" id="doc-intro">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="mb-4 mt-4">Introduction</h3>
|
||||||
|
<p>RoyalUI Admin is a responsive HTML template that is based on the CSS framework Bootstrap 5 and it is built with Sass. Sass compiler makes it easier to code and customize. If you are unfamiliar with Bootstrap or Sass, visit their
|
||||||
|
website and read through the documentation. All of Bootstrap components have been modified to fit the style of RoyalUI Admin and provide a consistent look throughout the template.</p>
|
||||||
|
<p>Before you start working with the template, we suggest you go through the pages that are bundled with the theme. Most of the template example pages contain quick tips on how to create or use a component which can
|
||||||
|
be really helpful when you need to create something on the fly.</p>
|
||||||
|
<p class="d-inline"><strong>Note</strong>: We are trying our best to document how to use the template. If you think that something is missing from the documentation, please do not hesitate to tell us about it. If you have any questions or issues regarding this theme please email us at <a class="d-inline text-info" href="mailto:info@bootstrapdash.com">info@bootstrapdash.com</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 grid-margin" id="doc-started">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="mb-4">Getting started</h3>
|
||||||
|
<p>You can directly use the compiled and ready-to-use the version of the template. But in case you plan to customize the template extensively the template allows you to do so.</p>
|
||||||
|
<p>Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations:</p>
|
||||||
|
<pre>
|
||||||
|
RoyalUI/
|
||||||
|
├── template/
|
||||||
|
├── css/
|
||||||
|
├── fonts/
|
||||||
|
├── images/
|
||||||
|
├── js/
|
||||||
|
├── pages/
|
||||||
|
├── partials/
|
||||||
|
├── index.html
|
||||||
|
├── scss/
|
||||||
|
├── vendors/
|
||||||
|
├── gulpfile.js
|
||||||
|
├── package.json
|
||||||
|
├── documentation/
|
||||||
|
├── CHANGELOG.md</pre>
|
||||||
|
<p class="mt-1">Note: The root folder denoted further in this documentation refers to the 'template' folder inside the downloaded folder</p>
|
||||||
|
<div class="alert alert-success mt-4 d-flex align-items-center" role="alert">
|
||||||
|
<i class="ti-info-alt me-4"></i>
|
||||||
|
<p>We have bundled up the vendor files needed for demo purpose into a folder 'vendors', you may not need all those vendors or may need to add more vendors in your application. If you want to make any change in the vendor package files, you can either change the src path for related tasks in the file gulpfile.js and run the task <code> bundleVendors </code> to rebuild the vendor files or manually edit the vendor folder.</p>
|
||||||
|
</div>
|
||||||
|
<hr class="mt-5">
|
||||||
|
<h4 class="mt-4">Installation</h4>
|
||||||
|
<p class="mb-0">
|
||||||
|
You need to install package files/Dependencies for this project if you want to customize it. To do this, you must have <span class="font-weight-bold">node and npm</span> installed in your computer.
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">Installation guide of the node can be found <a href="https://nodejs.org/en/">here</a>. As npm comes bundled with a node, a separate installation of npm is not needed.</p>
|
||||||
|
<p>
|
||||||
|
If you have installed them, just go to the root folder and run the following command in your command prompt or terminal (for the mac users).
|
||||||
|
</p>
|
||||||
|
<pre class="shell-mode">
|
||||||
|
npm install</pre>
|
||||||
|
<p class="mt-4">
|
||||||
|
This will install the dev dependencies in the local <span class="font-weight-bold">node_modules</span> folder in your root directory.
|
||||||
|
</p>
|
||||||
|
<p class="mt-2">
|
||||||
|
Then you will need to install <span class="font-weight-bold">Gulp</span>. We use the Gulp task manager for the development processes. Gulp will watch for changes to the SCSS files and automatically compile the files to CSS.
|
||||||
|
</p>
|
||||||
|
<p>Getting started with Gulp is pretty simple. The <a href="https://gulpjs.com/" target="_blank">Gulp</a> site is a great place to get information on installing Gulp if you need more information. You need to first install Gulp-cli in your machine using the below command.</p>
|
||||||
|
<pre class="shell-mode">
|
||||||
|
npm install -g gulp-cli</pre>
|
||||||
|
<p class="mt-4">This installs Gulp-cli globally to your machine. The other thing that Gulp requires, which, is really what does all the work, is the gulpfile.js. In this file, you set up all of your tasks that you will run.</p>
|
||||||
|
<p>Don't worry. We have this file already created for you!</p>
|
||||||
|
<p>To run this project in development mode enter the following command below. This will start the file watch by gulp and whenever a file is modified, the SCSS files will be compiled to create the CSS file.</p>
|
||||||
|
<pre class="shell-mode">
|
||||||
|
gulp serve</pre>
|
||||||
|
<div class="alert alert-warning mt-4" role="alert">
|
||||||
|
<i class="ti-info-alt-outline"></i>It is important to run <code>gulp serve</code> command from the directory where the gulpfile.js is located.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- partial:../partials/_footer.html -->
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="d-sm-flex justify-content-center justify-content-sm-between">
|
||||||
|
<span class="text-muted text-center text-sm-left d-block d-sm-inline-block">Copyright © 2018 <a href="https://www.bootstrapdash.com/" target="_blank">Bootstrapdash</a>. All rights reserved.</span>
|
||||||
|
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Hand-crafted & made with <i class="ti-heart text-danger ms-1"></i></span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- partial -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- plugins:js -->
|
||||||
|
<script src="../vendors/base/vendor.bundle.base.js"></script>
|
||||||
|
<!-- endinject -->
|
||||||
|
<!-- inject:js -->
|
||||||
|
<script src="../js/off-canvas.js"></script>
|
||||||
|
<script src="../js/hoverable-collapse.js"></script>
|
||||||
|
<script src="../js/template.js"></script>
|
||||||
|
<script src="../js/todolist.js"></script>
|
||||||
|
<!-- endinject -->
|
||||||
|
<!-- Custom js for this page-->
|
||||||
|
<script src="../js/documentation.js"></script>
|
||||||
|
<!-- End custom js for this page-->
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
BIN
static/admin/fonts/.DS_Store
vendored
Normal file
BIN
static/admin/fonts/Roboto/.DS_Store
vendored
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Black.eot
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Black.ttf
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Black.woff
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Black.woff2
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Bold.eot
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Bold.ttf
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Bold.woff
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Bold.woff2
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Light.eot
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Light.ttf
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Light.woff
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Light.woff2
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Medium.eot
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Medium.ttf
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Medium.woff
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Medium.woff2
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Regular.eot
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Regular.ttf
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Regular.woff
Normal file
BIN
static/admin/fonts/Roboto/Roboto-Regular.woff2
Normal file
176
static/admin/gulpfile.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var browserSync = require('browser-sync').create();
|
||||||
|
var sass = require('gulp-sass');
|
||||||
|
var del = require('del');
|
||||||
|
var replace = require('gulp-replace');
|
||||||
|
var injectPartials = require('gulp-inject-partials');
|
||||||
|
var inject = require('gulp-inject');
|
||||||
|
var sourcemaps = require('gulp-sourcemaps');
|
||||||
|
var concat = require('gulp-concat');
|
||||||
|
var merge = require('merge-stream');
|
||||||
|
|
||||||
|
gulp.paths = {
|
||||||
|
dist: 'dist',
|
||||||
|
};
|
||||||
|
|
||||||
|
var paths = gulp.paths;
|
||||||
|
|
||||||
|
gulp.task('sass', function () {
|
||||||
|
return gulp.src('./scss/**/style.scss')
|
||||||
|
.pipe(sourcemaps.init())
|
||||||
|
.pipe(sass({outputStyle: 'expanded'}).on('error', sass.logError))
|
||||||
|
.pipe(sourcemaps.write('./maps'))
|
||||||
|
.pipe(gulp.dest('./css'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Static Server + watching scss/html files
|
||||||
|
gulp.task('serve', gulp.series('sass', function() {
|
||||||
|
|
||||||
|
browserSync.init({
|
||||||
|
port: 3100,
|
||||||
|
server: "./",
|
||||||
|
ghostMode: false,
|
||||||
|
notify: false
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.watch('scss/**/*.scss', gulp.series('sass'));
|
||||||
|
gulp.watch('**/*.html').on('change', browserSync.reload);
|
||||||
|
gulp.watch('js/**/*.js').on('change', browserSync.reload);
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Static Server without watching scss files
|
||||||
|
gulp.task('serve:lite', function() {
|
||||||
|
|
||||||
|
browserSync.init({
|
||||||
|
server: "./",
|
||||||
|
ghostMode: false,
|
||||||
|
notify: false
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.watch('**/*.css').on('change', browserSync.reload);
|
||||||
|
gulp.watch('**/*.html').on('change', browserSync.reload);
|
||||||
|
gulp.watch('js/**/*.js').on('change', browserSync.reload);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('sass:watch', function () {
|
||||||
|
gulp.watch('./scss/**/*.scss');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* inject partials like sidebar and navbar */
|
||||||
|
gulp.task('injectPartial', function () {
|
||||||
|
var injPartial1 = gulp.src("./pages/**/*.html", { base: "./" })
|
||||||
|
.pipe(injectPartials())
|
||||||
|
.pipe(gulp.dest("."));
|
||||||
|
var injPartial2 = gulp.src("./*.html", { base: "./" })
|
||||||
|
.pipe(injectPartials())
|
||||||
|
.pipe(gulp.dest("."));
|
||||||
|
return merge(injPartial1, injPartial2);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* inject Js and CCS assets into HTML */
|
||||||
|
gulp.task('injectCommonAssets', function () {
|
||||||
|
return gulp.src('./**/*.html')
|
||||||
|
.pipe(inject(gulp.src([
|
||||||
|
'./vendors/ti-icons/css/themify-icons.css',
|
||||||
|
'./vendors/base/vendor.bundle.base.css',
|
||||||
|
'./vendors/base/vendor.bundle.base.js'
|
||||||
|
], {read: false}), {name: 'plugins', relative: true}))
|
||||||
|
.pipe(inject(gulp.src([
|
||||||
|
'./css/*.css',
|
||||||
|
'./js/off-canvas.js',
|
||||||
|
'./js/hoverable-collapse.js',
|
||||||
|
'./js/template.js',
|
||||||
|
'./js/todolist.js'
|
||||||
|
], {read: false}), {relative: true}))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
/* inject Js and CCS assets into HTML */
|
||||||
|
gulp.task('injectLayoutStyles', function () {
|
||||||
|
return gulp.src('./**/*.html')
|
||||||
|
.pipe(inject(gulp.src([
|
||||||
|
'./css/style.css',
|
||||||
|
], {read: false}), {relative: true}))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
/*replace image path and linking after injection*/
|
||||||
|
gulp.task('replacePath', function(){
|
||||||
|
var replacePath1 = gulp.src(['./pages/*/*.html'], { base: "./" })
|
||||||
|
.pipe(replace('="images/', '="../../images/'))
|
||||||
|
.pipe(replace('href="pages/', 'href="../../pages/'))
|
||||||
|
.pipe(replace('href="documentation/', 'href="../../documentation/'))
|
||||||
|
.pipe(replace('href="index.html"', 'href="../../index.html"'))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
var replacePath2 = gulp.src(['./pages/*.html'], { base: "./" })
|
||||||
|
.pipe(replace('="images/', '="../images/'))
|
||||||
|
.pipe(replace('"pages/', '"../pages/'))
|
||||||
|
.pipe(replace('href="index.html"', 'href="../index.html"'))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
var replacePath3 = gulp.src(['./index.html'], { base: "./" })
|
||||||
|
.pipe(replace('="images/', '="images/'))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
return merge(replacePath1, replacePath2, replacePath3);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*sequence for injecting partials and replacing paths*/
|
||||||
|
gulp.task('inject', gulp.series('injectPartial' , 'injectCommonAssets' , 'injectLayoutStyles', 'replacePath'));
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('clean:vendors', function () {
|
||||||
|
return del([
|
||||||
|
'vendors/**/*'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*Building vendor scripts needed for basic template rendering*/
|
||||||
|
gulp.task('buildBaseVendorScripts', function() {
|
||||||
|
return gulp.src([
|
||||||
|
'./node_modules/jquery/dist/jquery.min.js',
|
||||||
|
// './node_modules/popper.js/dist/umd/popper.min.js',
|
||||||
|
'./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js',
|
||||||
|
'./node_modules/perfect-scrollbar/dist/perfect-scrollbar.min.js'
|
||||||
|
])
|
||||||
|
.pipe(concat('vendor.bundle.base.js'))
|
||||||
|
.pipe(gulp.dest('./vendors/base'));
|
||||||
|
});
|
||||||
|
|
||||||
|
/*Building vendor styles needed for basic template rendering*/
|
||||||
|
gulp.task('buildBaseVendorStyles', function() {
|
||||||
|
return gulp.src(['./node_modules/perfect-scrollbar/css/perfect-scrollbar.css'])
|
||||||
|
.pipe(concat('vendor.bundle.base.css'))
|
||||||
|
.pipe(gulp.dest('./vendors/base'));
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Scripts for addons */
|
||||||
|
gulp.task('copyRecursiveVendorFiles', function () {
|
||||||
|
var chartJs = gulp.src(['./node_modules/chart.js/dist/Chart.min.js'])
|
||||||
|
.pipe(gulp.dest('./vendors/chart.js'));
|
||||||
|
var ti = gulp.src(['./node_modules/ti-icons/css/themify-icons.css'])
|
||||||
|
.pipe(gulp.dest('./vendors/ti-icons/css'));
|
||||||
|
var tiFonts = gulp.src(['./node_modules/ti-icons/fonts/*'])
|
||||||
|
.pipe(gulp.dest('./vendors/ti-icons/fonts'));
|
||||||
|
return merge(chartJs, ti, tiFonts);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Copy essential map files
|
||||||
|
gulp.task('copyMapFiles', function() {
|
||||||
|
var map1 = gulp.src('node_modules/bootstrap/dist/js/bootstrap.min.js.map')
|
||||||
|
.pipe(gulp.dest('./vendors/base'));
|
||||||
|
return merge(map1);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*sequence for building vendor scripts and styles*/
|
||||||
|
gulp.task('bundleVendors', gulp.series('clean:vendors', 'buildBaseVendorStyles','buildBaseVendorScripts','copyRecursiveVendorFiles','copyMapFiles'));
|
||||||
|
|
||||||
|
gulp.task('default', gulp.series('serve'));
|
BIN
static/admin/images/.DS_Store
vendored
Normal file
BIN
static/admin/images/auth/.DS_Store
vendored
Normal file
BIN
static/admin/images/auth/lockscreen-bg.jpg
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
static/admin/images/auth/login-bg.jpg
Executable file
After Width: | Height: | Size: 162 KiB |
BIN
static/admin/images/auth/register-bg.jpg
Executable file
After Width: | Height: | Size: 269 KiB |
3
static/admin/images/dashboard/shape-1.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="56" height="39" viewBox="0 0 56 39">
|
||||||
|
<path fill="#7EBBFF" fill-rule="nonzero" d="M6.18 19.87A15.62 15.62 0 0 1 6 17.5C6 8.94 12.94 2 21.5 2 30.06 2 37 8.94 37 17.5c0 1.582-.237 3.108-.677 4.545a5 5 0 1 1-4.047 6.596A15.449 15.449 0 0 1 21.5 33c-.867 0-1.717-.071-2.544-.208A7.001 7.001 0 1 1 6.17 28.123 4.995 4.995 0 0 1 4 24c0-1.715.864-3.229 2.18-4.13zM39 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM2 20a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm53 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM34 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM21 37a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM36.5 1a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 626 B |
3
static/admin/images/dashboard/shape-2.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="56" height="39" viewBox="0 0 56 39">
|
||||||
|
<path fill="#7EBBFF" fill-rule="nonzero" d="M49.82 19.13c.118.772.18 1.564.18 2.37C50 30.06 43.06 37 34.5 37 25.94 37 19 30.06 19 21.5c0-1.582.237-3.108.677-4.545a5 5 0 1 1 4.047-6.596A15.449 15.449 0 0 1 34.5 6c.867 0 1.717.071 2.544.208a7.001 7.001 0 1 1 12.785 4.669A4.995 4.995 0 0 1 52 15a4.995 4.995 0 0 1-2.18 4.13zM17 32a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm37-13a2 2 0 1 1 0 4 2 2 0 0 1 0-4zM1 15a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm21 20a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM35 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM19.5 38a.5.5 0 1 1 0 1 .5.5 0 0 1 0-1z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 633 B |
3
static/admin/images/dashboard/shape-3.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="51" viewBox="0 0 48 51">
|
||||||
|
<path fill="#7EBBFF" fill-rule="nonzero" d="M14.41 7.005a15.62 15.62 0 0 1 1.89-1.442c7.179-4.662 16.778-2.622 21.44 4.558 4.663 7.18 2.622 16.779-4.557 21.441a15.498 15.498 0 0 1-4.18 1.908 5 5 0 1 1-7.736.198 15.449 15.449 0 0 1-9.525-6.663 15.608 15.608 0 0 1-1.212-2.248 7.001 7.001 0 1 1-3.047-13.265 4.995 4.995 0 0 1 2.276-4.066 4.995 4.995 0 0 1 4.65-.42zm28.668 20.516a2 2 0 1 1 3.355-2.179 2 2 0 0 1-3.355 2.179zM12.024 3.57a2 2 0 1 1 3.355-2.18 2 2 0 0 1-3.355 2.18zm25.511 46.627a1 1 0 1 1 1.678-1.09 1 1 0 0 1-1.678 1.09zm5.336-28.505a1 1 0 1 1 1.678-1.089 1 1 0 0 1-1.678 1.09zm-34.756 7.07a1 1 0 1 1 1.677-1.089 1 1 0 0 1-1.677 1.09zm38.634-6.607a.5.5 0 1 1 .838-.545.5.5 0 0 1-.838.545z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 801 B |
3
static/admin/images/dashboard/shape-4.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="93" height="99" viewBox="0 0 93 99">
|
||||||
|
<path fill="#7EBBFF" fill-rule="nonzero" d="M27.66 13.596a30.44 30.44 0 0 1 3.684-2.81c13.991-9.086 32.699-5.11 41.785 8.882 9.086 13.991 5.11 32.7-8.882 41.785a30.203 30.203 0 0 1-8.148 3.718c.284.33.55.683.793 1.059 2.931 4.513 1.649 10.548-2.865 13.479-4.513 2.93-10.548 1.648-13.479-2.865-2.32-3.574-2-8.101.475-11.286A30.107 30.107 0 0 1 22.462 52.57a30.417 30.417 0 0 1-2.361-4.379c-6.127 3.065-13.71 1.102-17.53-4.78-4.103-6.318-2.307-14.767 4.012-18.87a13.578 13.578 0 0 1 7.579-2.202 9.734 9.734 0 0 1 4.435-7.924 9.734 9.734 0 0 1 9.063-.82zm55.872 39.981a3.898 3.898 0 1 1 6.537-4.245 3.898 3.898 0 0 1-6.537 4.245zM23.012 6.902a3.898 3.898 0 1 1 6.538-4.246 3.898 3.898 0 0 1-6.538 4.246zm49.717 90.87a1.949 1.949 0 1 1 3.269-2.123 1.949 1.949 0 0 1-3.269 2.123zm10.399-55.551a1.949 1.949 0 1 1 3.269-2.123 1.949 1.949 0 0 1-3.269 2.123zM15.393 56a1.949 1.949 0 1 1 3.27-2.123A1.949 1.949 0 0 1 15.392 56zm75.292-12.877a.974.974 0 1 1 1.634-1.062.974.974 0 0 1-1.634 1.062z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
static/admin/images/faces/.DS_Store
vendored
Normal file
BIN
static/admin/images/faces/face1.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
static/admin/images/faces/face10.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/admin/images/faces/face11.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
static/admin/images/faces/face12.jpg
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
static/admin/images/faces/face13.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
static/admin/images/faces/face14.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
static/admin/images/faces/face15.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
static/admin/images/faces/face16.jpg
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
static/admin/images/faces/face17.jpg
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
static/admin/images/faces/face18.jpg
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
static/admin/images/faces/face19.jpg
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
static/admin/images/faces/face2.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
static/admin/images/faces/face20.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/admin/images/faces/face21.jpg
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
static/admin/images/faces/face22.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/admin/images/faces/face23.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
static/admin/images/faces/face24.jpg
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
static/admin/images/faces/face25.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
static/admin/images/faces/face26.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
static/admin/images/faces/face27.jpg
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
static/admin/images/faces/face28.jpg
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
static/admin/images/faces/face3.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/admin/images/faces/face4.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/admin/images/faces/face5.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
static/admin/images/faces/face6.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/admin/images/faces/face7.jpg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
static/admin/images/faces/face8.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/admin/images/faces/face9.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
static/admin/images/favicon.ico
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
static/admin/images/favicon.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
13
static/admin/images/logo-mini.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>royal mini</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="royal-mini">
|
||||||
|
<path d="M2.40000004,0.0487804878 L16.1999999,0.0487804878 C21.0601057,0.0487804878 25,3.98867475 25,8.84878062 L25,22.6 C25,23.9254834 23.9254834,25 22.6,25 L8.80000013,25 C3.93989426,25 5.9519131e-16,21.0601057 0,16.1999999 L0,2.44878052 C-1.62324903e-16,1.1232971 1.07451662,0.0487804878 2.40000004,0.0487804878 Z" id="Rectangle" fill="#F5A622"></path>
|
||||||
|
<path d="M0,0.0487804878 L8.08025177,0.0487804878 C12.5254705,0.0487804878 16.1290323,3.65234226 16.1290323,8.09756098 L16.1290323,13.7463414 C16.1290323,15.0718248 15.0545156,16.1463415 13.7290322,16.1463415 L8.06451613,16.1463415 C3.61060686,16.1463415 2.32180341e-15,12.5357346 1.77635684e-15,8.08182533 L0,0.0487804878 Z" id="Rectangle" fill="#FDBC0A"></path>
|
||||||
|
<path d="M15.0129032,21.4778537 L11.0645161,14.6009756 L10.0580645,14.6009756 L10.0580645,21.4778537 L5.64516129,21.4778537 L5.64516129,3.26829268 L13.1806452,3.26829268 C14.6258137,3.26829268 15.8473068,3.5172658 16.8451613,4.01521951 C17.8430157,4.51317322 18.5913954,5.19570298 19.0903226,6.06282927 C19.5892498,6.92995556 19.8387097,7.9215554 19.8387097,9.03765854 C19.8387097,10.3598115 19.4731219,11.4844832 18.7419355,12.4117073 C18.010749,13.3389315 16.9483941,13.9742422 15.5548387,14.3176585 L19.8903226,21.4778537 L15.0129032,21.4778537 Z M10.0580645,11.5875122 L12.7935484,11.5875122 C13.6709721,11.5875122 14.316127,11.3943434 14.7290323,11.008 C15.1419375,10.6216566 15.3483871,10.0507355 15.3483871,9.29521951 C15.3483871,8.57404517 15.1376365,8.00741669 14.716129,7.59531707 C14.2946215,7.18321745 13.6537677,6.97717073 12.7935484,6.97717073 L10.0580645,6.97717073 L10.0580645,11.5875122 Z" id="R" fill="#FFFFFF"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |