@@ -95,6 +95,10 @@ sessionrepo := sessionrepo.New(psqlDB)
vertokrepo := vertokrepo.New(psqlDB) pwdtokrepo := passwordtokrepo.NewPasswordResetTokenRepo(psqlDB) + notecache := notecache.New(redisDB, cfg.CacheNoteTTL) + noterepo := noterepo.New(psqlDB) + notesrv := notesrv.New(noterepo, notePasswordHasher, notecache) + userepo := userepo.New(psqlDB) usercache := usercache.New(redisDB, cfg.CacheUsersTTL) usersrv := usersrv.New(@@ -102,6 +106,7 @@ userepo,
sessionrepo, vertokrepo, pwdtokrepo, + noterepo, userPasswordHasher, jwtTokenizer, mailermq,@@ -112,10 +117,6 @@ cfg.JwtRefreshTokenTTL,
cfg.VerificationTokenTTL, cfg.ResetPasswordTokenTTL, ) - - notecache := notecache.New(redisDB, cfg.CacheNoteTTL) - noterepo := noterepo.New(psqlDB) - notesrv := notesrv.New(noterepo, notePasswordHasher, notecache) rateLimiterConfig := ratelimit.Config{ RPS: cfg.RateLimiterRPS,
@@ -449,13 +449,15 @@ e.Equal(httpResp.Code, http.StatusBadRequest)
} type getMeResponse struct { - Email string `json:"email"` - CreatedAt time.Time `json:"created_at"` + Email string `json:"email"` + CreatedAt time.Time `json:"created_at"` + LastLoginAt time.Time `json:"last_login_at"` + NotesCreated int `json:"notes_created"` } func (e *AppTestSuite) TestApiV1_getMe() { email := e.uuid() + "@test.com" - _, toks := e.createAndSingIn(email, "password") + uid, toks := e.createAndSingIn(email, "password") httpResp := e.httpRequest(http.MethodGet, "/api/v1/me", nil, toks.AccessToken)@@ -466,6 +468,15 @@ e.readBodyAndUnjsonify(httpResp.Body, &body)
e.Equal(email, body.Email) e.NotZero(body.CreatedAt) + e.NotZero(body.LastLoginAt) + + var notesCount int + err := e.postgresDB. + QueryRow(e.ctx, "select count(*) from notes_authors where user_id = $1", uid). + Scan(¬esCount) + e.require.NoError(err) + + e.Equal(body.NotesCreated, notesCount) } // createAndSingIn creates an activated user, logs them in,
@@ -105,6 +105,10 @@ pwdtokrepo := passwordtokrepo.NewPasswordResetTokenRepo(e.postgresDB)
stubOAuthProvider := newOauthProviderMock() + notecache := notecache.New(e.redisDB, cfg.CacheUsersTTL) + noterepo := noterepo.New(e.postgresDB) + notesrv := notesrv.New(noterepo, e.hasher, notecache) + userepo := userepo.New(e.postgresDB) usercache := usercache.New(e.redisDB, cfg.CacheUsersTTL) usersrv := usersrv.New(@@ -112,6 +116,7 @@ userepo,
sessionrepo, vertokrepo, pwdtokrepo, + noterepo, e.hasher, e.jwtTokenizer, newMailerMockService(),@@ -122,10 +127,6 @@ cfg.JwtRefreshTokenTTL,
cfg.VerificationTokenTTL, cfg.ResetPasswordTokenTTL, ) - - notecache := notecache.New(e.redisDB, cfg.CacheUsersTTL) - noterepo := noterepo.New(e.postgresDB) - notesrv := notesrv.New(noterepo, e.hasher, notecache) // for testing purposes, it's ok to have high values ig ratelimitCfg := ratelimit.Config{
@@ -41,6 +41,8 @@ Refresh string
} type UserInfo struct { - Email string - CreatedAt time.Time + Email string + CreatedAt time.Time + LastLoginAt time.Time + NotesCreated int }
@@ -13,6 +13,7 @@ "github.com/olexsmir/onasty/internal/hasher"
"github.com/olexsmir/onasty/internal/jwtutil" "github.com/olexsmir/onasty/internal/models" "github.com/olexsmir/onasty/internal/oauth" + "github.com/olexsmir/onasty/internal/store/psql/noterepo" "github.com/olexsmir/onasty/internal/store/psql/passwordtokrepo" "github.com/olexsmir/onasty/internal/store/psql/sessionrepo" "github.com/olexsmir/onasty/internal/store/psql/userepo"@@ -51,6 +52,7 @@ userstore userepo.UserStorer
sessionstore sessionrepo.SessionStorer vertokrepo vertokrepo.VerificationTokenStorer pwdtokrepo passwordtokrepo.PasswordResetTokenStorer + notestore noterepo.NoteStorer cache usercache.UserCacheer hasher hasher.Hasher@@ -70,6 +72,7 @@ userstore userepo.UserStorer,
sessionstore sessionrepo.SessionStorer, vertokrepo vertokrepo.VerificationTokenStorer, pwdtokrepo passwordtokrepo.PasswordResetTokenStorer, + notestore noterepo.NoteStorer, hasher hasher.Hasher, jwtTokenizer jwtutil.JWTTokenizer, mailermq mailermq.Mailer,@@ -82,6 +85,7 @@ userstore: userstore,
sessionstore: sessionstore, vertokrepo: vertokrepo, pwdtokrepo: pwdtokrepo, + notestore: notestore, cache: cache, hasher: hasher, jwtTokenizer: jwtTokenizer,@@ -174,9 +178,16 @@ if err != nil {
return dtos.UserInfo{}, err } + count, err := u.notestore.GetCountOfNotesByAuthorID(ctx, userID) + if err != nil { + return dtos.UserInfo{}, err + } + return dtos.UserInfo{ - Email: user.Email, - CreatedAt: user.CreatedAt, + Email: user.Email, + CreatedAt: user.CreatedAt, + LastLoginAt: user.LastLoginAt, + NotesCreated: int(count), }, nil }
@@ -28,6 +28,9 @@
// GetAllByAuthorID returns all notes with specified author. GetAllByAuthorID(ctx context.Context, authorID uuid.UUID) ([]models.Note, error) + // GetCountOfNotesByAuthorID returns count of notes created by specified author. + GetCountOfNotesByAuthorID(ctx context.Context, authorID uuid.UUID) (int64, error) + // GetBySlugAndPassword gets a note by slug and password. // the "password" should be hashed. //@@ -170,6 +173,20 @@ notes = append(notes, note)
} return notes, rows.Err() +} + +func (s *NoteRepo) GetCountOfNotesByAuthorID( + ctx context.Context, + authorID uuid.UUID, +) (int64, error) { + var count int64 + err := s.db.QueryRow( + ctx, + `select count(*) from notes_authors where user_id = $1`, + authorID.String(), + ).Scan(&count) + + return count, err } func (s *NoteRepo) GetBySlugAndPassword(
@@ -264,8 +264,10 @@ })
} type getMeResponse struct { - Email string `json:"email"` - CreatedAt time.Time `json:"created_at"` + Email string `json:"email"` + CreatedAt time.Time `json:"created_at"` + LastLoginAt time.Time `json:"last_login_at"` + NotesCreated int `json:"notes_created"` } func (a *APIV1) getMeHandler(c *gin.Context) {@@ -276,7 +278,9 @@ return
} c.JSON(http.StatusOK, getMeResponse{ - Email: uinfo.Email, - CreatedAt: uinfo.CreatedAt, + Email: uinfo.Email, + CreatedAt: uinfo.CreatedAt, + LastLoginAt: uinfo.LastLoginAt, + NotesCreated: uinfo.NotesCreated, }) }