added admin options
This commit is contained in:
@@ -105,6 +105,83 @@ func (q *Queries) GetBooking(ctx context.Context, id int64) (GetBookingRow, erro
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listAllBookingsWithDetails = `-- name: ListAllBookingsWithDetails :many
|
||||
SELECT
|
||||
b.id,
|
||||
b.user_id,
|
||||
b.service_id,
|
||||
b.event_date,
|
||||
b.address,
|
||||
b.notes,
|
||||
b.created_at,
|
||||
b.status,
|
||||
b.service_option,
|
||||
b.event_type,
|
||||
u.email as user_email,
|
||||
s.name as service_name,
|
||||
s.description as service_description,
|
||||
s.price_cents as service_price_cents
|
||||
FROM bookings b
|
||||
JOIN users u ON b.user_id = u.id
|
||||
JOIN services s ON b.service_id = s.id
|
||||
ORDER BY b.created_at DESC
|
||||
`
|
||||
|
||||
type ListAllBookingsWithDetailsRow struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID sql.NullInt64 `json:"user_id"`
|
||||
ServiceID sql.NullInt64 `json:"service_id"`
|
||||
EventDate time.Time `json:"event_date"`
|
||||
Address sql.NullString `json:"address"`
|
||||
Notes sql.NullString `json:"notes"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
Status sql.NullString `json:"status"`
|
||||
ServiceOption sql.NullString `json:"service_option"`
|
||||
EventType sql.NullString `json:"event_type"`
|
||||
UserEmail string `json:"user_email"`
|
||||
ServiceName string `json:"service_name"`
|
||||
ServiceDescription sql.NullString `json:"service_description"`
|
||||
ServicePriceCents int32 `json:"service_price_cents"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListAllBookingsWithDetails(ctx context.Context) ([]ListAllBookingsWithDetailsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listAllBookingsWithDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListAllBookingsWithDetailsRow{}
|
||||
for rows.Next() {
|
||||
var i ListAllBookingsWithDetailsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.ServiceID,
|
||||
&i.EventDate,
|
||||
&i.Address,
|
||||
&i.Notes,
|
||||
&i.CreatedAt,
|
||||
&i.Status,
|
||||
&i.ServiceOption,
|
||||
&i.EventType,
|
||||
&i.UserEmail,
|
||||
&i.ServiceName,
|
||||
&i.ServiceDescription,
|
||||
&i.ServicePriceCents,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listBookingsByUser = `-- name: ListBookingsByUser :many
|
||||
SELECT id, user_id, service_id, event_date, address, notes, created_at, status, service_option, event_type
|
||||
FROM bookings
|
||||
@@ -232,3 +309,19 @@ func (q *Queries) ListBookingsWithServiceByUser(ctx context.Context, userID sql.
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateBookingStatus = `-- name: UpdateBookingStatus :exec
|
||||
UPDATE bookings
|
||||
SET status = $2
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateBookingStatusParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Status sql.NullString `json:"status"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBookingStatus(ctx context.Context, arg UpdateBookingStatusParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateBookingStatus, arg.ID, arg.Status)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,4 +43,5 @@ type User struct {
|
||||
Email string `json:"email"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
OryIdentityID sql.NullString `json:"ory_identity_id"`
|
||||
IsAdmin sql.NullBool `json:"is_admin"`
|
||||
}
|
||||
|
||||
@@ -33,3 +33,29 @@ ORDER BY b.event_date DESC;
|
||||
SELECT id, user_id, service_id, event_date, address, notes, created_at, status, service_option, event_type
|
||||
FROM bookings
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ListAllBookingsWithDetails :many
|
||||
SELECT
|
||||
b.id,
|
||||
b.user_id,
|
||||
b.service_id,
|
||||
b.event_date,
|
||||
b.address,
|
||||
b.notes,
|
||||
b.created_at,
|
||||
b.status,
|
||||
b.service_option,
|
||||
b.event_type,
|
||||
u.email as user_email,
|
||||
s.name as service_name,
|
||||
s.description as service_description,
|
||||
s.price_cents as service_price_cents
|
||||
FROM bookings b
|
||||
JOIN users u ON b.user_id = u.id
|
||||
JOIN services s ON b.service_id = s.id
|
||||
ORDER BY b.created_at DESC;
|
||||
|
||||
-- name: UpdateBookingStatus :exec
|
||||
UPDATE bookings
|
||||
SET status = $2
|
||||
WHERE id = $1;
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
const createUser = `-- name: CreateUser :one
|
||||
INSERT INTO users (email, ory_identity_id)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, email, created_at, ory_identity_id
|
||||
RETURNING id, email, created_at, ory_identity_id, is_admin
|
||||
`
|
||||
|
||||
type CreateUserParams struct {
|
||||
@@ -29,12 +29,13 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
|
||||
&i.Email,
|
||||
&i.CreatedAt,
|
||||
&i.OryIdentityID,
|
||||
&i.IsAdmin,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByEmail = `-- name: GetUserByEmail :one
|
||||
SELECT id, email, created_at, ory_identity_id FROM users WHERE email = $1 LIMIT 1
|
||||
SELECT id, email, created_at, ory_identity_id, is_admin FROM users WHERE email = $1 LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
|
||||
@@ -45,12 +46,13 @@ func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error
|
||||
&i.Email,
|
||||
&i.CreatedAt,
|
||||
&i.OryIdentityID,
|
||||
&i.IsAdmin,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByOryID = `-- name: GetUserByOryID :one
|
||||
SELECT id, email, created_at, ory_identity_id FROM users WHERE ory_identity_id = $1 LIMIT 1
|
||||
SELECT id, email, created_at, ory_identity_id, is_admin FROM users WHERE ory_identity_id = $1 LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByOryID(ctx context.Context, oryIdentityID sql.NullString) (User, error) {
|
||||
@@ -61,6 +63,7 @@ func (q *Queries) GetUserByOryID(ctx context.Context, oryIdentityID sql.NullStri
|
||||
&i.Email,
|
||||
&i.CreatedAt,
|
||||
&i.OryIdentityID,
|
||||
&i.IsAdmin,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -69,7 +72,7 @@ const updateUserOryID = `-- name: UpdateUserOryID :one
|
||||
UPDATE users
|
||||
SET ory_identity_id = $1
|
||||
WHERE email = $2
|
||||
RETURNING id, email, created_at, ory_identity_id
|
||||
RETURNING id, email, created_at, ory_identity_id, is_admin
|
||||
`
|
||||
|
||||
type UpdateUserOryIDParams struct {
|
||||
@@ -85,6 +88,7 @@ func (q *Queries) UpdateUserOryID(ctx context.Context, arg UpdateUserOryIDParams
|
||||
&i.Email,
|
||||
&i.CreatedAt,
|
||||
&i.OryIdentityID,
|
||||
&i.IsAdmin,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
ory "github.com/ory/client-go"
|
||||
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
func CatalogHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
func CatalogHandler(q *db.Queries, tmpl *template.Template, oryClient *ory.APIClient) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
svcs, err := q.ListServices(r.Context())
|
||||
if err != nil {
|
||||
@@ -15,10 +17,17 @@ func CatalogHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
type data struct {
|
||||
Services []db.Service
|
||||
ActivePage string
|
||||
Services []db.Service
|
||||
ActivePage string
|
||||
Authenticated bool
|
||||
OryLoginURL string
|
||||
}
|
||||
if err := tmpl.ExecuteTemplate(w, "catalog.tmpl", data{Services: svcs, ActivePage: "catalog"}); err != nil {
|
||||
if err := tmpl.ExecuteTemplate(w, "catalog.tmpl", data{
|
||||
Services: svcs,
|
||||
ActivePage: "catalog",
|
||||
Authenticated: isAuthenticated(r, oryClient),
|
||||
OryLoginURL: "/login",
|
||||
}); err != nil {
|
||||
http.Error(w, "Failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
ory "github.com/ory/client-go"
|
||||
|
||||
"decor-by-hannahs/internal/db"
|
||||
)
|
||||
|
||||
@@ -15,7 +17,7 @@ type Photo struct {
|
||||
Caption string
|
||||
}
|
||||
|
||||
func GalleryHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
func GalleryHandler(q *db.Queries, tmpl *template.Template, oryClient *ory.APIClient) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var photos []Photo
|
||||
|
||||
@@ -40,10 +42,17 @@ func GalleryHandler(q *db.Queries, tmpl *template.Template) http.HandlerFunc {
|
||||
})
|
||||
|
||||
type data struct {
|
||||
Photos []Photo
|
||||
ActivePage string
|
||||
Photos []Photo
|
||||
ActivePage string
|
||||
Authenticated bool
|
||||
OryLoginURL string
|
||||
}
|
||||
if err := tmpl.ExecuteTemplate(w, "gallery.tmpl", data{Photos: photos, ActivePage: "gallery"}); err != nil {
|
||||
if err := tmpl.ExecuteTemplate(w, "gallery.tmpl", data{
|
||||
Photos: photos,
|
||||
ActivePage: "gallery",
|
||||
Authenticated: isAuthenticated(r, oryClient),
|
||||
OryLoginURL: "/login",
|
||||
}); err != nil {
|
||||
http.Error(w, "Failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
</div>
|
||||
</button>
|
||||
<a href="/" class="header-brand" style="position: relative;">
|
||||
<img src="/assets/balloon.png" alt="Decor By Hannah's">
|
||||
Decor By Hannah's
|
||||
<img src="/assets/balloon.png" alt="Decor By Hannahs">
|
||||
Decor By Hannahs
|
||||
<img src="/assets/party-icons/party-hat.png" alt="" style="position: absolute; top: -8px; right: -15px; width: 25px; height: 25px; transform: rotate(20deg);">
|
||||
</a>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
<footer style="position: relative;">
|
||||
<img src="/assets/party-icons/penguin-wearing-party-hat.png" alt="" style="position: absolute; bottom: 10px; left: 20px; width: 50px; height: 50px; opacity: 0.6;">
|
||||
<img src="/assets/party-icons/party-hat-with-horn.png" alt="" style="position: absolute; bottom: 10px; right: 20px; width: 50px; height: 50px; opacity: 0.6; transform: rotate(-20deg);">
|
||||
<small>© 2025 Decor By Hannah's. All rights reserved.</small>
|
||||
<small>© 2025 Decor By Hannahs. All rights reserved.</small>
|
||||
</footer>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="hero" style="position: relative;">
|
||||
<img src="/assets/party-icons/penguin-wearing-party-hat.png" alt="Party penguin" style="position: absolute; top: 20px; right: 10%; width: 100px; height: 100px; opacity: 0.8; animation: float 3s ease-in-out infinite;">
|
||||
<img src="/assets/party-icons/jester-hat.png" alt="Jester hat" style="position: absolute; bottom: 20px; left: 10%; width: 80px; height: 80px; opacity: 0.8; animation: float 4s ease-in-out infinite; animation-delay: 1s;">
|
||||
<h1>Welcome to Decor By Hannah's</h1>
|
||||
<h1>Welcome to Decor By Hannahs</h1>
|
||||
<p>We take care of all the headache for your party decorations so you don't have to</p>
|
||||
<div style="display: flex; gap: 1rem; justify-content: center; margin-top: 2rem; flex-wrap: wrap;">
|
||||
<a href="/catalog" class="btn btn-primary">Browse Services</a>
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
</div>
|
||||
</button>
|
||||
<a href="/" class="header-brand">
|
||||
<img src="/assets/balloon.png" alt="Decor By Hannah's">
|
||||
Decor By Hannah's
|
||||
<img src="/assets/balloon.png" alt="Decor By Hannahs">
|
||||
Decor By Hannahs
|
||||
</a>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||
@@ -85,7 +85,7 @@
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<small>© 2025 Decor By Hannah's. All rights reserved.</small>
|
||||
<small>© 2025 Decor By Hannahs. All rights reserved.</small>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Redirecting - Decor By Hannah's</title>
|
||||
<title>Redirecting - Decor By Hannahs</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css">
|
||||
<script src="/static/htmx/htmx.min.js"></script>
|
||||
<link rel="icon" href="/assets/balloon.png" type="image/png">
|
||||
|
||||
Reference in New Issue
Block a user