Compare commits
3 commits
0326e90c1b
...
9ef5e33b82
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ef5e33b82 | |||
| 9df1d0de56 | |||
| 6c883f3867 |
11 changed files with 308 additions and 52 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -26,8 +26,11 @@ go.work.sum
|
||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
.direnv
|
.direnv/
|
||||||
|
|
||||||
|
# pg
|
||||||
|
.pgdata/
|
||||||
|
.pgsocket/
|
||||||
# Editor/IDE
|
# Editor/IDE
|
||||||
# .idea/
|
# .idea/
|
||||||
# .vscode/
|
# .vscode/
|
||||||
|
|
|
||||||
27
flake.nix
27
flake.nix
|
|
@ -14,6 +14,7 @@
|
||||||
"x86_64-darwin"
|
"x86_64-darwin"
|
||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
];
|
];
|
||||||
|
|
||||||
forEachSupportedSystem =
|
forEachSupportedSystem =
|
||||||
f:
|
f:
|
||||||
inputs.nixpkgs.lib.genAttrs supportedSystems (
|
inputs.nixpkgs.lib.genAttrs supportedSystems (
|
||||||
|
|
@ -39,7 +40,33 @@
|
||||||
go
|
go
|
||||||
sqlc
|
sqlc
|
||||||
goose
|
goose
|
||||||
|
postgresql
|
||||||
|
|
||||||
|
(pkgs.writeShellScriptBin "pg-start" ''
|
||||||
|
export PGDATA=${"$PWD"}/.pgdata
|
||||||
|
export PGHOST=${"$PWD"}/.pgsocket
|
||||||
|
|
||||||
|
mkdir -p "$PGDATA"
|
||||||
|
mkdir -p "$PGHOST"
|
||||||
|
|
||||||
|
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
||||||
|
echo "Initializing database..."
|
||||||
|
initdb -D "$PGDATA"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting postgres..."
|
||||||
|
pg_ctl -D "$PGDATA" -l logfile -o "-k $PGHOST" start
|
||||||
|
'')
|
||||||
|
|
||||||
|
(pkgs.writeShellScriptBin "pg-stop" ''
|
||||||
|
export PGDATA=${"$PWD"}/.pgdata
|
||||||
|
pg_ctl -D "$PGDATA" stop
|
||||||
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export PGDATA=$PWD/.pgdata
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
"codeberg.org/nextgo/dbots/internal/errorutil"
|
"codeberg.org/nextgo/dbots/internal/errorutil"
|
||||||
"codeberg.org/nextgo/dbots/internal/paginate"
|
"codeberg.org/nextgo/dbots/internal/paginate"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
|
@ -18,6 +20,20 @@ func NewService(q *db.Queries) *Service {
|
||||||
return &Service{q: q}
|
return &Service{q: q}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) Get(ctx context.Context, id string) (*db.Bot, error) {
|
||||||
|
bot, err := s.q.GetBot(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error getting bot", "err", err, "id", id)
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, errorutil.ErrNotFound.Err
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bot, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) ListBots(ctx context.Context, status db.BotStatus, p paginate.Params) (paginate.Page[*db.Bot], error) {
|
func (s *Service) ListBots(ctx context.Context, status db.BotStatus, p paginate.Params) (paginate.Page[*db.Bot], error) {
|
||||||
status = db.BotStatus(strings.ToLower(status.String()))
|
status = db.BotStatus(strings.ToLower(status.String()))
|
||||||
total, err := s.q.CountBotsByUsername(ctx, db.CountBotsByUsernameParams{
|
total, err := s.q.CountBotsByUsername(ctx, db.CountBotsByUsernameParams{
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import (
|
||||||
|
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
"codeberg.org/nextgo/dbots/internal/errorutil"
|
"codeberg.org/nextgo/dbots/internal/errorutil"
|
||||||
|
"codeberg.org/nextgo/dbots/internal/middleware"
|
||||||
"codeberg.org/nextgo/dbots/internal/paginate"
|
"codeberg.org/nextgo/dbots/internal/paginate"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
@ -23,14 +25,24 @@ func NewRouter(q *db.Queries) *Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Routes() http.Handler {
|
func (r *Router) Routes() http.Handler {
|
||||||
r.router.Get("/bots", r.listBots)
|
|
||||||
r.router.Route("/bots", func(router chi.Router) {
|
r.router.Route("/bots", func(router chi.Router) {
|
||||||
|
router.Get("/", r.listBots)
|
||||||
|
router.Route("/{botID}", func(b chi.Router) {
|
||||||
|
b.Use(middleware.BotContext(r.admin.q))
|
||||||
|
b.Get("/", r.getBot)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return r.router
|
return r.router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) getBot(w http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx := req.Context()
|
||||||
|
bot := middleware.GetBot(ctx)
|
||||||
|
|
||||||
|
render.JSON(w, req, bot)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) listBots(w http.ResponseWriter, req *http.Request) {
|
func (r *Router) listBots(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
status := db.BotStatus(req.URL.Query().Get("s"))
|
status := db.BotStatus(req.URL.Query().Get("s"))
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@ package bot
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
"codeberg.org/nextgo/dbots/internal/errorutil"
|
"codeberg.org/nextgo/dbots/internal/errorutil"
|
||||||
"codeberg.org/nextgo/dbots/internal/middleware"
|
"codeberg.org/nextgo/dbots/internal/middleware"
|
||||||
"codeberg.org/nextgo/dbots/internal/paginate"
|
"codeberg.org/nextgo/dbots/internal/paginate"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -23,7 +26,7 @@ func NewService(q *db.Queries) *Service {
|
||||||
func (s *Service) Submit(ctx context.Context, data CreateBotRequest) (*db.Bot, error) {
|
func (s *Service) Submit(ctx context.Context, data CreateBotRequest) (*db.Bot, error) {
|
||||||
user := middleware.GetUser(ctx)
|
user := middleware.GetUser(ctx)
|
||||||
|
|
||||||
var count int32 = 0
|
var count int32
|
||||||
b, err := s.q.CreateBot(ctx, db.CreateBotParams{
|
b, err := s.q.CreateBot(ctx, db.CreateBotParams{
|
||||||
ID: data.ID,
|
ID: data.ID,
|
||||||
Overview: &data.Overview,
|
Overview: &data.Overview,
|
||||||
|
|
@ -41,20 +44,127 @@ func (s *Service) Submit(ctx context.Context, data CreateBotRequest) (*db.Bot, e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error submitting bot", "err", err, "bot_id", data.ID)
|
slog.Error("error submitting bot", "err", err, "bot_id", data.ID)
|
||||||
var pgErr *pgconn.PgError
|
var pgErr *pgconn.PgError
|
||||||
if errors.As(err, &pgErr) {
|
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
|
||||||
switch pgErr.Code {
|
return nil, errorutil.ErrBotAlreadyExists
|
||||||
case "23505":
|
|
||||||
return nil, errorutil.ErrBotAlreadyExists
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.addCoOwners(ctx, b.ID, user.ID, data.CoOwners); err != nil {
|
||||||
|
// Bot was created but co-owners failed. Log and surface the error.
|
||||||
|
// ideally this whole operation runs in a transaction but who cares lol.
|
||||||
|
slog.Error("error adding co-owners after submit", "err", err, "bot_id", b.ID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addCoOwners validates and inserts each co-owner for a bot.
|
||||||
|
// mainOwnerID is provided to reject attempts to set the main owner as a co-owner.
|
||||||
|
func (s *Service) addCoOwners(ctx context.Context, botID, mainOwnerID string, coOwnerIDs []string) error {
|
||||||
|
for _, id := range coOwnerIDs {
|
||||||
|
if err := validateCoOwner(id, mainOwnerID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.q.AddBotCoOwner(ctx, db.AddBotCoOwnerParams{
|
||||||
|
BotID: botID,
|
||||||
|
UserID: id,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("adding co-owner %s: %w", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCoOwner adds a single co-owner to a bot.
|
||||||
|
// Only the main owner of the bot may call this.
|
||||||
|
func (s *Service) AddCoOwner(ctx context.Context, botID, coOwnerID string) error {
|
||||||
|
user := middleware.GetUser(ctx)
|
||||||
|
|
||||||
|
bot, err := s.q.GetBot(ctx, botID)
|
||||||
|
if err != nil {
|
||||||
|
return errorutil.ErrNotFound.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bot.MainOwnerID == nil || *bot.MainOwnerID != user.ID {
|
||||||
|
return errorutil.ErrForbidden.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateCoOwner(coOwnerID, user.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.q.AddBotCoOwner(ctx, db.AddBotCoOwnerParams{
|
||||||
|
BotID: botID,
|
||||||
|
UserID: coOwnerID,
|
||||||
|
}); err != nil {
|
||||||
|
slog.Error("error adding co-owner", "err", err, "bot_id", botID, "user_id", coOwnerID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveCoOwner removes a co-owner from a bot.
|
||||||
|
// Only the main owner of the bot may call this.
|
||||||
|
func (s *Service) RemoveCoOwner(ctx context.Context, botID, coOwnerID string) error {
|
||||||
|
user := middleware.GetUser(ctx)
|
||||||
|
|
||||||
|
bot, err := s.q.GetBot(ctx, botID)
|
||||||
|
if err != nil {
|
||||||
|
return errorutil.ErrNotFound.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bot.MainOwnerID == nil || *bot.MainOwnerID != user.ID {
|
||||||
|
return errorutil.ErrForbidden.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.q.RemoveBotCoOwner(ctx, db.RemoveBotCoOwnerParams{
|
||||||
|
BotID: botID,
|
||||||
|
UserID: coOwnerID,
|
||||||
|
}); err != nil {
|
||||||
|
slog.Error("error removing co-owner", "err", err, "bot_id", botID, "user_id", coOwnerID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCoOwners returns the users who are co-owners of a bot.
|
||||||
|
func (s *Service) ListCoOwners(ctx context.Context, botID string) ([]*db.User, error) {
|
||||||
|
owners, err := s.q.ListCoOwnersByBot(ctx, botID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error listing co-owners", "err", err, "bot_id", botID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return owners, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateCoOwner checks that the given ID is a valid Discord snowflake
|
||||||
|
// and is not the same as the main owner.
|
||||||
|
func validateCoOwner(id, mainOwnerID string) error {
|
||||||
|
if _, err := strconv.ParseUint(id, 10, 64); err != nil {
|
||||||
|
return errorutil.ErrInvalidID
|
||||||
|
}
|
||||||
|
if id == mainOwnerID {
|
||||||
|
return errorutil.ErrMainOwnerAsCoOwner
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) Get(ctx context.Context, id string) (*db.Bot, error) {
|
func (s *Service) Get(ctx context.Context, id string) (*db.Bot, error) {
|
||||||
return s.q.GetBot(ctx, id)
|
bot, err := s.q.GetBot(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error getting bot", "err", err, "id", id)
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, errorutil.ErrNotFound.Err
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) List(
|
func (s *Service) List(
|
||||||
|
|
@ -75,7 +185,6 @@ func (s *Service) List(
|
||||||
Status: db.BotStatusApproved,
|
Status: db.BotStatusApproved,
|
||||||
Limit: p.Limit,
|
Limit: p.Limit,
|
||||||
Offset: p.Offset,
|
Offset: p.Offset,
|
||||||
// todo: query
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error listing bots", "query", query, "err", err)
|
slog.Error("error listing bots", "query", query, "err", err)
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,7 @@ func (c *CreateBotRequest) Bind(req *http.Request) error {
|
||||||
return errorutil.ErrInvalidID
|
return errorutil.ErrInvalidID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: proper checks idk
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
|
@ -14,10 +13,6 @@ import (
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey string
|
|
||||||
|
|
||||||
const botKey contextKey = "bot"
|
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
bots *Service
|
bots *Service
|
||||||
router chi.Router
|
router chi.Router
|
||||||
|
|
@ -31,32 +26,39 @@ func NewRouter(q *db.Queries) *Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Routes() http.Handler {
|
func (r *Router) Routes() http.Handler {
|
||||||
r.router.
|
r.router.Get("/", r.listBots) // todo: deprecate this
|
||||||
With(middleware.AuthGuardMiddleware).
|
r.router.With(middleware.AuthGuardMiddleware).Post("/", r.submitBot)
|
||||||
Post("/", r.submitBot)
|
|
||||||
r.router.Get("/", r.listBots)
|
|
||||||
r.router.Route("/{botID}", func(router chi.Router) {
|
r.router.Route("/{botID}", func(router chi.Router) {
|
||||||
router.Use(r.BotContext)
|
router.Use(middleware.BotContext(r.bots.q))
|
||||||
router.With(r.BotCache).Get("/", r.getBot)
|
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
|
return r.router
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) submitBot(w http.ResponseWriter, req *http.Request) {
|
func (r *Router) submitBot(w http.ResponseWriter, req *http.Request) {
|
||||||
data := &CreateBotRequest{}
|
var data CreateBotRequest
|
||||||
|
|
||||||
if err := render.Bind(req, data); err != nil {
|
if err := render.Bind(req, &data); err != nil {
|
||||||
render.Render(w, req, errorutil.ErrInvalidRequest(err))
|
render.Render(w, req, errorutil.ErrInvalidRequest(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
bot, err := r.bots.Submit(ctx, *data)
|
bot, err := r.bots.Submit(ctx, data)
|
||||||
if errors.Is(err, errorutil.ErrBotAlreadyExists) {
|
if err != nil {
|
||||||
render.Render(w, req, errorutil.ErrInvalidRequest(err))
|
if errors.Is(err, errorutil.ErrBotAlreadyExists) {
|
||||||
} else {
|
render.Render(w, req, errorutil.ErrInvalidRequest(err))
|
||||||
render.Render(w, req, errorutil.ErrInternal(err))
|
} else {
|
||||||
|
render.Render(w, req, errorutil.ErrInternal(err))
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Status(req, http.StatusCreated)
|
render.Status(req, http.StatusCreated)
|
||||||
|
|
@ -65,12 +67,7 @@ func (r *Router) submitBot(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
func (r *Router) getBot(w http.ResponseWriter, req *http.Request) {
|
func (r *Router) getBot(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
bot := middleware.GetBot(ctx)
|
||||||
bot, ok := ctx.Value(botKey).(*db.Bot)
|
|
||||||
if !ok {
|
|
||||||
render.Render(w, req, errorutil.ErrInvalidRequest(nil))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
render.JSON(w, req, bot)
|
render.JSON(w, req, bot)
|
||||||
}
|
}
|
||||||
|
|
@ -89,19 +86,57 @@ func (r *Router) listBots(w http.ResponseWriter, req *http.Request) {
|
||||||
render.JSON(w, req, page)
|
render.JSON(w, req, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) BotContext(next http.Handler) http.Handler {
|
func (r *Router) listCoOwners(w http.ResponseWriter, req *http.Request) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
ctx := req.Context()
|
||||||
ctx := req.Context()
|
bot := middleware.GetBot(ctx)
|
||||||
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)
|
owners, err := r.bots.ListCoOwners(ctx, bot.ID)
|
||||||
next.ServeHTTP(w, req.WithContext(ctx))
|
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 := middleware.GetBot(ctx)
|
||||||
|
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 := middleware.GetBot(ctx)
|
||||||
|
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) BotCache(next http.Handler) http.Handler {
|
func (r *Router) BotCache(next http.Handler) http.Handler {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ErrBotNotFound = errors.New("bot not found")
|
||||||
ErrBotAlreadyExists = errors.New("this bot has already been submitted")
|
ErrBotAlreadyExists = errors.New("this bot has already been submitted")
|
||||||
ErrSearchFailed = errors.New("no bots found fitting this filter")
|
ErrSearchFailed = errors.New("no bots found fitting this filter")
|
||||||
ErrInvalidID = errors.New("invalid discord id")
|
ErrInvalidID = errors.New("invalid discord id")
|
||||||
|
|
@ -54,6 +55,11 @@ var ErrUnauthorized = &ErrResponse{
|
||||||
StatusText: "unauthorized",
|
StatusText: "unauthorized",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrForbidden = &ErrResponse{
|
||||||
|
HTTPStatusCode: 403,
|
||||||
|
StatusText: "forbidden",
|
||||||
|
}
|
||||||
|
|
||||||
func errString(err error) string {
|
func errString(err error) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
45
internal/middleware/bot.go
Normal file
45
internal/middleware/bot.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
|
"codeberg.org/nextgo/dbots/internal/errorutil"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
const botKey contextKey = "bot"
|
||||||
|
|
||||||
|
func BotContext(q *db.Queries) func(next http.Handler) http.Handler {
|
||||||
|
return func(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 := q.GetBot(ctx, botID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
render.Render(w, req, errorutil.ErrNotFound)
|
||||||
|
} else {
|
||||||
|
render.Render(w, req, errorutil.ErrInternal(err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, botKey, bot)
|
||||||
|
next.ServeHTTP(w, req.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBot(ctx context.Context) *db.Bot {
|
||||||
|
bot, ok := ctx.Value(botKey).(*db.Bot)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bot
|
||||||
|
}
|
||||||
|
|
@ -21,10 +21,10 @@ type Server struct {
|
||||||
queries *db.Queries
|
queries *db.Queries
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(queries *db.Queries, logger *slog.Logger) *Server {
|
func NewServer(queries *db.Queries) *Server {
|
||||||
router := chi.NewMux()
|
router := chi.NewMux()
|
||||||
|
|
||||||
router.Use(httplog.RequestLogger(logger, nil))
|
router.Use(httplog.RequestLogger(slog.Default(), &httplog.Options{}))
|
||||||
router.Use(middleware.Recoverer)
|
router.Use(middleware.Recoverer)
|
||||||
router.Use(middleware.RequestID)
|
router.Use(middleware.RequestID)
|
||||||
router.Use(middleware.RealIP)
|
router.Use(middleware.RealIP)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ type Config struct {
|
||||||
var config Config
|
var config Config
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
gonsoleHandler := gonsole.New(os.Stdout, slog.LevelDebug)
|
||||||
|
slogHandler := slog.New(gonsoleHandler)
|
||||||
|
slog.SetDefault(slogHandler)
|
||||||
|
|
||||||
dotenv.MustLoad()
|
dotenv.MustLoad()
|
||||||
if err := env.Load(&config); err != nil {
|
if err := env.Load(&config); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -28,9 +32,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
gonsoleHandler := gonsole.New(os.Stdout, slog.LevelDebug)
|
|
||||||
slogHandler := slog.New(gonsoleHandler)
|
|
||||||
slog.SetDefault(slogHandler)
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
conn, err := pgxpool.New(ctx, config.DatabaseURL)
|
conn, err := pgxpool.New(ctx, config.DatabaseURL)
|
||||||
|
|
@ -40,7 +41,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
queries := db.New(conn)
|
queries := db.New(conn)
|
||||||
server := server.NewServer(queries, slogHandler)
|
server := server.NewServer(queries)
|
||||||
|
|
||||||
server.Start(config.Port)
|
server.Start(config.Port)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue