package bot import ( "context" "errors" "net/http" "codeberg.org/nextgo/dbots/internal/db" "codeberg.org/nextgo/dbots/internal/errorutil" "codeberg.org/nextgo/dbots/internal/middleware" "codeberg.org/nextgo/dbots/internal/paginate" "github.com/go-chi/chi/v5" "github.com/go-chi/render" ) type contextKey string const botKey contextKey = "bot" type Router struct { bots *Service router chi.Router } func NewRouter(q *db.Queries) *Router { return &Router{ bots: NewService(q), router: chi.NewRouter(), } } func (r *Router) Routes() http.Handler { r.router.Get("/", r.listBots) // todo: deprecate this r.router.With(middleware.AuthGuardMiddleware).Post("/", r.submitBot) r.router.Route("/{botID}", func(router chi.Router) { router.Use(r.BotContext) router.With(r.BotCache).Get("/", r.getBot) router.Route("/co-owners", func(c chi.Router) { c.Use(middleware.AuthGuardMiddleware) c.Get("/", r.listCoOwners) c.Post("/{userID}", r.addCoOwner) c.Delete("/{userID}", r.removeCoOwner) }) }) return r.router } func (r *Router) submitBot(w http.ResponseWriter, req *http.Request) { var data CreateBotRequest if err := render.Bind(req, &data); err != nil { render.Render(w, req, errorutil.ErrInvalidRequest(err)) return } ctx := req.Context() bot, err := r.bots.Submit(ctx, data) if errors.Is(err, errorutil.ErrBotAlreadyExists) { render.Render(w, req, errorutil.ErrInvalidRequest(err)) } else { render.Render(w, req, errorutil.ErrInternal(err)) } render.Status(req, http.StatusCreated) render.JSON(w, req, bot) } func (r *Router) getBot(w http.ResponseWriter, req *http.Request) { ctx := req.Context() bot, ok := ctx.Value(botKey).(*db.Bot) if !ok { render.Render(w, req, errorutil.ErrInvalidRequest(nil)) return } render.JSON(w, req, bot) } func (r *Router) listBots(w http.ResponseWriter, req *http.Request) { ctx := req.Context() query := req.URL.Query().Get("q") p := paginate.ParseParams(req) page, err := r.bots.List(ctx, query, p) if err != nil { render.Render(w, req, errorutil.ErrInvalidRequest(err)) return } render.JSON(w, req, page) } func (r *Router) listCoOwners(w http.ResponseWriter, req *http.Request) { ctx := req.Context() bot := ctx.Value(botKey).(*db.Bot) owners, err := r.bots.ListCoOwners(ctx, bot.ID) if err != nil { render.Render(w, req, errorutil.ErrInternal(err)) return } render.JSON(w, req, owners) } func (r *Router) addCoOwner(w http.ResponseWriter, req *http.Request) { ctx := req.Context() bot := ctx.Value(botKey).(*db.Bot) userID := chi.URLParam(req, "userID") if err := r.bots.AddCoOwner(ctx, bot.ID, userID); err != nil { switch { case errors.Is(err, errorutil.ErrNotFound.Err): render.Render(w, req, errorutil.ErrNotFound) case errors.Is(err, errorutil.ErrForbidden.Err): render.Render(w, req, errorutil.ErrForbidden) default: render.Render(w, req, errorutil.ErrInvalidRequest(err)) } return } render.NoContent(w, req) } func (r *Router) removeCoOwner(w http.ResponseWriter, req *http.Request) { ctx := req.Context() bot := ctx.Value(botKey).(*db.Bot) userID := chi.URLParam(req, "userID") if err := r.bots.RemoveCoOwner(ctx, bot.ID, userID); err != nil { switch { case errors.Is(err, errorutil.ErrNotFound.Err): render.Render(w, req, errorutil.ErrNotFound) case errors.Is(err, errorutil.ErrForbidden.Err): render.Render(w, req, errorutil.ErrForbidden) default: render.Render(w, req, errorutil.ErrInternal(err)) } return } render.NoContent(w, req) } func (r *Router) BotContext(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() botID := chi.URLParam(req, "botID") bot, err := r.bots.Get(ctx, botID) if err != nil { render.Render(w, req, errorutil.ErrNotFound) return } ctx = context.WithValue(ctx, botKey, bot) next.ServeHTTP(w, req.WithContext(ctx)) }) } func (r *Router) BotCache(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Add("Cache-Control", "max-age=3600") next.ServeHTTP(w, req) }) }