Add OpenAPI docs, frontend, migrations, and API updates

- OpenAPI: add missing endpoints (add-from-url, subscriptions, public availability)
- OpenAPI: CalendarSubscription schema, Subscriptions tag
- Frontend app
- Migrations: count_for_availability, subscriptions_sync, user_preferences, calendar_settings
- Config, rate limit, auth, calendar, booking, ICS, availability, user service updates

Made-with: Cursor
This commit is contained in:
Michilis
2026-03-02 14:07:55 +00:00
parent 2cb9d72a7f
commit 75105b8b46
8120 changed files with 1486881 additions and 314 deletions

View File

@@ -1,11 +1,15 @@
package api
import (
"time"
"github.com/calendarapi/internal/api/handlers"
"github.com/calendarapi/internal/api/openapi"
"github.com/calendarapi/internal/config"
mw "github.com/calendarapi/internal/middleware"
"github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)
type Handlers struct {
@@ -23,12 +27,26 @@ type Handlers struct {
ICS *handlers.ICSHandler
}
func NewRouter(h Handlers, authMW *mw.AuthMiddleware, rateLimiter *mw.RateLimiter) *chi.Mux {
func NewRouter(h Handlers, authMW *mw.AuthMiddleware, rateLimiter *mw.RateLimiter, cfg *config.Config) *chi.Mux {
r := chi.NewRouter()
r.Use(chimw.RequestID)
r.Use(chimw.Logger)
r.Use(chimw.Recoverer)
r.Use(chimw.RealIP)
r.Use(chimw.Timeout(30 * time.Second))
origins := cfg.CORSOrigins
if len(origins) == 0 {
origins = []string{"http://localhost:5173", "http://127.0.0.1:5173"}
}
r.Use(cors.Handler(cors.Options{
AllowedOrigins: origins,
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-API-Key"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
}))
r.Use(rateLimiter.Limit)
// OpenAPI spec and Swagger UI
@@ -45,6 +63,10 @@ func NewRouter(h Handlers, authMW *mw.AuthMiddleware, rateLimiter *mw.RateLimite
r.Post("/booking/{token}/reserve", h.Booking.Reserve)
r.Get("/cal/{token}/feed.ics", h.ICS.PublicFeed)
// Public availability (no auth) - for external booking tools
r.Get("/availability/aggregate", h.Availability.GetAggregate)
r.Get("/availability/{token}", h.Availability.GetByToken)
})
// Authenticated routes
@@ -71,6 +93,7 @@ func NewRouter(h Handlers, authMW *mw.AuthMiddleware, rateLimiter *mw.RateLimite
r.With(mw.RequireScope("calendars", "write")).Post("/", h.Calendar.Create)
r.With(mw.RequireScope("calendars", "write")).Post("/import", h.ICS.Import)
r.With(mw.RequireScope("calendars", "write")).Post("/import-url", h.ICS.ImportURL)
r.With(mw.RequireScope("calendars", "write")).Post("/add-from-url", h.ICS.AddFromURL)
r.Route("/{id}", func(r chi.Router) {
r.With(mw.RequireScope("calendars", "read")).Get("/", h.Calendar.Get)
@@ -87,6 +110,12 @@ func NewRouter(h Handlers, authMW *mw.AuthMiddleware, rateLimiter *mw.RateLimite
// ICS
r.With(mw.RequireScope("calendars", "read")).Get("/export.ics", h.ICS.Export)
// Subscriptions
r.With(mw.RequireScope("calendars", "read")).Get("/subscriptions", h.ICS.ListSubscriptions)
r.With(mw.RequireScope("calendars", "write")).Post("/subscriptions", h.ICS.AddSubscription)
r.With(mw.RequireScope("calendars", "write")).Delete("/subscriptions/{subId}", h.ICS.DeleteSubscription)
r.With(mw.RequireScope("calendars", "write")).Post("/subscriptions/{subId}/sync", h.ICS.SyncSubscription)
})
})