feat: add discord api metadata
This commit is contained in:
parent
9ef5e33b82
commit
f46c73511a
16 changed files with 230 additions and 101 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -31,6 +31,8 @@ go.work.sum
|
||||||
# pg
|
# pg
|
||||||
.pgdata/
|
.pgdata/
|
||||||
.pgsocket/
|
.pgsocket/
|
||||||
|
logfile
|
||||||
|
|
||||||
# Editor/IDE
|
# Editor/IDE
|
||||||
# .idea/
|
# .idea/
|
||||||
# .vscode/
|
# .vscode/
|
||||||
|
|
|
||||||
27
internal/config/config.go
Normal file
27
internal/config/config.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeberg.org/ungo/env"
|
||||||
|
"codeberg.org/ungo/env/dotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DatabaseURL string `env:"DATABASE_URL,required"`
|
||||||
|
Port int `env:"PORT,default=8080"`
|
||||||
|
DiscordToken string `env:"DISCORD_TOKEN"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() Config {
|
||||||
|
var cfg Config
|
||||||
|
if err := env.Load(&cfg); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := dotenv.Load(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -134,34 +134,6 @@ func (q *Queries) GetBot(ctx context.Context, id string) (*Bot, error) {
|
||||||
return &i, err
|
return &i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBotByUsername = `-- name: GetBotByUsername :one
|
|
||||||
SELECT id, username, avatar, overview, description, is_slash, install_context, guild_count, install_count, imported_from, prefix, created_at, updated_at, status, main_owner_id FROM bots
|
|
||||||
WHERE username = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) GetBotByUsername(ctx context.Context, username string) (*Bot, error) {
|
|
||||||
row := q.db.QueryRow(ctx, getBotByUsername, username)
|
|
||||||
var i Bot
|
|
||||||
err := row.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Username,
|
|
||||||
&i.Avatar,
|
|
||||||
&i.Overview,
|
|
||||||
&i.Description,
|
|
||||||
&i.IsSlash,
|
|
||||||
&i.InstallContext,
|
|
||||||
&i.GuildCount,
|
|
||||||
&i.InstallCount,
|
|
||||||
&i.ImportedFrom,
|
|
||||||
&i.Prefix,
|
|
||||||
&i.CreatedAt,
|
|
||||||
&i.UpdatedAt,
|
|
||||||
&i.Status,
|
|
||||||
&i.MainOwnerID,
|
|
||||||
)
|
|
||||||
return &i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const listBots = `-- name: ListBots :many
|
const listBots = `-- name: ListBots :many
|
||||||
SELECT id, username, avatar, overview, description, is_slash, install_context, guild_count, install_count, imported_from, prefix, created_at, updated_at, status, main_owner_id FROM bots
|
SELECT id, username, avatar, overview, description, is_slash, install_context, guild_count, install_count, imported_from, prefix, created_at, updated_at, status, main_owner_id FROM bots
|
||||||
ORDER BY id
|
ORDER BY id
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type InstallContext string
|
type InstallContext string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -41,3 +46,13 @@ func (s BotStatus) IsValid() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CoOwnerSlice []User
|
||||||
|
|
||||||
|
func (c *CoOwnerSlice) Scan(src any) error {
|
||||||
|
b, ok := src.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expected []byte, got %T", src)
|
||||||
|
}
|
||||||
|
return json.Unmarshal(b, c)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ type Querier interface {
|
||||||
DeleteBot(ctx context.Context, id string) error
|
DeleteBot(ctx context.Context, id string) error
|
||||||
DeleteUser(ctx context.Context, id string) error
|
DeleteUser(ctx context.Context, id string) error
|
||||||
GetBot(ctx context.Context, id string) (*Bot, error)
|
GetBot(ctx context.Context, id string) (*Bot, error)
|
||||||
GetBotByUsername(ctx context.Context, username string) (*Bot, error)
|
|
||||||
GetBotCoOwner(ctx context.Context, arg GetBotCoOwnerParams) (*BotCoOwner, error)
|
GetBotCoOwner(ctx context.Context, arg GetBotCoOwnerParams) (*BotCoOwner, error)
|
||||||
GetUser(ctx context.Context, id string) (*User, error)
|
GetUser(ctx context.Context, id string) (*User, error)
|
||||||
GetUserByUsername(ctx context.Context, username string) (*User, error)
|
GetUserByUsername(ctx context.Context, username string) (*User, error)
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@
|
||||||
SELECT * FROM bots
|
SELECT * FROM bots
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: GetBotByUsername :one
|
|
||||||
SELECT * FROM bots
|
|
||||||
WHERE username = $1;
|
|
||||||
|
|
||||||
-- name: CreateBot :one
|
-- name: CreateBot :one
|
||||||
INSERT INTO bots (
|
INSERT INTO bots (
|
||||||
id,
|
id,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ var (
|
||||||
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")
|
||||||
ErrMainOwnerAsCoOwner = errors.New("you cannot set yourself as a co-owner")
|
ErrMainOwnerAsCoOwner = errors.New("you cannot set yourself as a co-owner")
|
||||||
|
ErrBotNotExists = errors.New("bot does not exist inside Discord")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrResponse struct {
|
type ErrResponse struct {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
|
|
@ -24,17 +23,17 @@ func AuthMiddleware(q *db.Queries) func(http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := q.GetUser(r.Context(), mainUserID)
|
user, err := q.GetUser(r.Context(), mainUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
user, err = q.CreateUser(r.Context(), db.CreateUserParams{
|
user, _ = q.CreateUser(r.Context(), db.CreateUserParams{
|
||||||
ID: mainUserID,
|
ID: mainUserID,
|
||||||
Username: "elisiei",
|
Username: "elisiei",
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
slog.Error("eeeehhh", "err", err)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q.CreateUser(r.Context(), db.CreateUserParams{
|
||||||
|
ID: "740358234002686004",
|
||||||
|
Username: "ulysses_ck",
|
||||||
|
}) //test
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), userKey, user) // mocked
|
ctx := context.WithValue(r.Context(), userKey, user) // mocked
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"codeberg.org/nextgo/dbots/internal/admin"
|
"codeberg.org/nextgo/dbots/internal/config"
|
||||||
"codeberg.org/nextgo/dbots/internal/bot"
|
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
customMiddlewares "codeberg.org/nextgo/dbots/internal/middleware"
|
customMiddlewares "codeberg.org/nextgo/dbots/internal/middleware"
|
||||||
|
"codeberg.org/nextgo/dbots/services/admin"
|
||||||
|
"codeberg.org/nextgo/dbots/services/bot"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
@ -19,25 +20,28 @@ import (
|
||||||
type Server struct {
|
type Server struct {
|
||||||
router *chi.Mux
|
router *chi.Mux
|
||||||
queries *db.Queries
|
queries *db.Queries
|
||||||
|
config config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(queries *db.Queries) *Server {
|
func NewServer(queries *db.Queries, config config.Config) *Server {
|
||||||
router := chi.NewMux()
|
router := chi.NewMux()
|
||||||
|
|
||||||
router.Use(httplog.RequestLogger(slog.Default(), &httplog.Options{}))
|
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)
|
||||||
router.Use(customMiddlewares.AuthMiddleware(queries))
|
router.Use(customMiddlewares.AuthMiddleware(queries)) // todo: use this middleware only when necessary
|
||||||
|
// i am using this globally cus it uses mocked data lol
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
router: router,
|
router: router,
|
||||||
queries: queries,
|
queries: queries,
|
||||||
|
config: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Register() {
|
func (s *Server) Register() {
|
||||||
botRouter := bot.NewRouter(s.queries)
|
botRouter := bot.NewRouter(s.queries, s.config)
|
||||||
adminRouter := admin.NewRouter(s.queries)
|
adminRouter := admin.NewRouter(s.queries)
|
||||||
|
|
||||||
s.router.Mount("/bots", botRouter.Routes())
|
s.router.Mount("/bots", botRouter.Routes())
|
||||||
|
|
|
||||||
18
main.go
18
main.go
|
|
@ -5,33 +5,21 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/config"
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
"codeberg.org/nextgo/dbots/internal/server"
|
"codeberg.org/nextgo/dbots/internal/server"
|
||||||
"codeberg.org/ungo/env"
|
|
||||||
"codeberg.org/ungo/env/dotenv"
|
|
||||||
"codeberg.org/ungo/gonsole"
|
"codeberg.org/ungo/gonsole"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
DatabaseURL string `env:"DATABASE_URL,required"`
|
|
||||||
Port int `env:"PORT,default=8080"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var config Config
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gonsoleHandler := gonsole.New(os.Stdout, slog.LevelDebug)
|
gonsoleHandler := gonsole.New(os.Stdout, slog.LevelDebug)
|
||||||
slogHandler := slog.New(gonsoleHandler)
|
slogHandler := slog.New(gonsoleHandler)
|
||||||
slog.SetDefault(slogHandler)
|
slog.SetDefault(slogHandler)
|
||||||
|
|
||||||
dotenv.MustLoad()
|
|
||||||
if err := env.Load(&config); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
config := config.LoadConfig()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
conn, err := pgxpool.New(ctx, config.DatabaseURL)
|
conn, err := pgxpool.New(ctx, config.DatabaseURL)
|
||||||
|
|
@ -41,7 +29,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
queries := db.New(conn)
|
queries := db.New(conn)
|
||||||
server := server.NewServer(queries)
|
server := server.NewServer(queries, config)
|
||||||
|
|
||||||
server.Start(config.Port)
|
server.Start(config.Port)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,22 @@ func NewService(q *db.Queries) *Service {
|
||||||
return &Service{q: q}
|
return &Service{q: q}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateStatus(ctx context.Context, id string, status db.BotStatus) (*db.Bot, error) {
|
||||||
|
bot, err := s.q.UpdateBotStatus(ctx, db.UpdateBotStatusParams{
|
||||||
|
ID: id,
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return nil, errorutil.ErrNotFound.Err
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bot, 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) {
|
||||||
bot, err := s.q.GetBot(ctx, id)
|
bot, err := s.q.GetBot(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
20
services/admin/input.go
Normal file
20
services/admin/input.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateBotStatusRequest struct {
|
||||||
|
NewStatus db.BotStatus `json:"new_status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UpdateBotStatusRequest) Bind(req *http.Request) error {
|
||||||
|
if !c.NewStatus.IsValid() {
|
||||||
|
return fmt.Errorf("'%s' is not a valid bot status", c.NewStatus.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"codeberg.org/nextgo/dbots/internal/db"
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
|
|
@ -30,12 +31,36 @@ func (r *Router) Routes() http.Handler {
|
||||||
router.Route("/{botID}", func(b chi.Router) {
|
router.Route("/{botID}", func(b chi.Router) {
|
||||||
b.Use(middleware.BotContext(r.admin.q))
|
b.Use(middleware.BotContext(r.admin.q))
|
||||||
b.Get("/", r.getBot)
|
b.Get("/", r.getBot)
|
||||||
|
b.Post("/status", r.updateStatus)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return r.router
|
return r.router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) updateStatus(w http.ResponseWriter, req *http.Request) {
|
||||||
|
var data UpdateBotStatusRequest
|
||||||
|
ctx := req.Context()
|
||||||
|
bot := middleware.GetBot(ctx)
|
||||||
|
|
||||||
|
if err := render.Bind(req, &data); err != nil {
|
||||||
|
render.Render(w, req, errorutil.ErrInvalidRequest(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedBot, err := r.admin.UpdateStatus(ctx, bot.ID, data.NewStatus)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errorutil.ErrNotFound.Err) {
|
||||||
|
render.Render(w, req, errorutil.ErrNotFound)
|
||||||
|
} else {
|
||||||
|
render.Render(w, req, errorutil.ErrInternal(err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(w, req, updatedBot)
|
||||||
|
}
|
||||||
|
|
||||||
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 := middleware.GetBot(ctx)
|
||||||
|
|
@ -2,11 +2,15 @@ package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/config"
|
||||||
"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"
|
||||||
|
|
@ -15,16 +19,76 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type DiscordApiBot struct {
|
||||||
q *db.Queries
|
Application DiscordApplication `json:"application"`
|
||||||
|
Bot DiscordBot `json:"bot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(q *db.Queries) *Service {
|
type DiscordApplication struct {
|
||||||
return &Service{q: q}
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsVerified bool `json:"is_verified"`
|
||||||
|
BotPublic bool `json:"bot_public"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscordBot struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Bot bool `json:"bot"`
|
||||||
|
ApproximateGuildCount int32 `json:"approximate_guild_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
q *db.Queries
|
||||||
|
c config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(q *db.Queries, c config.Config) *Service {
|
||||||
|
return &Service{q: q, c: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: finish
|
||||||
|
func (s *Service) GetDiscordStats(id string) (*DiscordApiBot, error) {
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("https://discord.com/api/v10/oauth2/authorize?client_id=%s&scope=bot", id),
|
||||||
|
nil,
|
||||||
|
) // todo: since this method is a bit "illegal", i should move to rpc endpoint (which does not provide guild count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", s.c.DiscordToken)
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bot DiscordApiBot
|
||||||
|
if err = json.Unmarshal(body, &bot); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bot, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
apiBot, err := s.GetDiscordStats(data.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorutil.ErrBotNotExists
|
||||||
|
}
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
b, err := s.q.CreateBot(ctx, db.CreateBotParams{
|
b, err := s.q.CreateBot(ctx, db.CreateBotParams{
|
||||||
|
|
@ -33,10 +97,10 @@ func (s *Service) Submit(ctx context.Context, data CreateBotRequest) (*db.Bot, e
|
||||||
IsSlash: data.IsSlash,
|
IsSlash: data.IsSlash,
|
||||||
InstallContext: data.InstallContext,
|
InstallContext: data.InstallContext,
|
||||||
ImportedFrom: nil,
|
ImportedFrom: nil,
|
||||||
Username: data.Username,
|
Username: apiBot.Bot.Username,
|
||||||
Avatar: &data.Avatar,
|
Avatar: &apiBot.Bot.Avatar,
|
||||||
Description: &data.Description,
|
Description: &data.Description,
|
||||||
GuildCount: &count,
|
GuildCount: &apiBot.Bot.ApproximateGuildCount,
|
||||||
InstallCount: &count,
|
InstallCount: &count,
|
||||||
Prefix: data.Prefix,
|
Prefix: data.Prefix,
|
||||||
MainOwnerID: &user.ID,
|
MainOwnerID: &user.ID,
|
||||||
|
|
@ -54,29 +118,14 @@ func (s *Service) Submit(ctx context.Context, data CreateBotRequest) (*db.Bot, e
|
||||||
// Bot was created but co-owners failed. Log and surface the error.
|
// Bot was created but co-owners failed. Log and surface the error.
|
||||||
// ideally this whole operation runs in a transaction but who cares lol.
|
// 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)
|
slog.Error("error adding co-owners after submit", "err", err, "bot_id", b.ID)
|
||||||
return nil, err
|
// so if co-owners fail it's because the co-owner isn't registered in the botlist,
|
||||||
|
// however, i should handle this but i'm lazy as fuck. for now i will only output
|
||||||
|
// a silent error log instead of aborting the whole request.
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// AddCoOwner adds a single co-owner to a bot.
|
||||||
// Only the main owner of the bot may call this.
|
// Only the main owner of the bot may call this.
|
||||||
func (s *Service) AddCoOwner(ctx context.Context, botID, coOwnerID string) error {
|
func (s *Service) AddCoOwner(ctx context.Context, botID, coOwnerID string) error {
|
||||||
|
|
@ -141,18 +190,6 @@ func (s *Service) ListCoOwners(ctx context.Context, botID string) ([]*db.User, e
|
||||||
return owners, nil
|
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) {
|
||||||
bot, err := s.q.GetBot(ctx, id)
|
bot, err := s.q.GetBot(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -193,3 +230,32 @@ func (s *Service) List(
|
||||||
|
|
||||||
return paginate.NewPage(bots, int(total), p), nil
|
return paginate.NewPage(bots, int(total), p), 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
@ -10,8 +10,6 @@ import (
|
||||||
|
|
||||||
type CreateBotRequest struct {
|
type CreateBotRequest struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Username string `json:"username"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
Overview string `json:"overview"`
|
Overview string `json:"overview"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
IsSlash bool `json:"is_slash"`
|
IsSlash bool `json:"is_slash"`
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/config"
|
||||||
"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"
|
||||||
|
|
@ -18,9 +19,9 @@ type Router struct {
|
||||||
router chi.Router
|
router chi.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(q *db.Queries) *Router {
|
func NewRouter(q *db.Queries, c config.Config) *Router {
|
||||||
return &Router{
|
return &Router{
|
||||||
bots: NewService(q),
|
bots: NewService(q, c),
|
||||||
router: chi.NewRouter(),
|
router: chi.NewRouter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue