Compare commits
17 Commits
5987fadb03
...
sqlc
Author | SHA1 | Date | |
---|---|---|---|
dd49a1e687 | |||
459a4e5c7d | |||
cec183b416 | |||
f20f256313 | |||
30c552cf6c | |||
3eab2a3c9f | |||
5d225f0186 | |||
52c3679158 | |||
aca8605870 | |||
6631288843 | |||
99c2eec759 | |||
623291541a | |||
d7f7a2d8d9 | |||
14828fb901 | |||
7894a05daf | |||
b97c6cba5c | |||
a7cf9fe4f0 |
@ -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
|
||||||
|
@ -38,16 +38,19 @@ func routes(app *config.AppConfig) http.Handler {
|
|||||||
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
||||||
|
|
||||||
mux.Route("/admin", func(mux chi.Router) {
|
mux.Route("/admin", func(mux chi.Router) {
|
||||||
// mux.Use(Auth)
|
if app.InProduction {
|
||||||
|
mux.Use(Auth)
|
||||||
|
}
|
||||||
mux.Get("/dashboard", handlers.Repo.AdminDashboard)
|
mux.Get("/dashboard", handlers.Repo.AdminDashboard)
|
||||||
|
|
||||||
mux.Get("/reservations-new", handlers.Repo.AdminNewReservations)
|
mux.Get("/reservations-new", handlers.Repo.AdminNewReservations)
|
||||||
mux.Get("/reservations-all", handlers.Repo.AdminAllReservations)
|
mux.Get("/reservations-all", handlers.Repo.AdminAllReservations)
|
||||||
mux.Get("/reservations-calendar", handlers.Repo.AdminReservationsCalendar)
|
mux.Get("/reservations-calendar", handlers.Repo.AdminReservationsCalendar)
|
||||||
mux.Get("/process-reservation/{src}/{id}", handlers.Repo.AdminProcessReservation)
|
mux.Post("/reservations-calendar", handlers.Repo.AdminPostReservationsCalendar)
|
||||||
mux.Get("/delete-reservation/{src}/{id}", handlers.Repo.AdminDeleteReservation)
|
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}", handlers.Repo.AdminShowReservation)
|
mux.Get("/reservations/{src}/{id}/show", handlers.Repo.AdminShowReservation)
|
||||||
mux.Post("/reservations/{src}/{id}", handlers.Repo.AdminPostShowReservation)
|
mux.Post("/reservations/{src}/{id}", handlers.Repo.AdminPostShowReservation)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
32
docker/docker-compose-example.yml
Normal file
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
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
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,7 +4,6 @@ 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/helpers"
|
||||||
"go-udemy-web-1/internal/models"
|
"go-udemy-web-1/internal/models"
|
||||||
@ -30,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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,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
|
||||||
}
|
}
|
||||||
@ -155,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,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")
|
||||||
@ -254,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")
|
||||||
@ -264,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,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
|
||||||
@ -402,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")
|
||||||
@ -410,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,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
|
||||||
@ -501,7 +505,6 @@ func (m *Repository) PostShowLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Logout logs a user out
|
// Logout logs a user out
|
||||||
func (m *Repository) Logout(w http.ResponseWriter, r *http.Request) {
|
func (m *Repository) Logout(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO Use Redis to store the session. Check the documentation of scs package
|
|
||||||
m.App.Session.Destroy(r.Context())
|
m.App.Session.Destroy(r.Context())
|
||||||
m.App.Session.RenewToken(r.Context())
|
m.App.Session.RenewToken(r.Context())
|
||||||
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
|
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
|
||||||
@ -554,6 +557,12 @@ func (m *Repository) AdminShowReservation(w http.ResponseWriter, r *http.Request
|
|||||||
stringMap := make(map[string]string)
|
stringMap := make(map[string]string)
|
||||||
stringMap["src"] = src
|
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
|
// get reservation from the database
|
||||||
res, err := m.DB.GetReservationByID(id)
|
res, err := m.DB.GetReservationByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -610,13 +619,106 @@ func (m *Repository) AdminPostShowReservation(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
month := r.Form.Get("month")
|
||||||
|
year := r.Form.Get("year")
|
||||||
|
|
||||||
m.App.Session.Put(r.Context(), "flash", "Changes saved")
|
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)
|
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
|
// AdminReservationsCalendar displays the reservation calendar
|
||||||
func (m *Repository) AdminReservationsCalendar(w http.ResponseWriter, r *http.Request) {
|
func (m *Repository) AdminReservationsCalendar(w http.ResponseWriter, r *http.Request) {
|
||||||
render.Template(w, r, "admin-reservations-calendar.page.tmpl", &models.TemplateData{})
|
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
|
// AdminProcessReservation marks a reservation as processed
|
||||||
@ -626,9 +728,16 @@ func (m *Repository) AdminProcessReservation(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
_ = m.DB.UpdateProcessedForReservation(id, 1)
|
_ = 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")
|
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)
|
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
|
// AdminDeleteReservation deletes a reservation
|
||||||
@ -638,7 +747,74 @@ func (m *Repository) AdminDeleteReservation(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
_ = m.DB.DeleteReservation(id)
|
_ = 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))
|
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)
|
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))
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ import (
|
|||||||
|
|
||||||
var functions = template.FuncMap{
|
var functions = template.FuncMap{
|
||||||
"humanDate": HumanDate,
|
"humanDate": HumanDate,
|
||||||
|
"formatDate": FormatDate,
|
||||||
|
"iterate": Iterate,
|
||||||
|
"add": Add,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -34,6 +37,24 @@ func HumanDate(t time.Time) string {
|
|||||||
return t.Format("2006-01-02")
|
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")
|
||||||
|
@ -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
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"
|
||||||
@ -386,3 +387,108 @@ func (m *postgresDBRepo) UpdateProcessedForReservation(id, processed int) error
|
|||||||
}
|
}
|
||||||
return nil
|
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,8 +79,11 @@ 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) {
|
||||||
|
if email == "a@b.c" {
|
||||||
return 1, "", nil
|
return 1, "", nil
|
||||||
}
|
}
|
||||||
|
return 0, "", errors.New("deliberate errors")
|
||||||
|
}
|
||||||
|
|
||||||
// AllReservations returns a slice of all reservations
|
// AllReservations returns a slice of all reservations
|
||||||
func (m *testDBRepo) AllReservations() ([]models.Reservation, error) {
|
func (m *testDBRepo) AllReservations() ([]models.Reservation, error) {
|
||||||
@ -116,3 +119,25 @@ func (m *testDBRepo) DeleteReservation(id int) error {
|
|||||||
func (m *testDBRepo) UpdateProcessedForReservation(id, processed int) error {
|
func (m *testDBRepo) UpdateProcessedForReservation(id, processed int) error {
|
||||||
return nil
|
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
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
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 {
|
||||||
@ -22,4 +25,18 @@ type DatabaseRepo interface {
|
|||||||
UpdateReservation(r models.Reservation) error
|
UpdateReservation(r models.Reservation) error
|
||||||
DeleteReservation(id int) error
|
DeleteReservation(id int) error
|
||||||
UpdateProcessedForReservation(id, processed 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
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
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
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
|
||||||
|
}
|
2
run.sh
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
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
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
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"
|
@ -26,7 +26,7 @@ All Reservations
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ .ID}}</td>
|
<td>{{ .ID}}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/admin/reservations/all/{{.ID}}">
|
<a href="/admin/reservations/all/{{.ID}}/show">
|
||||||
{{ .LastName}}
|
{{ .LastName}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -26,7 +26,7 @@ New Reservations
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ .ID}}</td>
|
<td>{{ .ID}}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/admin/reservations/new/{{.ID}}">
|
<a href="/admin/reservations/new/{{.ID}}/show">
|
||||||
{{ .LastName}}
|
{{ .LastName}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -5,7 +5,73 @@ Reservations Calendar
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
|
{{$now := index .Data "now"}}
|
||||||
|
{{$rooms := index .Data "rooms"}}
|
||||||
|
{{$dim := index .IntMap "days_in_month"}}
|
||||||
|
{{$curMonth := index .StringMap "this_month"}}
|
||||||
|
{{$curYear := index .StringMap "this_month_year"}}
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
Reservations calendar content
|
<div class="text-center">
|
||||||
|
<h3>{{formatDate $now "January"}} {{formatDate $now "2006"}}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float-start">
|
||||||
|
<a class="btn btn-sm btn-outline-secondary"
|
||||||
|
href="/admin/reservations-calendar?y={{index .StringMap "last_month_year"}}&m={{index .StringMap "last_month"}}"><<</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float-end">
|
||||||
|
<a class="btn btn-sm btn-outline-secondary"
|
||||||
|
href="/admin/reservations-calendar?y={{index .StringMap "next_month_year"}}&m={{index .StringMap "next_month"}}">>></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<form method="post" action="/admin/reservations-calendar">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
|
<input type="hidden" name="m" value="{{index .StringMap "this_month"}}">
|
||||||
|
<input type="hidden" name="y" value="{{index .StringMap "this_month_year"}}">
|
||||||
|
|
||||||
|
{{range $rooms}}
|
||||||
|
{{$roomID := .ID}}
|
||||||
|
{{$blocks := index $.Data (printf "block_map_%d" .ID)}}
|
||||||
|
{{$reservations := index $.Data (printf "reservation_map_%d" .ID)}}
|
||||||
|
<h4 class="mt-4">{{.RoomName}}</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-sm">
|
||||||
|
<tr class="table-dark">
|
||||||
|
{{range $index := iterate $dim}}
|
||||||
|
<td class="text-center">
|
||||||
|
{{add $index 1}}
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
<tr class="table-light">
|
||||||
|
{{range $index := iterate $dim}}
|
||||||
|
<td class="text-center">
|
||||||
|
{{if gt (index $reservations (printf "%s-%s-%d" $curYear $curMonth (add $index 1))) 0}}
|
||||||
|
<a href="/admin/reservations/cal/{{index $reservations (printf "%s-%s-%d" $curYear $curMonth (add $index 1))}}/show?y={{$curYear}}&m={{$curMonth}}">
|
||||||
|
<span class="text-danger">R</span>
|
||||||
|
</a>
|
||||||
|
{{else}}
|
||||||
|
<input
|
||||||
|
{{if gt (index $blocks (printf "%s-%s-%d" $curYear $curMonth (add $index 1))) 0}}
|
||||||
|
checked
|
||||||
|
name="remove_block_{{$roomID}}_{{printf "%s-%s-%d" $curYear $curMonth (add $index 1)}}"
|
||||||
|
value="{{index $blocks (printf "%s-%s-%d" $curYear $curMonth (add $index 1))}}"
|
||||||
|
{{else}}
|
||||||
|
name="add_block_{{$roomID}}_{{printf "%s-%s-%d" $curYear $curMonth (add $index 1)}}"
|
||||||
|
value="1"
|
||||||
|
{{end}}
|
||||||
|
type="checkbox">
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<hr>
|
||||||
|
<input type="submit" class="btn btn-primary" value="Save Changes">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -15,6 +15,8 @@ Reservation
|
|||||||
</p>
|
</p>
|
||||||
<form method="post" action="/admin/reservations/{{$src}}/{{$res.ID}}" class="" novalidate>
|
<form method="post" action="/admin/reservations/{{$src}}/{{$res.ID}}" class="" novalidate>
|
||||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
|
<input type="hidden" name="year" value="{{index .StringMap "year"}}">
|
||||||
|
<input type="hidden" name="month" value="{{index .StringMap "month"}}">
|
||||||
|
|
||||||
<div class="form-group mt-5">
|
<div class="form-group mt-5">
|
||||||
<label for="first_name">First name:</label>
|
<label for="first_name">First name:</label>
|
||||||
@ -50,8 +52,14 @@ Reservation
|
|||||||
|
|
||||||
<div class="float-start">
|
<div class="float-start">
|
||||||
<input type="submit" class="btn btn-primary" value="Save">
|
<input type="submit" class="btn btn-primary" value="Save">
|
||||||
|
{{if eq $src "cal"}}
|
||||||
|
<a href="#" onclick="window.history.go(-1)" class="btn btn-warning">Cancel</a>
|
||||||
|
{{else}}
|
||||||
<a href="/admin/reservations-{{$src}}" class="btn btn-warning">Cancel</a>
|
<a href="/admin/reservations-{{$src}}" class="btn btn-warning">Cancel</a>
|
||||||
|
{{end}}
|
||||||
|
{{ if eq $res.Processed 0}}
|
||||||
<a href="#" class="btn btn-info" onclick="processRes({{$res.ID}})">Mark as Processed</a>
|
<a href="#" class="btn btn-info" onclick="processRes({{$res.ID}})">Mark as Processed</a>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -74,7 +82,7 @@ function processRes(id) {
|
|||||||
msg: 'Are you sure?',
|
msg: 'Are you sure?',
|
||||||
callback: function(result) {
|
callback: function(result) {
|
||||||
if (result != false) {
|
if (result != false) {
|
||||||
window.location.href = "/admin/process-reservation/{{$src}}/" + id;
|
window.location.href = "/admin/process-reservation/{{$src}}/" + id + "/do?y={{index .StringMap "year"}}&m={{index .StringMap "month"}}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -85,7 +93,7 @@ function deleteRes(id) {
|
|||||||
msg: 'Are you sure?',
|
msg: 'Are you sure?',
|
||||||
callback: function(result) {
|
callback: function(result) {
|
||||||
if (result != false) {
|
if (result != false) {
|
||||||
window.location.href = "/admin/delete-reservation/{{$src}}/" + id;
|
window.location.href = "/admin/delete-reservation/{{$src}}/" + id + "/do?y={{index .StringMap "year"}}&m={{index .StringMap "month"}}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -62,7 +62,8 @@
|
|||||||
<!-- partial -->
|
<!-- partial -->
|
||||||
<div class="container-fluid page-body-wrapper">
|
<div class="container-fluid page-body-wrapper">
|
||||||
<!-- partial:partials/_sidebar.html -->
|
<!-- partial:partials/_sidebar.html -->
|
||||||
<nav class="sidebar sidebar-offcanvas" id="sidebar">
|
<!-- TODO sidebar-offcanvas need to be repaired -->
|
||||||
|
<nav class="sidebar" id="sidebar">
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/admin/dashboard">
|
<a class="nav-link" href="/admin/dashboard">
|
||||||
|
Reference in New Issue
Block a user