feat: consistent internal error logging

This commit is contained in:
ae 2025-05-04 14:07:23 +03:00
parent 0e9a221728
commit f2e36b09f2
Signed by: ae
GPG Key ID: 995EFD5C1B532B3E
3 changed files with 29 additions and 4 deletions

View File

@ -175,6 +175,7 @@ func (rs authResource) Create(w http.ResponseWriter, r *http.Request) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
if err != nil {
log.Error().Err(err).Msg("Failed to create user")
respondError(w, http.StatusInternalServerError, "Failed to create user")
return
}
@ -187,11 +188,14 @@ func (rs authResource) Create(w http.ResponseWriter, r *http.Request) {
if isDuplicateEntry(err) {
respondError(w, http.StatusConflict, "Username is already in use")
} else {
log.Error().Err(err).Msg("Failed to create user")
respondError(w, http.StatusInternalServerError, "Failed to create user")
}
return
}
log.Info().Msgf("User account '%s' created", user.Username)
respondJSON(w, http.StatusCreated, map[string]string{
"id": user.ID.String(),
"username": user.Username,
@ -235,6 +239,7 @@ func (rs authResource) Login(w http.ResponseWriter, r *http.Request) {
// the database for further token rotations.
tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin)
if err != nil {
log.Error().Err(err).Msg("Failed to generate tokens")
respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return
}
@ -310,6 +315,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
log.Error().Err(err).Msg("Failed to update password")
respondError(w, http.StatusInternalServerError, "Failed to update password")
return
}
@ -319,6 +325,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
PasswordHash: string(hashedPassword),
})
if err != nil {
log.Error().Err(err).Msg("Failed to update password")
respondError(w, http.StatusInternalServerError, "Failed to update password")
return
}
@ -331,6 +338,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
// Generate a new pair (access & refresh tokens)
tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin)
if err != nil {
log.Error().Err(err).Msg("Failed to generate tokens")
respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return
}
@ -377,6 +385,7 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) {
err := rs.Users.DeleteUser(r.Context(), user.ID)
if err != nil {
log.Error().Err(err).Msg("Failed to delete user")
respondError(w, http.StatusInternalServerError, "Failed to delete user")
return
}
@ -385,9 +394,11 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) {
rs.setAuthCookies(w, nil, true)
if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil {
log.Error().Msgf("Failed to revoke refresh tokens: %s", err)
log.Warn().Err(err).Msg("Failed to revoke refresh tokens")
}
log.Info().Msgf("User account '%s' deleted by the owner", user.Username)
w.WriteHeader(http.StatusNoContent)
}
@ -400,6 +411,7 @@ func (rs authResource) List(w http.ResponseWriter, r *http.Request) {
Offset: offset,
})
if err != nil {
log.Error().Err(err).Msg("Failed to retrieve users")
respondError(w, http.StatusInternalServerError, "Failed to retrieve users")
return
}
@ -431,6 +443,7 @@ func (rs authResource) AdminDelete(w http.ResponseWriter, r *http.Request) {
}
if err := rs.Users.DeleteUser(r.Context(), targetID); err != nil {
log.Error().Err(err).Msg("Failed to delete user")
respondError(w, http.StatusInternalServerError, "Failed to delete user")
return
}
@ -439,6 +452,8 @@ func (rs authResource) AdminDelete(w http.ResponseWriter, r *http.Request) {
log.Error().Msgf("Failed to revoke refresh tokens: %s", err)
}
log.Info().Msgf("User account '%s' deleted by an admin", targetID)
w.WriteHeader(http.StatusNoContent)
}
@ -529,6 +544,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request
// Revoke the given (single use) refresh token
if err := rs.RevokeRefreshToken(r.Context(), refreshToken); err != nil {
log.Error().Err(err).Msg("Failed to revoke token")
respondError(w, http.StatusInternalServerError, "Failed to revoke token")
return
}
@ -542,6 +558,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request
// Generate a new pair (access & refresh tokens)
tokenPair, err := rs.GenerateTokenPair(r.Context(), userID, claims.Admin)
if err != nil {
log.Error().Err(err).Msg("Failed to generate tokens")
respondError(w, http.StatusInternalServerError, "Failed to generate tokens")
return
}
@ -575,6 +592,7 @@ func (rs authResource) Logout(w http.ResponseWriter, r *http.Request) {
rs.setAuthCookies(w, nil, true)
if err := rs.Tokens.RevokeAllUserRefreshTokens(r.Context(), userID); err != nil {
log.Error().Err(err).Msg("Failed to logout")
respondError(w, http.StatusInternalServerError, "Failed to logout")
return
}

View File

@ -100,6 +100,7 @@ func (rs *notesResource) Create(w http.ResponseWriter, r *http.Request) {
// Metadata object (parent)
note, err := rs.Notes.CreateNote(r.Context(), userID)
if err != nil {
log.Error().Err(err).Msg("Failed to create note")
respondError(w, http.StatusInternalServerError, "Failed to create note")
return
}
@ -112,6 +113,7 @@ func (rs *notesResource) Create(w http.ResponseWriter, r *http.Request) {
ContentHash: sha1ContentHash(initVersionTitle, initVersionContent),
})
if err != nil {
log.Error().Err(err).Msg("Failed to create initial version")
respondError(w, http.StatusInternalServerError, "Failed to create initial version")
return
}
@ -146,6 +148,7 @@ func (rs *notesResource) ListMetadata(w http.ResponseWriter, r *http.Request) {
Offset: offset,
})
if err != nil {
log.Error().Err(err).Msg("Failed to retrieve notes")
respondError(w, http.StatusInternalServerError, "Failed to retrieve notes")
return
}
@ -197,6 +200,7 @@ func (rs *notesResource) Delete(w http.ResponseWriter, r *http.Request) {
UserID: userID, // NOTE: using `fullNote.userID` here'd be insecure
})
if err != nil {
log.Error().Err(err).Msg("Failed to delete note")
respondError(w, http.StatusInternalServerError, "Failed to delete note")
return
}
@ -220,6 +224,7 @@ func (rs *notesResource) GetVersionHistory(w http.ResponseWriter, r *http.Reques
Offset: offset,
})
if err != nil {
log.Error().Err(err).Msg("Failed to get version history")
respondError(w, http.StatusInternalServerError, "Failed to get version history")
return
}
@ -303,7 +308,7 @@ func (rs *notesResource) CreateVersion(w http.ResponseWriter, r *http.Request) {
ContentHash: sha1ContentHash(*req.Title, *req.Content),
})
if err != nil {
log.Error().Err(err).Msg("Failed to create new note version")
log.Error().Err(err).Msg("Failed to create note version")
respondError(w, http.StatusInternalServerError, "Failed to create note version")
return
}

View File

@ -67,7 +67,7 @@ func Run(conn *pgx.Conn, q *data.Queries, config SvcConfig) error {
r.Use(middleware.AllowContentType("application/json"))
// Cleanup workers
scheduleTokenCleanup(context.Background(), q)
scheduleArtifactCleanup(context.Background(), q)
// Routes grouped by functionality (we must prefix the API routes with `/api`
// as the domain will be the same for the front and back ends)
@ -88,10 +88,12 @@ func Run(conn *pgx.Conn, q *data.Queries, config SvcConfig) error {
// Start worker that automatically cleans up the `notes` (cascading to `note_versions`) and
// `refresh_tokens` tables from expired (or revoked) entries. The tasks run once during
// initialization and then once an hour until the backend is shutdown.
func scheduleTokenCleanup(ctx context.Context, q *data.Queries) {
func scheduleArtifactCleanup(ctx context.Context, q *data.Queries) {
cleanupNotes(ctx, q)
cleanupRefreshTokens(ctx, q)
log.Info().Msg("Scheduled database artifact cleanup to run once an hour")
ticker := time.NewTicker(1 * time.Hour)
go func() {
for range ticker.C {