all repos

onasty @ e075c324c446934958b3a537a3dfdb14c404fb7c

a one-time notes service
11 files changed, 33 insertions(+), 41 deletions(-)
refactor: deal with TODOs and typos (#30)

* refactor(usersrv): add util func for sending emails

* chore: update taskfile

* remove some comment

* fix email sending

* feat(mailer): add some logging

* add comment

* fix typo

* fix more typos
Author: Smirnov Oleksandr ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2024-10-14 17:35:42 +0300
Parent: 943fcdb
M Dockerfile

@@ -10,6 +10,7 @@ COPY internal internal

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -s' -o /onasty ./cmd/server -FROM scratch +FROM alpine:3.20 COPY --from=builder /onasty /onasty +RUN apk --no-cache add ca-certificates ENTRYPOINT ["/onasty"]
M Taskfile.yml

@@ -7,11 +7,8 @@ includes:

migrate: ./migrations/Taskfile.yml tasks: - build: - - go build -o .bin/onasty ./cmd/server/ - run: - - docker compose up -d --build core + - docker compose up -d --build --remove-orphans core lint: - golangci-lint run

@@ -29,7 +26,7 @@ - task: test:unit

- task: test:e2e test:unit: - - go test -v --short ./... + - go test --count=1 -v --short ./... test:e2e: - go test --count=1 -v ./e2e/
M e2e/apiv1_auth_test.go

@@ -44,7 +44,7 @@ username string

email string password string }{ - {name: "all fiels empty", email: "", password: "", username: ""}, + {name: "all fields empty", email: "", password: "", username: ""}, { name: "non valid email", username: "testing",

@@ -99,8 +99,6 @@ }),

) e.Equal(http.StatusCreated, httpResp.Code) - - // TODO: probably should get the token from the email user := e.getLastInsertedUserByEmail(email) token := e.getVerificationTokenByUserID(user.ID)

@@ -158,7 +156,7 @@ password: password,

expectedCode: http.StatusBadRequest, }, { - name: "wrong credintials", + name: "wrong credentials", email: email, password: e.uuid(), expectedCode: http.StatusUnauthorized,

@@ -235,7 +233,7 @@ expectedMsg: models.ErrUserIsNotActivated.Error(),

}, { name: "wrong email", - email: "wrong@emai.com", + email: "wrong@email.com", password: password, expectedCode: http.StatusUnauthorized, },
M e2e/e2e_utils_test.go

@@ -33,7 +33,7 @@ e.require.NoError(err)

} // httpRequest sends http request to the server and returns `httptest.ResponseRecorder` -// conteny-type always set to application/json +// content-type always set to application/json func (e *AppTestSuite) httpRequest( method, url string, body []byte,
M internal/mailer/mailgun.go

@@ -38,6 +38,7 @@ return err

} slog.DebugContext(ctx, "email sent", "subject", subject, "content", content, "err", err) + slog.InfoContext(ctx, "email sent", "to", to) metrics.RecordEmailSent() return nil
M internal/models/user.go

@@ -31,7 +31,6 @@ LastLoginAt time.Time

} func (u User) Validate() error { - // NOTE: there's probably a better way to validate emails _, err := mail.ParseAddress(u.Email) if err != nil { return errors.New("user: invalid email") //nolint:err113
M internal/service/notesrv/notesrv.go

@@ -61,7 +61,6 @@ if err != nil {

return dtos.NoteDTO{}, err } - // TODO: there should be a better way to do it m := models.Note{ //nolint:exhaustruct ExpiresAt: note.ExpiresAt, BurnBeforeExpiration: note.BurnBeforeExpiration,

@@ -78,6 +77,6 @@ return note, nil

} // TODO: in future not remove, leave some metadata - // to shot user that note was alreasy seen + // to shot user that note was already seen return note, n.noterepo.DeleteBySlug(ctx, note.Slug) }
M internal/service/usersrv/email.go

@@ -10,7 +10,7 @@

var ErrFailedToSendVerifcationEmail = errors.New("failed to send verification email") const ( - verificationEmailSubject = "Onasty: verifiy your email" + verificationEmailSubject = "Onasty: verify your email" verificationEmailBody = `To verify your email, please follow this link: <a href="%[1]s/api/v1/auth/verify/%[2]s">%[1]s/api/v1/auth/verify/%[2]s</a> <br />

@@ -24,11 +24,10 @@ cancel context.CancelFunc,

userEmail string, token string, url string, -) error { +) { select { case <-ctx.Done(): - slog.ErrorContext(ctx, "failed to send verfication email", "err", ctx.Err()) - return ErrFailedToSendVerifcationEmail + slog.ErrorContext(ctx, "failed to send verification email", "err", ctx.Err()) default: if err := u.mailer.Send( ctx,

@@ -36,10 +35,8 @@ userEmail,

verificationEmailSubject, fmt.Sprintf(verificationEmailBody, url, token), ); err != nil { - return errors.Join(ErrFailedToSendVerifcationEmail, err) + slog.ErrorContext(ctx, "failed to send verification email", "err", err) } cancel() } - - return nil }
M internal/service/usersrv/usersrv.go

@@ -14,6 +14,7 @@ "github.com/olexsmir/onasty/internal/models"

"github.com/olexsmir/onasty/internal/store/psql/sessionrepo" "github.com/olexsmir/onasty/internal/store/psql/userepo" "github.com/olexsmir/onasty/internal/store/psql/vertokrepo" + "github.com/olexsmir/onasty/internal/transport/http/reqid" ) type UserServicer interface {

@@ -93,16 +94,8 @@ if err := u.vertokrepo.Create(ctx, vtok, uid, time.Now(), time.Now().Add(u.verificationTokenTTL)); err != nil {

return uuid.Nil, err } - // TODO: handle the error that might be returned - // i dont think that there's need to handle the error, just log it - bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second) - go u.sendVerificationEmail( //nolint:errcheck,contextcheck - bgCtx, - bgCancel, - inp.Email, - vtok, - u.appURL, - ) + sendingCtx, cancel := getContextForEmailSending(ctx) + go u.sendVerificationEmail(sendingCtx, cancel, inp.Email, vtok, u.appURL) return uid, nil }

@@ -223,14 +216,8 @@ if err != nil {

return err } - bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second) - go u.sendVerificationEmail( //nolint:errcheck,contextcheck - bgCtx, - bgCancel, - inp.Email, - token, - u.appURL, - ) + sendingCtx, cancel := getContextForEmailSending(ctx) + go u.sendVerificationEmail(sendingCtx, cancel, inp.Email, token, u.appURL) return nil }

@@ -263,3 +250,11 @@ Access: accessToken,

Refresh: refreshToken, }, err } + +func getContextForEmailSending(ctx context.Context) (context.Context, context.CancelFunc) { + rid := reqid.GetContext(ctx) + resCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + resCtx = reqid.SetContext(resCtx, rid) + + return resCtx, cancel +}
M internal/transport/http/apiv1/middleware.go

@@ -70,7 +70,7 @@ metrics.RecordFailedRequestMetric(c.Request.Method, c.Request.RequestURI)

} } -//nolint:unused // TODO: remove me later +//nolint:unused func (a *APIV1) isUserAuthorized(c *gin.Context) bool { return !a.getUserID(c).IsNil() }
M internal/transport/http/reqid/reqid.go

@@ -48,3 +48,8 @@ return ""

} return rid } + +// SetContext gets a parent context and returns a child context with the set provided request ID +func SetContext(ctx context.Context, rid string) context.Context { + return context.WithValue(ctx, RequestID, rid) +}