7 files changed,
66 insertions(+),
19 deletions(-)
Author:
Olexandr Smirnov
ss2316544@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2025-07-08 16:12:38 +0300
Parent:
f01c95c
M
cmd/api/main.go
··· 95 95 vertokrepo := vertokrepo.New(psqlDB) 96 96 pwdtokrepo := passwordtokrepo.NewPasswordResetTokenRepo(psqlDB) 97 97 98 + notecache := notecache.New(redisDB, cfg.CacheNoteTTL) 99 + noterepo := noterepo.New(psqlDB) 100 + notesrv := notesrv.New(noterepo, notePasswordHasher, notecache) 101 + 98 102 userepo := userepo.New(psqlDB) 99 103 usercache := usercache.New(redisDB, cfg.CacheUsersTTL) 100 104 usersrv := usersrv.New( ··· 102 106 sessionrepo, 103 107 vertokrepo, 104 108 pwdtokrepo, 109 + noterepo, 105 110 userPasswordHasher, 106 111 jwtTokenizer, 107 112 mailermq, ··· 112 117 cfg.VerificationTokenTTL, 113 118 cfg.ResetPasswordTokenTTL, 114 119 ) 115 - 116 - notecache := notecache.New(redisDB, cfg.CacheNoteTTL) 117 - noterepo := noterepo.New(psqlDB) 118 - notesrv := notesrv.New(noterepo, notePasswordHasher, notecache) 119 120 120 121 rateLimiterConfig := ratelimit.Config{ 121 122 RPS: cfg.RateLimiterRPS,
M
e2e/apiv1_auth_test.go
··· 449 449 } 450 450 451 451 type getMeResponse struct { 452 - Email string `json:"email"` 453 - CreatedAt time.Time `json:"created_at"` 452 + Email string `json:"email"` 453 + CreatedAt time.Time `json:"created_at"` 454 + LastLoginAt time.Time `json:"last_login_at"` 455 + NotesCreated int `json:"notes_created"` 454 456 } 455 457 456 458 func (e *AppTestSuite) TestApiV1_getMe() { 457 459 email := e.uuid() + "@test.com" 458 - _, toks := e.createAndSingIn(email, "password") 460 + uid, toks := e.createAndSingIn(email, "password") 459 461 460 462 httpResp := e.httpRequest(http.MethodGet, "/api/v1/me", nil, toks.AccessToken) 461 463 ··· 466 468 467 469 e.Equal(email, body.Email) 468 470 e.NotZero(body.CreatedAt) 471 + e.NotZero(body.LastLoginAt) 472 + 473 + var notesCount int 474 + err := e.postgresDB. 475 + QueryRow(e.ctx, "select count(*) from notes_authors where user_id = $1", uid). 476 + Scan(¬esCount) 477 + e.require.NoError(err) 478 + 479 + e.Equal(body.NotesCreated, notesCount) 469 480 } 470 481 471 482 // createAndSingIn creates an activated user, logs them in,
M
e2e/e2e_test.go
··· 105 105 106 106 stubOAuthProvider := newOauthProviderMock() 107 107 108 + notecache := notecache.New(e.redisDB, cfg.CacheUsersTTL) 109 + noterepo := noterepo.New(e.postgresDB) 110 + notesrv := notesrv.New(noterepo, e.hasher, notecache) 111 + 108 112 userepo := userepo.New(e.postgresDB) 109 113 usercache := usercache.New(e.redisDB, cfg.CacheUsersTTL) 110 114 usersrv := usersrv.New( ··· 112 116 sessionrepo, 113 117 vertokrepo, 114 118 pwdtokrepo, 119 + noterepo, 115 120 e.hasher, 116 121 e.jwtTokenizer, 117 122 newMailerMockService(), ··· 122 127 cfg.VerificationTokenTTL, 123 128 cfg.ResetPasswordTokenTTL, 124 129 ) 125 - 126 - notecache := notecache.New(e.redisDB, cfg.CacheUsersTTL) 127 - noterepo := noterepo.New(e.postgresDB) 128 - notesrv := notesrv.New(noterepo, e.hasher, notecache) 129 130 130 131 // for testing purposes, it's ok to have high values ig 131 132 ratelimitCfg := ratelimit.Config{
M
internal/service/usersrv/usersrv.go
··· 13 13 "github.com/olexsmir/onasty/internal/jwtutil" 14 14 "github.com/olexsmir/onasty/internal/models" 15 15 "github.com/olexsmir/onasty/internal/oauth" 16 + "github.com/olexsmir/onasty/internal/store/psql/noterepo" 16 17 "github.com/olexsmir/onasty/internal/store/psql/passwordtokrepo" 17 18 "github.com/olexsmir/onasty/internal/store/psql/sessionrepo" 18 19 "github.com/olexsmir/onasty/internal/store/psql/userepo" ··· 51 52 sessionstore sessionrepo.SessionStorer 52 53 vertokrepo vertokrepo.VerificationTokenStorer 53 54 pwdtokrepo passwordtokrepo.PasswordResetTokenStorer 55 + notestore noterepo.NoteStorer 54 56 cache usercache.UserCacheer 55 57 56 58 hasher hasher.Hasher ··· 70 72 sessionstore sessionrepo.SessionStorer, 71 73 vertokrepo vertokrepo.VerificationTokenStorer, 72 74 pwdtokrepo passwordtokrepo.PasswordResetTokenStorer, 75 + notestore noterepo.NoteStorer, 73 76 hasher hasher.Hasher, 74 77 jwtTokenizer jwtutil.JWTTokenizer, 75 78 mailermq mailermq.Mailer, ··· 82 85 sessionstore: sessionstore, 83 86 vertokrepo: vertokrepo, 84 87 pwdtokrepo: pwdtokrepo, 88 + notestore: notestore, 85 89 cache: cache, 86 90 hasher: hasher, 87 91 jwtTokenizer: jwtTokenizer, ··· 174 178 return dtos.UserInfo{}, err 175 179 } 176 180 181 + count, err := u.notestore.GetCountOfNotesByAuthorID(ctx, userID) 182 + if err != nil { 183 + return dtos.UserInfo{}, err 184 + } 185 + 177 186 return dtos.UserInfo{ 178 - Email: user.Email, 179 - CreatedAt: user.CreatedAt, 187 + Email: user.Email, 188 + CreatedAt: user.CreatedAt, 189 + LastLoginAt: user.LastLoginAt, 190 + NotesCreated: int(count), 180 191 }, nil 181 192 } 182 193
M
internal/store/psql/noterepo/noterepo.go
··· 28 28 // GetAllByAuthorID returns all notes with specified author. 29 29 GetAllByAuthorID(ctx context.Context, authorID uuid.UUID) ([]models.Note, error) 30 30 31 + // GetCountOfNotesByAuthorID returns count of notes created by specified author. 32 + GetCountOfNotesByAuthorID(ctx context.Context, authorID uuid.UUID) (int64, error) 33 + 31 34 // GetBySlugAndPassword gets a note by slug and password. 32 35 // the "password" should be hashed. 33 36 // ··· 170 173 } 171 174 172 175 return notes, rows.Err() 176 +} 177 + 178 +func (s *NoteRepo) GetCountOfNotesByAuthorID( 179 + ctx context.Context, 180 + authorID uuid.UUID, 181 +) (int64, error) { 182 + var count int64 183 + err := s.db.QueryRow( 184 + ctx, 185 + `select count(*) from notes_authors where user_id = $1`, 186 + authorID.String(), 187 + ).Scan(&count) 188 + 189 + return count, err 173 190 } 174 191 175 192 func (s *NoteRepo) GetBySlugAndPassword(
M
internal/transport/http/apiv1/auth.go
··· 264 264 } 265 265 266 266 type getMeResponse struct { 267 - Email string `json:"email"` 268 - CreatedAt time.Time `json:"created_at"` 267 + Email string `json:"email"` 268 + CreatedAt time.Time `json:"created_at"` 269 + LastLoginAt time.Time `json:"last_login_at"` 270 + NotesCreated int `json:"notes_created"` 269 271 } 270 272 271 273 func (a *APIV1) getMeHandler(c *gin.Context) { ··· 276 278 } 277 279 278 280 c.JSON(http.StatusOK, getMeResponse{ 279 - Email: uinfo.Email, 280 - CreatedAt: uinfo.CreatedAt, 281 + Email: uinfo.Email, 282 + CreatedAt: uinfo.CreatedAt, 283 + LastLoginAt: uinfo.LastLoginAt, 284 + NotesCreated: uinfo.NotesCreated, 281 285 }) 282 286 }