all repos

onasty @ e075c32

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
        10
         

      
        11
        11
         RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -s' -o /onasty ./cmd/server

      
        12
        12
         

      
        13
        
        -FROM scratch

      
        
        13
        +FROM alpine:3.20

      
        14
        14
         COPY --from=builder /onasty /onasty

      
        
        15
        +RUN apk --no-cache add ca-certificates

      
        15
        16
         ENTRYPOINT ["/onasty"]

      
M Taskfile.yml
···
        7
        7
           migrate: ./migrations/Taskfile.yml

      
        8
        8
         

      
        9
        9
         tasks:

      
        10
        
        -  build:

      
        11
        
        -    - go build -o .bin/onasty ./cmd/server/

      
        12
        
        -

      
        13
        10
           run:

      
        14
        
        -    - docker compose up -d --build core

      
        
        11
        +    - docker compose up -d --build --remove-orphans core

      
        15
        12
         

      
        16
        13
           lint:

      
        17
        14
             - golangci-lint run

      ···
        29
        26
             - task: test:e2e

      
        30
        27
         

      
        31
        28
           test:unit:

      
        32
        
        -    - go test -v --short ./...

      
        
        29
        +    - go test --count=1 -v --short ./...

      
        33
        30
         

      
        34
        31
           test:e2e:

      
        35
        32
             - go test --count=1 -v ./e2e/

      
M e2e/apiv1_auth_test.go
···
        44
        44
         		email    string

      
        45
        45
         		password string

      
        46
        46
         	}{

      
        47
        
        -		{name: "all fiels empty", email: "", password: "", username: ""},

      
        
        47
        +		{name: "all fields empty", email: "", password: "", username: ""},

      
        48
        48
         		{

      
        49
        49
         			name:     "non valid email",

      
        50
        50
         			username: "testing",

      ···
        99
        99
         	)

      
        100
        100
         

      
        101
        101
         	e.Equal(http.StatusCreated, httpResp.Code)

      
        102
        
        -

      
        103
        
        -	// TODO: probably should get the token from the email

      
        104
        102
         

      
        105
        103
         	user := e.getLastInsertedUserByEmail(email)

      
        106
        104
         	token := e.getVerificationTokenByUserID(user.ID)

      ···
        158
        156
         			expectedCode: http.StatusBadRequest,

      
        159
        157
         		},

      
        160
        158
         		{

      
        161
        
        -			name:         "wrong credintials",

      
        
        159
        +			name:         "wrong credentials",

      
        162
        160
         			email:        email,

      
        163
        161
         			password:     e.uuid(),

      
        164
        162
         			expectedCode: http.StatusUnauthorized,

      ···
        235
        233
         		},

      
        236
        234
         		{

      
        237
        235
         			name:         "wrong email",

      
        238
        
        -			email:        "wrong@emai.com",

      
        
        236
        +			email:        "wrong@email.com",

      
        239
        237
         			password:     password,

      
        240
        238
         			expectedCode: http.StatusUnauthorized,

      
        241
        239
         		},

      
M e2e/e2e_utils_test.go
···
        33
        33
         }

      
        34
        34
         

      
        35
        35
         // httpRequest sends http request to the server and returns `httptest.ResponseRecorder`

      
        36
        
        -// conteny-type always set to application/json

      
        
        36
        +// content-type always set to application/json

      
        37
        37
         func (e *AppTestSuite) httpRequest(

      
        38
        38
         	method, url string,

      
        39
        39
         	body []byte,

      
M internal/mailer/mailgun.go
···
        38
        38
         	}

      
        39
        39
         

      
        40
        40
         	slog.DebugContext(ctx, "email sent", "subject", subject, "content", content, "err", err)

      
        
        41
        +	slog.InfoContext(ctx, "email sent", "to", to)

      
        41
        42
         	metrics.RecordEmailSent()

      
        42
        43
         

      
        43
        44
         	return nil

      
M internal/models/user.go
···
        31
        31
         }

      
        32
        32
         

      
        33
        33
         func (u User) Validate() error {

      
        34
        
        -	// NOTE: there's probably a better way to validate emails

      
        35
        34
         	_, err := mail.ParseAddress(u.Email)

      
        36
        35
         	if err != nil {

      
        37
        36
         		return errors.New("user: invalid email") //nolint:err113

      
M internal/service/notesrv/notesrv.go
···
        61
        61
         		return dtos.NoteDTO{}, err

      
        62
        62
         	}

      
        63
        63
         

      
        64
        
        -	// TODO: there should be a better way to do it

      
        65
        64
         	m := models.Note{ //nolint:exhaustruct

      
        66
        65
         		ExpiresAt:            note.ExpiresAt,

      
        67
        66
         		BurnBeforeExpiration: note.BurnBeforeExpiration,

      ···
        78
        77
         	}

      
        79
        78
         

      
        80
        79
         	// TODO: in future not remove, leave some metadata

      
        81
        
        -	// to shot user that note was alreasy seen

      
        
        80
        +	// to shot user that note was already seen

      
        82
        81
         	return note, n.noterepo.DeleteBySlug(ctx, note.Slug)

      
        83
        82
         }

      
