feat: add WannPassts booking app with Go backend and Vue frontend
Initial project setup for a privacy-focused, self-hosted scheduling app: - Go backend with JWT auth, SQLite storage, and chi router - Calendar integrations via Google OAuth (FreeBusy), CalDAV, and ICS links - Public booking pages with configurable slots and booking requests - AES-256-GCM encryption for stored provider tokens - Vue 3 + TypeScript frontend with Vite, Pinia, and Vue Router - Docs, .gitignore, and .env.example for local development
This commit is contained in:
commit
cb30223fd4
47 changed files with 6599 additions and 0 deletions
93
backend/internal/httpapi/bookings.go
Normal file
93
backend/internal/httpapi/bookings.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) ListBookings(w http.ResponseWriter, r *http.Request) {
|
||||
bookings, err := s.DB.BookingsForUser(userFrom(r).ID)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Anfragen nicht lesbar")
|
||||
return
|
||||
}
|
||||
out := make([]BookingJSON, 0, len(bookings))
|
||||
for _, b := range bookings {
|
||||
out = append(out, bookingToJSON(b))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (s *Server) AcceptBooking(w http.ResponseWriter, r *http.Request) {
|
||||
user := userFrom(r)
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "Ungültige ID")
|
||||
return
|
||||
}
|
||||
booking, err := s.DB.BookingByID(id, user.ID)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "Anfrage nicht gefunden")
|
||||
return
|
||||
}
|
||||
if booking.Status != "pending" {
|
||||
jsonError(w, http.StatusConflict, "Diese Anfrage wurde bereits bearbeitet")
|
||||
return
|
||||
}
|
||||
// Kollision mit bestehenden busy-Zeiträumen prüfen (angommene Buchungen
|
||||
// und Kalendertermine liegen beide in busy_slots).
|
||||
busy, err := s.DB.BusyForUser(user.ID, booking.StartTs-1, booking.EndTs+1)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Belegung nicht prüfbar")
|
||||
return
|
||||
}
|
||||
for _, b := range busy {
|
||||
if b.BookingID.Valid && b.BookingID.Int64 == booking.ID {
|
||||
continue
|
||||
}
|
||||
if b.StartTs < booking.EndTs && booking.StartTs < b.EndTs {
|
||||
jsonError(w, http.StatusConflict, "Der Zeitraum kollidiert inzwischen mit einem anderen Termin")
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := s.DB.InsertBusyForBooking(booking); err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Termin konnte nicht eingetragen werden")
|
||||
return
|
||||
}
|
||||
if err := s.DB.SetBookingStatus(booking.ID, "accepted"); err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Status konnte nicht gespeichert werden")
|
||||
return
|
||||
}
|
||||
booking.Status = "accepted"
|
||||
writeJSON(w, http.StatusOK, bookingToJSON(booking))
|
||||
}
|
||||
|
||||
func (s *Server) DeclineBooking(w http.ResponseWriter, r *http.Request) {
|
||||
user := userFrom(r)
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "Ungültige ID")
|
||||
return
|
||||
}
|
||||
booking, err := s.DB.BookingByID(id, user.ID)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "Anfrage nicht gefunden")
|
||||
return
|
||||
}
|
||||
if booking.Status == "pending" {
|
||||
if err := s.DB.SetBookingStatus(booking.ID, "declined"); err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Status konnte nicht gespeichert werden")
|
||||
return
|
||||
}
|
||||
} else if booking.Status == "accepted" {
|
||||
if err := s.DB.DeleteBusyByBooking(booking.ID); err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Termin konnte nicht entfernt werden")
|
||||
return
|
||||
}
|
||||
if err := s.DB.SetBookingStatus(booking.ID, "declined"); err != nil {
|
||||
jsonError(w, http.StatusInternalServerError, "Status konnte nicht gespeichert werden")
|
||||
return
|
||||
}
|
||||
}
|
||||
booking.Status = "declined"
|
||||
writeJSON(w, http.StatusOK, bookingToJSON(booking))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue