2023-03-15 02:04:48 -07:00
|
|
|
package backend
|
2022-05-02 08:19:37 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-09-20 04:02:48 -07:00
|
|
|
"io"
|
2022-05-02 08:19:37 -07:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
|
2023-06-03 07:18:47 -07:00
|
|
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
|
|
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
2022-05-02 08:19:37 -07:00
|
|
|
|
2023-05-10 15:59:40 -07:00
|
|
|
"github.com/davidbyttow/govips/v2/vips"
|
2023-09-19 17:39:14 -07:00
|
|
|
"github.com/getsentry/sentry-go"
|
2022-09-20 04:02:48 -07:00
|
|
|
"github.com/go-chi/render"
|
2022-05-12 07:41:32 -07:00
|
|
|
_ "github.com/joho/godotenv/autoload"
|
2023-03-15 02:04:48 -07:00
|
|
|
"github.com/urfave/cli/v2"
|
2022-05-02 08:19:37 -07:00
|
|
|
)
|
|
|
|
|
2023-03-15 02:04:48 -07:00
|
|
|
var Command = &cli.Command{
|
|
|
|
Name: "web",
|
|
|
|
Usage: "Run the API server",
|
|
|
|
Action: run,
|
|
|
|
}
|
|
|
|
|
|
|
|
func run(c *cli.Context) error {
|
2023-09-19 17:39:14 -07:00
|
|
|
// initialize sentry
|
|
|
|
if dsn := os.Getenv("SENTRY_DSN"); dsn != "" {
|
2023-12-29 19:27:08 -08:00
|
|
|
// We don't need to check the error here--it's fine if no DSN is set.
|
|
|
|
_ = sentry.Init(sentry.ClientOptions{
|
2023-09-19 18:40:07 -07:00
|
|
|
Dsn: dsn,
|
2023-09-19 18:42:45 -07:00
|
|
|
Debug: os.Getenv("DEBUG") == "true",
|
2023-09-19 18:40:07 -07:00
|
|
|
Release: server.Tag,
|
2023-09-20 08:03:12 -07:00
|
|
|
EnableTracing: os.Getenv("SENTRY_TRACING") == "true",
|
2023-09-20 06:28:20 -07:00
|
|
|
TracesSampleRate: 0.05,
|
|
|
|
ProfilesSampleRate: 0.05,
|
2023-09-19 17:39:14 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-05-21 15:36:21 -07:00
|
|
|
// set vips log level to WARN, else it will spam logs on info level
|
|
|
|
vips.LoggingSettings(nil, vips.LogLevelWarning)
|
|
|
|
|
2023-05-10 15:59:40 -07:00
|
|
|
vips.Startup(nil)
|
|
|
|
defer vips.Shutdown()
|
|
|
|
|
2022-05-02 08:19:37 -07:00
|
|
|
port := ":" + os.Getenv("PORT")
|
|
|
|
|
|
|
|
s, err := server.New()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error creating server: %v", err)
|
2023-03-15 02:04:48 -07:00
|
|
|
return nil
|
2022-05-02 08:19:37 -07:00
|
|
|
}
|
|
|
|
|
2022-09-20 04:02:48 -07:00
|
|
|
// set render.Decode to a custom one that checks content length
|
|
|
|
render.Decode = decode
|
|
|
|
|
2022-05-02 08:19:37 -07:00
|
|
|
// mount api routes
|
|
|
|
mountRoutes(s)
|
|
|
|
|
|
|
|
e := make(chan error)
|
|
|
|
|
|
|
|
// run server in another goroutine (for gracefully shutting down, see below)
|
|
|
|
go func() {
|
2022-07-11 13:35:20 -07:00
|
|
|
e <- http.ListenAndServe(port, s.Router)
|
2022-05-02 08:19:37 -07:00
|
|
|
}()
|
|
|
|
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
|
|
defer stop()
|
|
|
|
|
|
|
|
log.Infof("API server running at %v!", port)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
log.Info("Interrupt signal received, shutting down...")
|
|
|
|
s.DB.Close()
|
2023-03-15 02:04:48 -07:00
|
|
|
return nil
|
2022-05-02 08:19:37 -07:00
|
|
|
case err := <-e:
|
|
|
|
log.Fatalf("Error running server: %v", err)
|
2023-09-06 16:43:05 -07:00
|
|
|
return err
|
2022-05-02 08:19:37 -07:00
|
|
|
}
|
|
|
|
}
|
2022-09-20 04:02:48 -07:00
|
|
|
|
|
|
|
const MaxContentLength = 2 * 1024 * 1024
|
|
|
|
|
|
|
|
// decode is a custom render.Decode function that makes sure the request doesn't exceed 2 megabytes.
|
|
|
|
func decode(r *http.Request, v any) error {
|
|
|
|
if r.ContentLength > MaxContentLength {
|
|
|
|
return server.APIError{
|
|
|
|
Code: server.ErrRequestTooBig,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this is copied from render.Decode to replace r.Body with an io.LimitedReader
|
|
|
|
var err error
|
|
|
|
lr := io.LimitReader(r.Body, MaxContentLength)
|
|
|
|
|
|
|
|
switch render.GetRequestContentType(r) {
|
|
|
|
case render.ContentTypeJSON:
|
|
|
|
err = render.DecodeJSON(lr, v)
|
|
|
|
default:
|
|
|
|
err = server.APIError{
|
|
|
|
Code: server.ErrBadRequest,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|