M internal/service/usersrv/email.go
···
        10
        10
         var ErrFailedToSendVerifcationEmail = errors.New("failed to send verification email")

      
        11
        11
         

      
        12
        12
         const (

      
        13
        
        -	verificationEmailSubject = "Onasty: verifiy your email"

      
        
        13
        +	verificationEmailSubject = "Onasty: verify your email"

      
        14
        14
         	verificationEmailBody    = `To verify your email, please follow this link:

      
        15
        15
         <a href="%[1]s/api/v1/auth/verify/%[2]s">%[1]s/api/v1/auth/verify/%[2]s</a>

      
        16
        16
         <br />

      ···
        24
        24
         	userEmail string,

      
        25
        25
         	token string,

      
        26
        26
         	url string,

      
        27
        
        -) error {

      
        
        27
        +) {

      
        28
        28
         	select {

      
        29
        29
         	case <-ctx.Done():

      
        30
        
        -		slog.ErrorContext(ctx, "failed to send verfication email", "err", ctx.Err())

      
        31
        
        -		return ErrFailedToSendVerifcationEmail

      
        
        30
        +		slog.ErrorContext(ctx, "failed to send verification email", "err", ctx.Err())

      
        32
        31
         	default:

      
        33
        32
         		if err := u.mailer.Send(

      
        34
        33
         			ctx,

      ···
        36
        35
         			verificationEmailSubject,

      
        37
        36
         			fmt.Sprintf(verificationEmailBody, url, token),

      
        38
        37
         		); err != nil {

      
        39
        
        -			return errors.Join(ErrFailedToSendVerifcationEmail, err)

      
        
        38
        +			slog.ErrorContext(ctx, "failed to send verification email", "err", err)

      
        40
        39
         		}

      
        41
        40
         		cancel()

      
        42
        41
         	}

      
        43
        
        -

      
        44
        
        -	return nil

      
        45
        42
         }

      
M internal/service/usersrv/usersrv.go
···
        14
        14
         	"github.com/olexsmir/onasty/internal/store/psql/sessionrepo"

      
        15
        15
         	"github.com/olexsmir/onasty/internal/store/psql/userepo"

      
        16
        16
         	"github.com/olexsmir/onasty/internal/store/psql/vertokrepo"

      
        
        17
        +	"github.com/olexsmir/onasty/internal/transport/http/reqid"

      
        17
        18
         )

      
        18
        19
         

      
        19
        20
         type UserServicer interface {

      ···
        93
        94
         		return uuid.Nil, err

      
        94
        95
         	}

      
        95
        96
         

      
        96
        
        -	// TODO: handle the error that might be returned

      
        97
        
        -	// i dont think that there's need to handle the error, just log it

      
        98
        
        -	bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second)

      
        99
        
        -	go u.sendVerificationEmail( //nolint:errcheck,contextcheck

      
        100
        
        -		bgCtx,

      
        101
        
        -		bgCancel,

      
        102
        
        -		inp.Email,

      
        103
        
        -		vtok,

      
        104
        
        -		u.appURL,

      
        105
        
        -	)

      
        
        97
        +	sendingCtx, cancel := getContextForEmailSending(ctx)

      
        
        98
        +	go u.sendVerificationEmail(sendingCtx, cancel, inp.Email, vtok, u.appURL)

      
        106
        99
         

      
        107
        100
         	return uid, nil

      
        108
        101
         }

      ···
        223
        216
         		return err

      
        224
        217
         	}

      
        225
        218
         

      
        226
        
        -	bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second)

      
        227
        
        -	go u.sendVerificationEmail( //nolint:errcheck,contextcheck

      
        228
        
        -		bgCtx,

      
        229
        
        -		bgCancel,

      
        230
        
        -		inp.Email,

      
        231
        
        -		token,

      
        232
        
        -		u.appURL,

      
        233
        
        -	)

      
        
        219
        +	sendingCtx, cancel := getContextForEmailSending(ctx)

      
        
        220
        +	go u.sendVerificationEmail(sendingCtx, cancel, inp.Email, token, u.appURL)

      
        234
        221
         

      
        235
        222
         	return nil

      
        236
        223
         }

      ···
        263
        250
         		Refresh: refreshToken,

      
        264
        251
         	}, err

      
        265
        252
         }

      
        
        253
        +

      
        
        254
        +func getContextForEmailSending(ctx context.Context) (context.Context, context.CancelFunc) {

      
        
        255
        +	rid := reqid.GetContext(ctx)

      
        
        256
        +	resCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

      
        
        257
        +	resCtx = reqid.SetContext(resCtx, rid)

      
        
        258
        +

      
        
        259
        +	return resCtx, cancel

      
        
        260
        +}

      
M internal/transport/http/apiv1/middleware.go
···
        70
        70
         	}

      
        71
        71
         }

      
        72
        72
         

      
        73
        
        -//nolint:unused // TODO: remove me later

      
        
        73
        +//nolint:unused

      
        74
        74
         func (a *APIV1) isUserAuthorized(c *gin.Context) bool {

      
        75
        75
         	return !a.getUserID(c).IsNil()

      
        76
        76
         }

      
M internal/transport/http/reqid/reqid.go
···
        48
        48
         	}

      
        49
        49
         	return rid

      
        50
        50
         }

      
        
        51
        +

      
        
        52
        +// SetContext gets a parent context and returns a child context with the set provided request ID

      
        
        53
        +func SetContext(ctx context.Context, rid string) context.Context {

      
        
        54
        +	return context.WithValue(ctx, RequestID, rid)

      
        
        55
        +}