feat(backend): add sentry integration

This commit is contained in:
sam 2023-09-20 02:39:14 +02:00
parent a6d31d150c
commit b04ed68832
No known key found for this signature in database
GPG Key ID: B4EF20DDE721CAA1
19 changed files with 130 additions and 90 deletions

View File

@ -11,6 +11,7 @@ import (
"codeberg.org/pronounscc/pronouns.cc/backend/server"
"github.com/davidbyttow/govips/v2/vips"
"github.com/getsentry/sentry-go"
"github.com/go-chi/render"
_ "github.com/joho/godotenv/autoload"
"github.com/urfave/cli/v2"
@ -23,6 +24,14 @@ var Command = &cli.Command{
}
func run(c *cli.Context) error {
// initialize sentry
if dsn := os.Getenv("SENTRY_DSN"); dsn != "" {
sentry.Init(sentry.ClientOptions{
Dsn: dsn,
Release: server.Tag,
})
}
// set vips log level to WARN, else it will spam logs on info level
vips.LoggingSettings(nil, vips.LogLevelWarning)

View File

@ -61,7 +61,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
// if the state can't be validated, return
if valid, err := s.validateCSRFState(ctx, decoded.State); !valid {
if err != nil {
return err
return errors.Wrap(err, "validating state")
}
return server.APIError{Code: server.ErrInvalidState}
@ -79,7 +79,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
dg, _ := discordgo.New(token.Type() + " " + token.AccessToken)
du, err := dg.User("@me")
if err != nil {
return err
return errors.Wrap(err, "getting discord user")
}
u, err := s.DB.DiscordUser(ctx, du.ID)
@ -90,7 +90,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
err = s.saveUndeleteToken(ctx, u.ID, token)
if err != nil {
log.Errorf("saving undelete token: %v", err)
return err
return errors.Wrap(err, "saving undelete token")
}
render.JSON(w, r, discordCallbackResponse{
@ -114,7 +114,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
tokenID := xid.New()
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
if err != nil {
return err
return errors.Wrap(err, "creating token")
}
// save token to database
@ -137,7 +137,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
return nil
} else if err != db.ErrUserNotFound { // internal error
return err
return errors.Wrap(err, "getting user")
}
// no user found, so save a ticket + save their Discord info in Redis
@ -145,7 +145,7 @@ func (s *Server) discordCallback(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetJSON(ctx, "discord:"+ticket, du, "EX", "600")
if err != nil {
log.Errorf("setting Discord user for ticket %q: %v", ticket, err)
return err
return errors.Wrap(err, "caching discord user for ticket")
}
render.JSON(w, r, discordCallbackResponse{
@ -278,7 +278,7 @@ func (s *Server) discordSignup(w http.ResponseWriter, r *http.Request) error {
valid, taken, err := s.DB.UsernameTaken(ctx, req.Username)
if err != nil {
return err
return errors.Wrap(err, "checking if username is taken")
}
if !valid {
return server.APIError{Code: server.ErrInvalidUsername}

View File

@ -54,7 +54,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
// if the state can't be validated, return
if valid, err := s.validateCSRFState(ctx, decoded.State); !valid {
if err != nil {
return err
return errors.Wrap(err, "validating state")
}
return server.APIError{Code: server.ErrInvalidState}
@ -111,7 +111,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
err = s.saveUndeleteToken(ctx, u.ID, token)
if err != nil {
log.Errorf("saving undelete token: %v", err)
return err
return errors.Wrap(err, "saving undelete token")
}
render.JSON(w, r, fediCallbackResponse{
@ -135,7 +135,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
tokenID := xid.New()
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
if err != nil {
return err
return errors.Wrap(err, "creating token")
}
// save token to database
@ -158,7 +158,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
return nil
} else if err != db.ErrUserNotFound { // internal error
return err
return errors.Wrap(err, "getting user")
}
// no user found, so save a ticket + save their Mastodon info in Redis
@ -166,7 +166,7 @@ func (s *Server) mastodonCallback(w http.ResponseWriter, r *http.Request) error
err = s.DB.SetJSON(ctx, "mastodon:"+ticket, mu, "EX", "600")
if err != nil {
log.Errorf("setting mastoAPI user for ticket %q: %v", ticket, err)
return err
return errors.Wrap(err, "setting user for ticket")
}
render.JSON(w, r, fediCallbackResponse{
@ -306,7 +306,7 @@ func (s *Server) mastodonSignup(w http.ResponseWriter, r *http.Request) error {
valid, taken, err := s.DB.UsernameTaken(ctx, req.Username)
if err != nil {
return err
return errors.Wrap(err, "checking if username is taken")
}
if !valid {
return server.APIError{Code: server.ErrInvalidUsername}

View File

@ -90,7 +90,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
err = s.saveUndeleteToken(ctx, u.ID, token)
if err != nil {
log.Errorf("saving undelete token: %v", err)
return err
return errors.Wrap(err, "saving undelete token")
}
render.JSON(w, r, fediCallbackResponse{
@ -114,7 +114,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
tokenID := xid.New()
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
if err != nil {
return err
return errors.Wrap(err, "creating token")
}
// save token to database
@ -137,7 +137,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
return nil
} else if err != db.ErrUserNotFound { // internal error
return err
return errors.Wrap(err, "getting user")
}
// no user found, so save a ticket + save their Misskey info in Redis
@ -145,7 +145,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetJSON(ctx, "misskey:"+ticket, mu.User, "EX", "600")
if err != nil {
log.Errorf("setting misskey user for ticket %q: %v", ticket, err)
return err
return errors.Wrap(err, "setting user for ticket")
}
render.JSON(w, r, fediCallbackResponse{
@ -234,7 +234,7 @@ func (s *Server) misskeySignup(w http.ResponseWriter, r *http.Request) error {
valid, taken, err := s.DB.UsernameTaken(ctx, req.Username)
if err != nil {
return err
return errors.Wrap(err, "checking if username is taken")
}
if !valid {
return server.APIError{Code: server.ErrInvalidUsername}

View File

@ -60,7 +60,7 @@ func (s *Server) googleCallback(w http.ResponseWriter, r *http.Request) error {
// if the state can't be validated, return
if valid, err := s.validateCSRFState(ctx, decoded.State); !valid {
if err != nil {
return err
return errors.Wrap(err, "validating state")
}
return server.APIError{Code: server.ErrInvalidState}
@ -109,7 +109,7 @@ func (s *Server) googleCallback(w http.ResponseWriter, r *http.Request) error {
err = s.saveUndeleteToken(ctx, u.ID, token)
if err != nil {
log.Errorf("saving undelete token: %v", err)
return err
return errors.Wrap(err, "saving undelete token")
}
render.JSON(w, r, googleCallbackResponse{
@ -133,7 +133,7 @@ func (s *Server) googleCallback(w http.ResponseWriter, r *http.Request) error {
tokenID := xid.New()
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
if err != nil {
return err
return errors.Wrap(err, "creating token")
}
// save token to database
@ -156,7 +156,7 @@ func (s *Server) googleCallback(w http.ResponseWriter, r *http.Request) error {
return nil
} else if err != db.ErrUserNotFound { // internal error
return err
return errors.Wrap(err, "getting user")
}
// no user found, so save a ticket + save their Google info in Redis
@ -164,7 +164,7 @@ func (s *Server) googleCallback(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetJSON(ctx, "google:"+ticket, partialGoogleUser{ID: googleID, Email: googleUsername}, "EX", "600")
if err != nil {
log.Errorf("setting Google user for ticket %q: %v", ticket, err)
return err
return errors.Wrap(err, "setting user for ticket")
}
render.JSON(w, r, googleCallbackResponse{
@ -281,7 +281,7 @@ func (s *Server) googleSignup(w http.ResponseWriter, r *http.Request) error {
valid, taken, err := s.DB.UsernameTaken(ctx, req.Username)
if err != nil {
return err
return errors.Wrap(err, "checking if username is taken")
}
if !valid {
return server.APIError{Code: server.ErrInvalidUsername}

View File

@ -77,7 +77,7 @@ func (s *Server) tumblrCallback(w http.ResponseWriter, r *http.Request) error {
// if the state can't be validated, return
if valid, err := s.validateCSRFState(ctx, decoded.State); !valid {
if err != nil {
return err
return errors.Wrap(err, "validating state")
}
return server.APIError{Code: server.ErrInvalidState}
@ -142,7 +142,7 @@ func (s *Server) tumblrCallback(w http.ResponseWriter, r *http.Request) error {
err = s.saveUndeleteToken(ctx, u.ID, token)
if err != nil {
log.Errorf("saving undelete token: %v", err)
return err
return errors.Wrap(err, "saving undelete token")
}
render.JSON(w, r, tumblrCallbackResponse{
@ -166,7 +166,7 @@ func (s *Server) tumblrCallback(w http.ResponseWriter, r *http.Request) error {
tokenID := xid.New()
token, err := s.Auth.CreateToken(u.ID, tokenID, u.IsAdmin, false, true)
if err != nil {
return err
return errors.Wrap(err, "creating token")
}
// save token to database
@ -189,7 +189,7 @@ func (s *Server) tumblrCallback(w http.ResponseWriter, r *http.Request) error {
return nil
} else if err != db.ErrUserNotFound { // internal error
return err
return errors.Wrap(err, "getting user")
}
// no user found, so save a ticket + save their Tumblr info in Redis
@ -197,7 +197,7 @@ func (s *Server) tumblrCallback(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetJSON(ctx, "tumblr:"+ticket, tumblrUserInfo{ID: tumblrID, Name: tumblrName}, "EX", "600")
if err != nil {
log.Errorf("setting Tumblr user for ticket %q: %v", ticket, err)
return err
return errors.Wrap(err, "setting user for ticket")
}
render.JSON(w, r, tumblrCallbackResponse{
@ -314,7 +314,7 @@ func (s *Server) tumblrSignup(w http.ResponseWriter, r *http.Request) error {
valid, taken, err := s.DB.UsernameTaken(ctx, req.Username)
if err != nil {
return err
return errors.Wrap(err, "checking if username is taken")
}
if !valid {
return server.APIError{Code: server.ErrInvalidUsername}

View File

@ -127,14 +127,14 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
return server.APIError{Code: server.ErrMemberNameInUse}
}
return err
return errors.Wrap(err, "creating member")
}
// set names, pronouns, fields
err = s.DB.SetMemberNamesPronouns(ctx, tx, m.ID, db.NotNull(cmr.Names), db.NotNull(cmr.Pronouns))
if err != nil {
log.Errorf("setting names and pronouns for member %v: %v", m.ID, err)
return err
return errors.Wrap(err, "setting names/pronouns")
}
m.Names = cmr.Names
m.Pronouns = cmr.Pronouns
@ -142,7 +142,7 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
err = s.DB.SetMemberFields(ctx, tx, m.ID, cmr.Fields)
if err != nil {
log.Errorf("setting fields for member %v: %v", m.ID, err)
return err
return errors.Wrap(err, "setting fields")
}
if cmr.Avatar != "" {
@ -161,13 +161,13 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
}
log.Errorf("converting member avatar: %v", err)
return err
return errors.Wrap(err, "converting avatar")
}
hash, err := s.DB.WriteMemberAvatar(ctx, m.ID, webp, jpg)
if err != nil {
log.Errorf("uploading member avatar: %v", err)
return err
return errors.Wrap(err, "uploading avatar")
}
err = tx.QueryRow(ctx, "UPDATE members SET avatar = $1 WHERE id = $2", hash, m.ID).Scan(&m.Avatar)
@ -180,7 +180,7 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
err = s.DB.UpdateActiveTime(ctx, tx, claims.UserID)
if err != nil {
log.Errorf("updating last active time for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "updating last active time")
}
err = tx.Commit(ctx)

View File

@ -66,7 +66,7 @@ func (s *Server) deleteMember(w http.ResponseWriter, r *http.Request) (err error
err = s.DB.UpdateActiveTime(ctx, s.DB, claims.UserID)
if err != nil {
log.Errorf("updating last active time for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "updating last active time")
}
render.NoContent(w, r)

View File

@ -105,7 +105,7 @@ func (s *Server) getMember(w http.ResponseWriter, r *http.Request) (err error) {
u, err := s.DB.User(ctx, m.UserID)
if err != nil {
return err
return errors.Wrap(err, "getting user")
}
if u.DeletedAt != nil {
@ -119,12 +119,12 @@ func (s *Server) getMember(w http.ResponseWriter, r *http.Request) (err error) {
fields, err := s.DB.MemberFields(ctx, m.ID)
if err != nil {
return err
return errors.Wrap(err, "getting member fields")
}
flags, err := s.DB.MemberFlags(ctx, m.ID)
if err != nil {
return err
return errors.Wrap(err, "getting member flags")
}
render.JSON(w, r, dbMemberToMember(u, m, fields, flags, isOwnMember))
@ -159,12 +159,12 @@ func (s *Server) getUserMember(w http.ResponseWriter, r *http.Request) error {
fields, err := s.DB.MemberFields(ctx, m.ID)
if err != nil {
return err
return errors.Wrap(err, "getting member fields")
}
flags, err := s.DB.MemberFlags(ctx, m.ID)
if err != nil {
return err
return errors.Wrap(err, "getting member flags")
}
render.JSON(w, r, dbMemberToMember(u, m, fields, flags, isOwnMember))
@ -189,12 +189,12 @@ func (s *Server) getMeMember(w http.ResponseWriter, r *http.Request) error {
fields, err := s.DB.MemberFields(ctx, m.ID)
if err != nil {
return err
return errors.Wrap(err, "getting member fields")
}
flags, err := s.DB.MemberFlags(ctx, m.ID)
if err != nil {
return err
return errors.Wrap(err, "getting member flags")
}
render.JSON(w, r, dbMemberToMember(u, m, fields, flags, true))

View File

@ -6,6 +6,7 @@ import (
"codeberg.org/pronounscc/pronouns.cc/backend/common"
"codeberg.org/pronounscc/pronouns.cc/backend/db"
"codeberg.org/pronounscc/pronouns.cc/backend/server"
"emperror.dev/errors"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/rs/xid"
@ -74,7 +75,7 @@ func (s *Server) getUserMembers(w http.ResponseWriter, r *http.Request) error {
ms, err := s.DB.UserMembers(ctx, u.ID, isSelf)
if err != nil {
return err
return errors.Wrap(err, "getting members")
}
render.JSON(w, r, membersToMemberList(ms, isSelf))
@ -87,7 +88,7 @@ func (s *Server) getMeMembers(w http.ResponseWriter, r *http.Request) error {
ms, err := s.DB.UserMembers(ctx, claims.UserID, true)
if err != nil {
return err
return errors.Wrap(err, "getting members")
}
render.JSON(w, r, membersToMemberList(ms, true))

View File

@ -220,13 +220,13 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
}
log.Errorf("converting member avatar: %v", err)
return err
return errors.Wrap(err, "converting member avatar")
}
hash, err := s.DB.WriteMemberAvatar(ctx, m.ID, webp, jpg)
if err != nil {
log.Errorf("uploading member avatar: %v", err)
return err
return errors.Wrap(err, "writing member avatar")
}
avatarHash = &hash
@ -244,7 +244,7 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
tx, err := s.DB.Begin(ctx)
if err != nil {
log.Errorf("creating transaction: %v", err)
return err
return errors.Wrap(err, "creating transaction")
}
defer tx.Rollback(ctx)
@ -275,7 +275,7 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetMemberNamesPronouns(ctx, tx, m.ID, names, pronouns)
if err != nil {
log.Errorf("setting names for member %v: %v", m.ID, err)
return err
return errors.Wrap(err, "setting names/pronouns")
}
m.Names = names
m.Pronouns = pronouns
@ -286,14 +286,14 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetMemberFields(ctx, tx, m.ID, *req.Fields)
if err != nil {
log.Errorf("setting fields for member %v: %v", m.ID, err)
return err
return errors.Wrap(err, "setting fields")
}
fields = *req.Fields
} else {
fields, err = s.DB.MemberFields(ctx, m.ID)
if err != nil {
log.Errorf("getting fields for member %v: %v", m.ID, err)
return err
return errors.Wrap(err, "getting fields")
}
}
@ -306,7 +306,7 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
}
log.Errorf("updating flags for member %v: %v", m.ID, err)
return err
return errors.Wrap(err, "updating flags")
}
}
@ -314,20 +314,20 @@ func (s *Server) patchMember(w http.ResponseWriter, r *http.Request) error {
err = s.DB.UpdateActiveTime(ctx, tx, claims.UserID)
if err != nil {
log.Errorf("updating last active time for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "updating last active time")
}
err = tx.Commit(ctx)
if err != nil {
log.Errorf("committing transaction: %v", err)
return err
return errors.Wrap(err, "committing transaction")
}
// get flags to return (we need to return full flag objects, not the array of IDs in the request body)
flags, err := s.DB.MemberFlags(ctx, m.ID)
if err != nil {
log.Errorf("getting user flags: %v", err)
return err
return errors.Wrap(err, "getting flags")
}
// echo the updated member back on success

View File

@ -7,6 +7,7 @@ import (
"codeberg.org/pronounscc/pronouns.cc/backend/db"
"codeberg.org/pronounscc/pronouns.cc/backend/log"
"codeberg.org/pronounscc/pronouns.cc/backend/server"
"emperror.dev/errors"
"github.com/go-chi/render"
)
@ -71,7 +72,7 @@ func (s *Server) getExport(w http.ResponseWriter, r *http.Request) error {
}
log.Errorf("getting export for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "getting export")
}
render.JSON(w, r, dataExportResponse{

View File

@ -8,6 +8,7 @@ import (
"codeberg.org/pronounscc/pronouns.cc/backend/db"
"codeberg.org/pronounscc/pronouns.cc/backend/log"
"codeberg.org/pronounscc/pronouns.cc/backend/server"
"emperror.dev/errors"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/rs/xid"
@ -146,7 +147,7 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) (err error) {
}
} else if err != nil {
log.Errorf("Error getting user by username: %v", err)
return err
return errors.Wrap(err, "getting user")
}
}
@ -162,13 +163,13 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) (err error) {
fields, err := s.DB.UserFields(ctx, u.ID)
if err != nil {
log.Errorf("Error getting user fields: %v", err)
return err
return errors.Wrap(err, "getting fields")
}
flags, err := s.DB.UserFlags(ctx, u.ID)
if err != nil {
log.Errorf("getting user flags: %v", err)
return err
return errors.Wrap(err, "getting flags")
}
var members []db.Member
@ -176,7 +177,7 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) (err error) {
members, err = s.DB.UserMembers(ctx, u.ID, isSelf)
if err != nil {
log.Errorf("Error getting user members: %v", err)
return err
return errors.Wrap(err, "getting user members")
}
}
@ -191,25 +192,25 @@ func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error {
u, err := s.DB.User(ctx, claims.UserID)
if err != nil {
log.Errorf("Error getting user: %v", err)
return err
return errors.Wrap(err, "getting users")
}
fields, err := s.DB.UserFields(ctx, u.ID)
if err != nil {
log.Errorf("Error getting user fields: %v", err)
return err
return errors.Wrap(err, "getting fields")
}
members, err := s.DB.UserMembers(ctx, u.ID, true)
if err != nil {
log.Errorf("Error getting user members: %v", err)
return err
return errors.Wrap(err, "getting members")
}
flags, err := s.DB.UserFlags(ctx, u.ID)
if err != nil {
log.Errorf("getting user flags: %v", err)
return err
return errors.Wrap(err, "getting flags")
}
render.JSON(w, r, GetMeResponse{

View File

@ -195,13 +195,13 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
}
log.Errorf("converting user avatar: %v", err)
return err
return errors.Wrap(err, "converting avatar")
}
hash, err := s.DB.WriteUserAvatar(ctx, claims.UserID, webp, jpg)
if err != nil {
log.Errorf("uploading user avatar: %v", err)
return err
return errors.Wrap(err, "uploading avatar")
}
avatarHash = &hash
@ -219,7 +219,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
tx, err := s.DB.Begin(ctx)
if err != nil {
log.Errorf("creating transaction: %v", err)
return err
return errors.Wrap(err, "creating transaction")
}
defer tx.Rollback(ctx)
@ -243,7 +243,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
u, err = s.DB.UpdateUser(ctx, tx, claims.UserID, req.DisplayName, req.Bio, req.MemberTitle, req.ListPrivate, req.Links, avatarHash, req.Timezone, req.CustomPreferences)
if err != nil && errors.Cause(err) != db.ErrNothingToUpdate {
log.Errorf("updating user: %v", err)
return err
return errors.Wrap(err, "updating user")
}
if req.Names != nil || req.Pronouns != nil {
@ -260,7 +260,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetUserNamesPronouns(ctx, tx, claims.UserID, names, pronouns)
if err != nil {
log.Errorf("setting names for member %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "setting names/pronouns")
}
u.Names = names
u.Pronouns = pronouns
@ -271,14 +271,14 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
err = s.DB.SetUserFields(ctx, tx, claims.UserID, *req.Fields)
if err != nil {
log.Errorf("setting fields for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "setting fields")
}
fields = *req.Fields
} else {
fields, err = s.DB.UserFields(ctx, claims.UserID)
if err != nil {
log.Errorf("getting fields for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "getting fields")
}
}
@ -291,7 +291,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
}
log.Errorf("updating flags for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "updating flags")
}
}
@ -299,13 +299,13 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
err = s.DB.UpdateActiveTime(ctx, tx, claims.UserID)
if err != nil {
log.Errorf("updating last active time for user %v: %v", claims.UserID, err)
return err
return errors.Wrap(err, "updating last active time")
}
err = tx.Commit(ctx)
if err != nil {
log.Errorf("committing transaction: %v", err)
return err
return errors.Wrap(err, "committing transaction")
}
// get fedi instance name if the user has a linked fedi account
@ -321,7 +321,7 @@ func (s *Server) patchUser(w http.ResponseWriter, r *http.Request) error {
flags, err := s.DB.UserFlags(ctx, u.ID)
if err != nil {
log.Errorf("getting user flags: %v", err)
return err
return errors.Wrap(err, "getting flags")
}
// echo the updated user back on success

View File

@ -5,6 +5,7 @@ import (
"codeberg.org/pronounscc/pronouns.cc/backend/log"
"codeberg.org/pronounscc/pronouns.cc/backend/server"
"emperror.dev/errors"
"github.com/go-chi/render"
)
@ -13,7 +14,7 @@ func (s *Server) GetSettings(w http.ResponseWriter, r *http.Request) (err error)
u, err := s.DB.User(r.Context(), claims.UserID)
if err != nil {
log.Errorf("getting user: %v", err)
return err
return errors.Wrap(err, "getting user")
}
render.JSON(w, r, u.Settings)

View File

@ -1,10 +1,13 @@
package server
import (
"context"
"fmt"
"net/http"
"codeberg.org/pronounscc/pronouns.cc/backend/log"
"github.com/getsentry/sentry-go"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
@ -12,6 +15,17 @@ import (
// The inner HandlerFunc additionally returns an error.
func WrapHandler(hn func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
hub := sentry.CurrentHub().Clone()
defer func(hub *sentry.Hub, r *http.Request) {
if err := recover(); err != nil {
hub.RecoverWithContext(
context.WithValue(r.Context(), sentry.RequestContextKey, r),
err,
)
}
}(hub, r)
err := hn(w, r)
if err != nil {
// if the function returned an API error, just render that verbatim
@ -24,10 +38,16 @@ func WrapHandler(hn func(w http.ResponseWriter, r *http.Request) error) http.Han
return
}
// otherwise, we log the error and return an internal server error message
log.Errorf("error in http handler: %v", err)
rctx := chi.RouteContext(r.Context())
hub.ConfigureScope(func(scope *sentry.Scope) {
scope.SetTag("method", rctx.RouteMethod)
scope.SetTag("path", rctx.RoutePattern())
})
apiErr := APIError{Code: ErrInternalServerError}
log.Errorf("error in handler for %v %v: %v", rctx.RouteMethod, rctx.RoutePattern(), err)
eventID := hub.CaptureException(err)
apiErr := APIError{ID: eventID, Code: ErrInternalServerError}
apiErr.prepare()
render.Status(r, apiErr.Status)
@ -39,9 +59,10 @@ func WrapHandler(hn func(w http.ResponseWriter, r *http.Request) error) http.Han
// APIError is an object returned by the API when an error occurs.
// It implements the error interface and can be returned by handlers.
type APIError struct {
Code int `json:"code"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
Code int `json:"code"`
ID *sentry.EventID `json:"id,omitempty"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
RatelimitReset *int `json:"ratelimit_reset,omitempty"`

View File

@ -2,12 +2,13 @@
If there is an error in your request, or the server encounters an error while processing it, an error object will be returned.
| Field | Type | Description |
| --------------- | ------- | --------------------------------------------------------------- |
| code | int | an [error code](./errors#error-codes) |
| message | ?string | a human-readable description of the error |
| details | ?string | more details about the error, most often for bad request errors |
| ratelimit_reset | ?int | the unix time when an expired rate limit will reset |
| Field | Type | Description |
| --------------- | ------- | ------------------------------------------------------------------- |
| code | int | an [error code](./errors#error-codes) |
| id | ?string | an opaque Sentry event ID, only returned for internal server errors |
| message | ?string | a human-readable description of the error |
| details | ?string | more details about the error, most often for bad request errors |
| ratelimit_reset | ?int | the unix time when an expired rate limit will reset |
### Error codes

1
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/bwmarrin/discordgo v0.27.1
github.com/davidbyttow/govips/v2 v2.13.0
github.com/georgysavva/scany/v2 v2.0.0
github.com/getsentry/sentry-go v0.24.1
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1
github.com/go-chi/httprate v0.7.1

6
go.sum
View File

@ -131,6 +131,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU=
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38=
github.com/getsentry/sentry-go v0.24.1 h1:W6/0GyTy8J6ge6lVCc94WB6Gx2ZuLrgopnn9w8Hiwuk=
github.com/getsentry/sentry-go v0.24.1/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
@ -140,6 +142,7 @@ github.com/go-chi/httprate v0.7.1 h1:d5kXARdms2PREQfU4pHvq44S6hJ1hPu4OXLeBKmCKWs
github.com/go-chi/httprate v0.7.1/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -397,6 +400,7 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -481,8 +485,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
github.com/tilinna/clock v1.1.0 h1:6IQQQCo6KoBxVudv6gwtY8o4eDfhHo8ojA5dP0MfhSs=