the rest
This commit is contained in:
76
internal/handlers/booking.go
Normal file
76
internal/handlers/booking.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"decor-by-hannahs/internal/auth"
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
func BookingPageHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type data struct {
|
||||
Title string
|
||||
ActivePage string
|
||||
}
|
||||
_ = tmpl.ExecuteTemplate(w, "booking.tmpl", data{Title: "Book a Service", ActivePage: "booking"})
|
||||
}
|
||||
}
|
||||
|
||||
func BookingAPIHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method is not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := auth.GetUser(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var in struct {
|
||||
ServiceID string `json:"service_id"`
|
||||
EventDate string `json:"event_date"`
|
||||
Address string `json:"address"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
eventTime, err := time.Parse(time.RFC3339, in.EventDate)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid date", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
svc, err := strconv.ParseInt(in.ServiceID, 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid Service ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
svcID := sql.NullInt64{Int64: svc, Valid: true}
|
||||
userID := sql.NullInt64{Int64: user.ID, Valid: true}
|
||||
|
||||
booking, err := q.CreateBooking(r.Context(), db.CreateBookingParams{
|
||||
UserID: userID,
|
||||
ServiceID: svcID,
|
||||
EventDate: eventTime,
|
||||
Address: sqlNullString(in.Address),
|
||||
Notes: sqlNullString(in.Notes),
|
||||
Status: sql.NullString{String: "pending", Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create booking", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, booking)
|
||||
}
|
||||
}
|
||||
25
internal/handlers/catalog.go
Normal file
25
internal/handlers/catalog.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
func CatalogHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
svcs, err := q.ListServices(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, "unable to load services", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
type data struct {
|
||||
Services []db.Service
|
||||
ActivePage string
|
||||
}
|
||||
if err := tmpl.ExecuteTemplate(w, "catalog.tmpl", data{Services: svcs, ActivePage: "catalog"}); err != nil {
|
||||
http.Error(w, "Failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
50
internal/handlers/gallery.go
Normal file
50
internal/handlers/gallery.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
type Photo struct {
|
||||
URL string
|
||||
Caption string
|
||||
}
|
||||
|
||||
func GalleryHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var photos []Photo
|
||||
|
||||
galleryDir := "assets/gallery-photos"
|
||||
entries, err := os.ReadDir(galleryDir)
|
||||
if err == nil {
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
ext := filepath.Ext(entry.Name())
|
||||
if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" {
|
||||
photos = append(photos, Photo{
|
||||
URL: "/assets/gallery-photos/" + entry.Name(),
|
||||
Caption: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(photos, func(i, j int) bool {
|
||||
return photos[i].URL < photos[j].URL
|
||||
})
|
||||
|
||||
type data struct {
|
||||
Photos []Photo
|
||||
ActivePage string
|
||||
}
|
||||
if err := tmpl.ExecuteTemplate(w, "gallery.tmpl", data{Photos: photos, ActivePage: "gallery"}); err != nil {
|
||||
http.Error(w, "Failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
internal/handlers/helpers.go
Normal file
26
internal/handlers/helpers.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func sqlNullString(s string) sql.NullString {
|
||||
if s == "" {
|
||||
return sql.NullString{}
|
||||
}
|
||||
return sql.NullString{String: s, Valid: true}
|
||||
}
|
||||
|
||||
func sqlNullUUID(s string) sql.NullString {
|
||||
if s == "" {
|
||||
return sql.NullString{}
|
||||
}
|
||||
return sql.NullString{String: s, Valid: true}
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
54
internal/handlers/home.go
Normal file
54
internal/handlers/home.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
func HomeHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("HomeHandler called: method=%s path=%s", r.Method, r.URL.Path)
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var photos []Photo
|
||||
|
||||
carouselDir := "assets/carousel-photos"
|
||||
entries, err := os.ReadDir(carouselDir)
|
||||
if err == nil {
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
ext := filepath.Ext(entry.Name())
|
||||
if ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" {
|
||||
photos = append(photos, Photo{
|
||||
URL: "/assets/carousel-photos/" + entry.Name(),
|
||||
Caption: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(photos, func(i, j int) bool {
|
||||
return photos[i].URL < photos[j].URL
|
||||
})
|
||||
|
||||
type data struct {
|
||||
Title string
|
||||
Photos []Photo
|
||||
ActivePage string
|
||||
}
|
||||
if err := tmpl.ExecuteTemplate(w, "home.tmpl", data{Title: "Home", Photos: photos, ActivePage: "home"}); err != nil {
|
||||
log.Printf("Error executing home.tmpl: %v", err)
|
||||
http.Error(w, "Failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
59
internal/handlers/ory_redirect.go
Normal file
59
internal/handlers/ory_redirect.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type OryRedirectData struct {
|
||||
Title string
|
||||
ActivePage string
|
||||
Flow string
|
||||
Message string
|
||||
Type string
|
||||
}
|
||||
|
||||
func OryRedirectHandler(tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
flow := r.URL.Query().Get("flow")
|
||||
|
||||
var message string
|
||||
var msgType string
|
||||
|
||||
switch flow {
|
||||
case "login":
|
||||
message = "Successfully logged in!"
|
||||
msgType = "success"
|
||||
case "registration":
|
||||
message = "Account created successfully!"
|
||||
msgType = "success"
|
||||
case "settings":
|
||||
message = "Settings updated successfully!"
|
||||
msgType = "success"
|
||||
case "verification":
|
||||
message = "Email verified successfully!"
|
||||
msgType = "success"
|
||||
case "recovery":
|
||||
message = "Password recovered successfully!"
|
||||
msgType = "success"
|
||||
case "logout":
|
||||
message = "Successfully logged out!"
|
||||
msgType = "info"
|
||||
default:
|
||||
message = "Action completed successfully!"
|
||||
msgType = "success"
|
||||
}
|
||||
|
||||
data := OryRedirectData{
|
||||
Title: "Redirect",
|
||||
ActivePage: "",
|
||||
Flow: flow,
|
||||
Message: message,
|
||||
Type: msgType,
|
||||
}
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "ory_redirect.tmpl", data); err != nil {
|
||||
http.Error(w, "Failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
82
internal/handlers/profile.go
Normal file
82
internal/handlers/profile.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"decor-by-hannahs/internal/auth"
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
type BookingWithService struct {
|
||||
ID int64
|
||||
ServiceName string
|
||||
ServiceDescription string
|
||||
ServicePriceCents int32
|
||||
EventDate string
|
||||
Address string
|
||||
Notes string
|
||||
Status string
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
type ProfileData struct {
|
||||
Authenticated bool
|
||||
Email string
|
||||
DisplayName string
|
||||
ID string
|
||||
ActivePage string
|
||||
Bookings []BookingWithService
|
||||
OrySettingsURL string
|
||||
}
|
||||
|
||||
func ProfileHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
pd := ProfileData{ActivePage: "profile"}
|
||||
|
||||
user, err := auth.GetUser(r.Context())
|
||||
if err == nil {
|
||||
pd.Authenticated = true
|
||||
pd.Email = user.Email
|
||||
pd.ID = strconv.FormatInt(user.ID, 10)
|
||||
pd.DisplayName = user.Email
|
||||
|
||||
bookings, err := q.ListBookingsWithServiceByUser(r.Context(), user.ID)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching bookings: %v", err)
|
||||
} else {
|
||||
for _, b := range bookings {
|
||||
pd.Bookings = append(pd.Bookings, BookingWithService{
|
||||
ID: b.ID,
|
||||
ServiceName: b.ServiceName,
|
||||
ServiceDescription: nullStringToString(b.ServiceDescription),
|
||||
ServicePriceCents: b.ServicePriceCents,
|
||||
EventDate: b.EventDate.Format("January 2, 2006"),
|
||||
Address: nullStringToString(b.Address),
|
||||
Notes: nullStringToString(b.Notes),
|
||||
Status: b.Status,
|
||||
CreatedAt: b.CreatedAt.Time.Format("Jan 2, 2006"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
oryAPIURL := os.Getenv("ORY_API_URL")
|
||||
if oryAPIURL == "" {
|
||||
tunnelPort := os.Getenv("TUNNEL_PORT")
|
||||
if tunnelPort == "" {
|
||||
tunnelPort = "4000"
|
||||
}
|
||||
oryAPIURL = "http://localhost:" + tunnelPort
|
||||
}
|
||||
pd.OrySettingsURL = oryAPIURL + "/ui/settings"
|
||||
}
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, "profile.tmpl", pd); err != nil {
|
||||
http.Error(w, "failed to render profile", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user