first commit
Made-with: Cursor
This commit is contained in:
68
internal/api/handlers/apikeys.go
Normal file
68
internal/api/handlers/apikeys.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type APIKeyHandler struct {
|
||||
apiKeySvc *service.APIKeyService
|
||||
}
|
||||
|
||||
func NewAPIKeyHandler(apiKeySvc *service.APIKeyService) *APIKeyHandler {
|
||||
return &APIKeyHandler{apiKeySvc: apiKeySvc}
|
||||
}
|
||||
|
||||
func (h *APIKeyHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Scopes map[string][]string `json:"scopes"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := h.apiKeySvc.Create(r.Context(), userID, req.Name, req.Scopes)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, key)
|
||||
}
|
||||
|
||||
func (h *APIKeyHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
keys, err := h.apiKeySvc.List(r.Context(), userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteList(w, keys, models.PageInfo{Limit: utils.DefaultLimit})
|
||||
}
|
||||
|
||||
func (h *APIKeyHandler) Revoke(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
keyID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.apiKeySvc.Revoke(r.Context(), userID, keyID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
106
internal/api/handlers/attendees.go
Normal file
106
internal/api/handlers/attendees.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type AttendeeHandler struct {
|
||||
attendeeSvc *service.AttendeeService
|
||||
}
|
||||
|
||||
func NewAttendeeHandler(attendeeSvc *service.AttendeeService) *AttendeeHandler {
|
||||
return &AttendeeHandler{attendeeSvc: attendeeSvc}
|
||||
}
|
||||
|
||||
func (h *AttendeeHandler) Add(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Attendees []struct {
|
||||
UserID *uuid.UUID `json:"user_id"`
|
||||
Email *string `json:"email"`
|
||||
} `json:"attendees"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var addReqs []service.AddAttendeeRequest
|
||||
for _, a := range req.Attendees {
|
||||
addReqs = append(addReqs, service.AddAttendeeRequest{
|
||||
UserID: a.UserID,
|
||||
Email: a.Email,
|
||||
})
|
||||
}
|
||||
|
||||
event, err := h.attendeeSvc.AddAttendees(r.Context(), userID, eventID, addReqs)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"event": event})
|
||||
}
|
||||
|
||||
func (h *AttendeeHandler) UpdateStatus(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
attendeeID, err := utils.ValidateUUID(chi.URLParam(r, "attendeeID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.attendeeSvc.UpdateStatus(r.Context(), userID, eventID, attendeeID, req.Status)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"event": event})
|
||||
}
|
||||
|
||||
func (h *AttendeeHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
attendeeID, err := utils.ValidateUUID(chi.URLParam(r, "attendeeID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.attendeeSvc.DeleteAttendee(r.Context(), userID, eventID, attendeeID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
109
internal/api/handlers/auth.go
Normal file
109
internal/api/handlers/auth.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authSvc *service.AuthService
|
||||
userSvc *service.UserService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authSvc *service.AuthService, userSvc *service.UserService) *AuthHandler {
|
||||
return &AuthHandler{authSvc: authSvc, userSvc: userSvc}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authSvc.Register(r.Context(), req.Email, req.Password, req.Timezone)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authSvc.Login(r.Context(), req.Email, req.Password)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.authSvc.Refresh(r.Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authSvc.Logout(r.Context(), req.RefreshToken); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserID(r.Context())
|
||||
if !ok {
|
||||
utils.WriteError(w, models.ErrAuthRequired)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetMe(r.Context(), userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"user": user})
|
||||
}
|
||||
60
internal/api/handlers/availability.go
Normal file
60
internal/api/handlers/availability.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
)
|
||||
|
||||
type AvailabilityHandler struct {
|
||||
availSvc *service.AvailabilityService
|
||||
}
|
||||
|
||||
func NewAvailabilityHandler(availSvc *service.AvailabilityService) *AvailabilityHandler {
|
||||
return &AvailabilityHandler{availSvc: availSvc}
|
||||
}
|
||||
|
||||
func (h *AvailabilityHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
q := r.URL.Query()
|
||||
|
||||
calIDStr := q.Get("calendar_id")
|
||||
if calIDStr == "" {
|
||||
utils.WriteError(w, models.NewValidationError("calendar_id required"))
|
||||
return
|
||||
}
|
||||
calID, err := utils.ValidateUUID(calIDStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
startStr := q.Get("start")
|
||||
endStr := q.Get("end")
|
||||
if startStr == "" || endStr == "" {
|
||||
utils.WriteError(w, models.NewValidationError("start and end required"))
|
||||
return
|
||||
}
|
||||
start, err := time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid start time"))
|
||||
return
|
||||
}
|
||||
end, err := time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid end time"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.availSvc.GetBusyBlocks(r.Context(), userID, calID, start, end)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, result)
|
||||
}
|
||||
98
internal/api/handlers/booking.go
Normal file
98
internal/api/handlers/booking.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type BookingHandler struct {
|
||||
bookingSvc *service.BookingService
|
||||
}
|
||||
|
||||
func NewBookingHandler(bookingSvc *service.BookingService) *BookingHandler {
|
||||
return &BookingHandler{bookingSvc: bookingSvc}
|
||||
}
|
||||
|
||||
func (h *BookingHandler) CreateLink(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.BookingConfig
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
link, err := h.bookingSvc.CreateLink(r.Context(), userID, calID, req)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, link)
|
||||
}
|
||||
|
||||
func (h *BookingHandler) GetAvailability(w http.ResponseWriter, r *http.Request) {
|
||||
token := chi.URLParam(r, "token")
|
||||
q := r.URL.Query()
|
||||
|
||||
startStr := q.Get("start")
|
||||
endStr := q.Get("end")
|
||||
if startStr == "" || endStr == "" {
|
||||
utils.WriteError(w, models.NewValidationError("start and end required"))
|
||||
return
|
||||
}
|
||||
|
||||
start, err := time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid start time"))
|
||||
return
|
||||
}
|
||||
end, err := time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid end time"))
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.bookingSvc.GetAvailability(r.Context(), token, start, end)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *BookingHandler) Reserve(w http.ResponseWriter, r *http.Request) {
|
||||
token := chi.URLParam(r, "token")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
SlotStart time.Time `json:"slot_start"`
|
||||
SlotEnd time.Time `json:"slot_end"`
|
||||
Notes *string `json:"notes"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.bookingSvc.Reserve(r.Context(), token, req.Name, req.Email, req.SlotStart, req.SlotEnd, req.Notes)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"ok": true, "event": event})
|
||||
}
|
||||
112
internal/api/handlers/calendars.go
Normal file
112
internal/api/handlers/calendars.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type CalendarHandler struct {
|
||||
calSvc *service.CalendarService
|
||||
}
|
||||
|
||||
func NewCalendarHandler(calSvc *service.CalendarService) *CalendarHandler {
|
||||
return &CalendarHandler{calSvc: calSvc}
|
||||
}
|
||||
|
||||
func (h *CalendarHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
calendars, err := h.calSvc.List(r.Context(), userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteList(w, calendars, models.PageInfo{Limit: utils.DefaultLimit})
|
||||
}
|
||||
|
||||
func (h *CalendarHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
cal, err := h.calSvc.Create(r.Context(), userID, req.Name, req.Color)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"calendar": cal})
|
||||
}
|
||||
|
||||
func (h *CalendarHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
cal, err := h.calSvc.Get(r.Context(), userID, calID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"calendar": cal})
|
||||
}
|
||||
|
||||
func (h *CalendarHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Name *string `json:"name"`
|
||||
Color *string `json:"color"`
|
||||
IsPublic *bool `json:"is_public"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
cal, err := h.calSvc.Update(r.Context(), userID, calID, req.Name, req.Color, req.IsPublic)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"calendar": cal})
|
||||
}
|
||||
|
||||
func (h *CalendarHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.calSvc.Delete(r.Context(), userID, calID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
146
internal/api/handlers/contacts.go
Normal file
146
internal/api/handlers/contacts.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type ContactHandler struct {
|
||||
contactSvc *service.ContactService
|
||||
}
|
||||
|
||||
func NewContactHandler(contactSvc *service.ContactService) *ContactHandler {
|
||||
return &ContactHandler{contactSvc: contactSvc}
|
||||
}
|
||||
|
||||
func (h *ContactHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
q := r.URL.Query()
|
||||
|
||||
var search *string
|
||||
if s := q.Get("search"); s != "" {
|
||||
search = &s
|
||||
}
|
||||
limit := 50
|
||||
if l := q.Get("limit"); l != "" {
|
||||
if v, err := strconv.Atoi(l); err == nil {
|
||||
limit = v
|
||||
}
|
||||
}
|
||||
|
||||
contacts, cursor, err := h.contactSvc.List(r.Context(), userID, search, limit, q.Get("cursor"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteList(w, contacts, models.PageInfo{Limit: int(utils.ClampLimit(limit)), NextCursor: cursor})
|
||||
}
|
||||
|
||||
func (h *ContactHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
var req struct {
|
||||
FirstName *string `json:"first_name"`
|
||||
LastName *string `json:"last_name"`
|
||||
Email *string `json:"email"`
|
||||
Phone *string `json:"phone"`
|
||||
Company *string `json:"company"`
|
||||
Notes *string `json:"notes"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
contact, err := h.contactSvc.Create(r.Context(), userID, service.CreateContactRequest{
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Company: req.Company,
|
||||
Notes: req.Notes,
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"contact": contact})
|
||||
}
|
||||
|
||||
func (h *ContactHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
contactID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
contact, err := h.contactSvc.Get(r.Context(), userID, contactID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"contact": contact})
|
||||
}
|
||||
|
||||
func (h *ContactHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
contactID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
FirstName *string `json:"first_name"`
|
||||
LastName *string `json:"last_name"`
|
||||
Email *string `json:"email"`
|
||||
Phone *string `json:"phone"`
|
||||
Company *string `json:"company"`
|
||||
Notes *string `json:"notes"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
contact, err := h.contactSvc.Update(r.Context(), userID, contactID, service.UpdateContactRequest{
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
Company: req.Company,
|
||||
Notes: req.Notes,
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"contact": contact})
|
||||
}
|
||||
|
||||
func (h *ContactHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
contactID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.contactSvc.Delete(r.Context(), userID, contactID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
204
internal/api/handlers/events.go
Normal file
204
internal/api/handlers/events.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type EventHandler struct {
|
||||
eventSvc *service.EventService
|
||||
}
|
||||
|
||||
func NewEventHandler(eventSvc *service.EventService) *EventHandler {
|
||||
return &EventHandler{eventSvc: eventSvc}
|
||||
}
|
||||
|
||||
func (h *EventHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
q := r.URL.Query()
|
||||
|
||||
startStr := q.Get("start")
|
||||
endStr := q.Get("end")
|
||||
if startStr == "" || endStr == "" {
|
||||
utils.WriteError(w, models.NewValidationError("start and end query params required"))
|
||||
return
|
||||
}
|
||||
start, err := time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid start time"))
|
||||
return
|
||||
}
|
||||
end, err := time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid end time"))
|
||||
return
|
||||
}
|
||||
|
||||
var calendarID *uuid.UUID
|
||||
if cid := q.Get("calendar_id"); cid != "" {
|
||||
id, err := utils.ValidateUUID(cid)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
calendarID = &id
|
||||
}
|
||||
|
||||
var search *string
|
||||
if s := q.Get("search"); s != "" {
|
||||
search = &s
|
||||
}
|
||||
var tag *string
|
||||
if t := q.Get("tag"); t != "" {
|
||||
tag = &t
|
||||
}
|
||||
|
||||
limit := 50
|
||||
if l := q.Get("limit"); l != "" {
|
||||
if v, err := strconv.Atoi(l); err == nil {
|
||||
limit = v
|
||||
}
|
||||
}
|
||||
|
||||
events, cursor, err := h.eventSvc.List(r.Context(), userID, service.ListEventParams{
|
||||
RangeStart: start,
|
||||
RangeEnd: end,
|
||||
CalendarID: calendarID,
|
||||
Search: search,
|
||||
Tag: tag,
|
||||
Limit: limit,
|
||||
Cursor: q.Get("cursor"),
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteList(w, events, models.PageInfo{Limit: int(utils.ClampLimit(limit)), NextCursor: cursor})
|
||||
}
|
||||
|
||||
func (h *EventHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
var req struct {
|
||||
CalendarID uuid.UUID `json:"calendar_id"`
|
||||
Title string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Location *string `json:"location"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime time.Time `json:"end_time"`
|
||||
Timezone string `json:"timezone"`
|
||||
AllDay bool `json:"all_day"`
|
||||
RecurrenceRule *string `json:"recurrence_rule"`
|
||||
Reminders []int32 `json:"reminders"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.eventSvc.Create(r.Context(), userID, service.CreateEventRequest{
|
||||
CalendarID: req.CalendarID,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Location: req.Location,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
Timezone: req.Timezone,
|
||||
AllDay: req.AllDay,
|
||||
RecurrenceRule: req.RecurrenceRule,
|
||||
Reminders: req.Reminders,
|
||||
Tags: req.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"event": event})
|
||||
}
|
||||
|
||||
func (h *EventHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.eventSvc.Get(r.Context(), userID, eventID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"event": event})
|
||||
}
|
||||
|
||||
func (h *EventHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Location *string `json:"location"`
|
||||
StartTime *time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time"`
|
||||
Timezone *string `json:"timezone"`
|
||||
AllDay *bool `json:"all_day"`
|
||||
RecurrenceRule *string `json:"recurrence_rule"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.eventSvc.Update(r.Context(), userID, eventID, service.UpdateEventRequest{
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Location: req.Location,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
Timezone: req.Timezone,
|
||||
AllDay: req.AllDay,
|
||||
RecurrenceRule: req.RecurrenceRule,
|
||||
Tags: req.Tags,
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"event": event})
|
||||
}
|
||||
|
||||
func (h *EventHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.eventSvc.Delete(r.Context(), userID, eventID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
189
internal/api/handlers/ics.go
Normal file
189
internal/api/handlers/ics.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/repository"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ICSHandler struct {
|
||||
calSvc *service.CalendarService
|
||||
eventSvc *service.EventService
|
||||
queries *repository.Queries
|
||||
}
|
||||
|
||||
func NewICSHandler(calSvc *service.CalendarService, eventSvc *service.EventService, queries *repository.Queries) *ICSHandler {
|
||||
return &ICSHandler{calSvc: calSvc, eventSvc: eventSvc, queries: queries}
|
||||
}
|
||||
|
||||
func (h *ICSHandler) Export(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.calSvc.GetRole(r.Context(), calID, userID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
rangeStart := now.AddDate(-1, 0, 0)
|
||||
rangeEnd := now.AddDate(1, 0, 0)
|
||||
|
||||
events, err := h.queries.ListEventsByCalendarInRange(r.Context(), repository.ListEventsByCalendarInRangeParams{
|
||||
CalendarID: utils.ToPgUUID(calID),
|
||||
EndTime: utils.ToPgTimestamptz(rangeStart),
|
||||
StartTime: utils.ToPgTimestamptz(rangeEnd),
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.ErrInternal)
|
||||
return
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//CalendarAPI//EN\r\n")
|
||||
|
||||
for _, ev := range events {
|
||||
b.WriteString("BEGIN:VEVENT\r\n")
|
||||
b.WriteString(fmt.Sprintf("UID:%s\r\n", utils.FromPgUUID(ev.ID).String()))
|
||||
b.WriteString(fmt.Sprintf("DTSTART:%s\r\n", utils.FromPgTimestamptz(ev.StartTime).Format("20060102T150405Z")))
|
||||
b.WriteString(fmt.Sprintf("DTEND:%s\r\n", utils.FromPgTimestamptz(ev.EndTime).Format("20060102T150405Z")))
|
||||
b.WriteString(fmt.Sprintf("SUMMARY:%s\r\n", ev.Title))
|
||||
if ev.Description.Valid {
|
||||
b.WriteString(fmt.Sprintf("DESCRIPTION:%s\r\n", ev.Description.String))
|
||||
}
|
||||
if ev.Location.Valid {
|
||||
b.WriteString(fmt.Sprintf("LOCATION:%s\r\n", ev.Location.String))
|
||||
}
|
||||
if ev.RecurrenceRule.Valid {
|
||||
b.WriteString(fmt.Sprintf("RRULE:%s\r\n", ev.RecurrenceRule.String))
|
||||
}
|
||||
b.WriteString("END:VEVENT\r\n")
|
||||
}
|
||||
|
||||
b.WriteString("END:VCALENDAR\r\n")
|
||||
|
||||
w.Header().Set("Content-Type", "text/calendar")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=calendar.ics")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(b.String()))
|
||||
}
|
||||
|
||||
func (h *ICSHandler) Import(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
|
||||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("invalid multipart form"))
|
||||
return
|
||||
}
|
||||
|
||||
calIDStr := r.FormValue("calendar_id")
|
||||
calID, err := utils.ValidateUUID(calIDStr)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
role, err := h.calSvc.GetRole(r.Context(), calID, userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
if role != "owner" && role != "editor" {
|
||||
utils.WriteError(w, models.ErrForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
file, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.NewValidationError("file required"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
utils.WriteError(w, models.ErrInternal)
|
||||
return
|
||||
}
|
||||
|
||||
count := h.parseAndImportICS(r.Context(), string(data), calID, userID)
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"ok": true,
|
||||
"imported": map[string]int{"events": count},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ICSHandler) parseAndImportICS(ctx context.Context, data string, calID, userID uuid.UUID) int {
|
||||
count := 0
|
||||
lines := strings.Split(data, "\n")
|
||||
var inEvent bool
|
||||
var title, description, location, rruleStr string
|
||||
var dtstart, dtend time.Time
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "BEGIN:VEVENT" {
|
||||
inEvent = true
|
||||
title, description, location, rruleStr = "", "", "", ""
|
||||
dtstart, dtend = time.Time{}, time.Time{}
|
||||
continue
|
||||
}
|
||||
if line == "END:VEVENT" && inEvent {
|
||||
inEvent = false
|
||||
if title != "" && !dtstart.IsZero() && !dtend.IsZero() {
|
||||
_, err := h.queries.CreateEvent(ctx, repository.CreateEventParams{
|
||||
ID: utils.ToPgUUID(uuid.New()),
|
||||
CalendarID: utils.ToPgUUID(calID),
|
||||
Title: title,
|
||||
Description: utils.ToPgText(description),
|
||||
Location: utils.ToPgText(location),
|
||||
StartTime: utils.ToPgTimestamptz(dtstart),
|
||||
EndTime: utils.ToPgTimestamptz(dtend),
|
||||
Timezone: "UTC",
|
||||
RecurrenceRule: utils.ToPgText(rruleStr),
|
||||
Tags: []string{},
|
||||
CreatedBy: utils.ToPgUUID(userID),
|
||||
UpdatedBy: utils.ToPgUUID(userID),
|
||||
})
|
||||
if err == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !inEvent {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(line, "SUMMARY:"):
|
||||
title = strings.TrimPrefix(line, "SUMMARY:")
|
||||
case strings.HasPrefix(line, "DESCRIPTION:"):
|
||||
description = strings.TrimPrefix(line, "DESCRIPTION:")
|
||||
case strings.HasPrefix(line, "LOCATION:"):
|
||||
location = strings.TrimPrefix(line, "LOCATION:")
|
||||
case strings.HasPrefix(line, "RRULE:"):
|
||||
rruleStr = strings.TrimPrefix(line, "RRULE:")
|
||||
case strings.HasPrefix(line, "DTSTART:"):
|
||||
dtstart, _ = time.Parse("20060102T150405Z", strings.TrimPrefix(line, "DTSTART:"))
|
||||
case strings.HasPrefix(line, "DTEND:"):
|
||||
dtend, _ = time.Parse("20060102T150405Z", strings.TrimPrefix(line, "DTEND:"))
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
64
internal/api/handlers/reminders.go
Normal file
64
internal/api/handlers/reminders.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type ReminderHandler struct {
|
||||
reminderSvc *service.ReminderService
|
||||
}
|
||||
|
||||
func NewReminderHandler(reminderSvc *service.ReminderService) *ReminderHandler {
|
||||
return &ReminderHandler{reminderSvc: reminderSvc}
|
||||
}
|
||||
|
||||
func (h *ReminderHandler) Add(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MinutesBefore []int32 `json:"minutes_before"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.reminderSvc.AddReminders(r.Context(), userID, eventID, req.MinutesBefore)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"event": event})
|
||||
}
|
||||
|
||||
func (h *ReminderHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
eventID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
reminderID, err := utils.ValidateUUID(chi.URLParam(r, "reminderID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.reminderSvc.DeleteReminder(r.Context(), userID, eventID, reminderID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
84
internal/api/handlers/sharing.go
Normal file
84
internal/api/handlers/sharing.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type SharingHandler struct {
|
||||
calSvc *service.CalendarService
|
||||
}
|
||||
|
||||
func NewSharingHandler(calSvc *service.CalendarService) *SharingHandler {
|
||||
return &SharingHandler{calSvc: calSvc}
|
||||
}
|
||||
|
||||
func (h *SharingHandler) Share(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Target struct {
|
||||
Email string `json:"email"`
|
||||
} `json:"target"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.calSvc.Share(r.Context(), userID, calID, req.Target.Email, req.Role); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
|
||||
func (h *SharingHandler) ListMembers(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
members, err := h.calSvc.ListMembers(r.Context(), userID, calID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteList(w, members, models.PageInfo{Limit: utils.DefaultLimit})
|
||||
}
|
||||
|
||||
func (h *SharingHandler) RemoveMember(w http.ResponseWriter, r *http.Request) {
|
||||
userID, _ := middleware.GetUserID(r.Context())
|
||||
calID, err := utils.ValidateUUID(chi.URLParam(r, "id"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
targetID, err := utils.ValidateUUID(chi.URLParam(r, "userID"))
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.calSvc.RemoveMember(r.Context(), userID, calID, targetID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
73
internal/api/handlers/users.go
Normal file
73
internal/api/handlers/users.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/calendarapi/internal/middleware"
|
||||
"github.com/calendarapi/internal/models"
|
||||
"github.com/calendarapi/internal/service"
|
||||
"github.com/calendarapi/internal/utils"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
userSvc *service.UserService
|
||||
}
|
||||
|
||||
func NewUserHandler(userSvc *service.UserService) *UserHandler {
|
||||
return &UserHandler{userSvc: userSvc}
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserID(r.Context())
|
||||
if !ok {
|
||||
utils.WriteError(w, models.ErrAuthRequired)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userSvc.GetMe(r.Context(), userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"user": user})
|
||||
}
|
||||
|
||||
func (h *UserHandler) UpdateMe(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserID(r.Context())
|
||||
if !ok {
|
||||
utils.WriteError(w, models.ErrAuthRequired)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Timezone *string `json:"timezone"`
|
||||
}
|
||||
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userSvc.Update(r.Context(), userID, req.Timezone)
|
||||
if err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{"user": user})
|
||||
}
|
||||
|
||||
func (h *UserHandler) DeleteMe(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := middleware.GetUserID(r.Context())
|
||||
if !ok {
|
||||
utils.WriteError(w, models.ErrAuthRequired)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userSvc.Delete(r.Context(), userID); err != nil {
|
||||
utils.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteOK(w)
|
||||
}
|
||||
Reference in New Issue
Block a user