feat: add createdAt timestamp to email verification and password reset entities#1821
Conversation
📝 WalkthroughWalkthroughThis PR adds persistent creation timestamps to email verification and password reset records, enforcing a one-day validity window in repository lookups. Entity schemas are updated with createdAt columns, repository queries filter by this timestamp, and a database migration applies the schema changes to both tables. ChangesEmail Verification and Password Reset Timestamp Expiry
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a createdAt timestamp to the email_verification and password_reset persistence layer and uses it to enforce a 24-hour validity window when looking up verification/reset tokens.
Changes:
- Adds a DB migration to introduce
createdAtcolumns (defaulting to current time) onemail_verificationandpassword_reset. - Updates
EmailVerificationEntityandPasswordResetEntityto map the newcreatedAttimestamp columns. - Updates the custom repository lookups to only return tokens created within the last day (
Constants.ONE_DAY_AGO()).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts | Adds createdAt columns to both tables with NOT NULL + default timestamp; includes down migration. |
| backend/src/entities/user/user-password/repository/user-password-custom-repository-extension.ts | Filters password reset lookup to last 24 hours (adds createdAt constraint) and imports Constants. |
| backend/src/entities/user/user-password/password-reset.entity.ts | Adds createdAt column mapping to PasswordResetEntity. |
| backend/src/entities/email/repository/email-verification-custom-repository-extension.ts | Filters email verification lookup to last 24 hours (adds createdAt constraint) and imports Constants. |
| backend/src/entities/email/email-verification.entity.ts | Adds createdAt column mapping to EmailVerificationEntity. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }) | ||
| .andWhere('email_verification.createdAt >= :valid_after', { | ||
| valid_after: Constants.ONE_DAY_AGO(), | ||
| }); |
| .where('password_reset.verification_string = :ver_string', { | ||
| ver_string: verificationString, | ||
| }) | ||
| .andWhere('password_reset.createdAt >= :valid_after', { | ||
| valid_after: Constants.ONE_DAY_AGO(), | ||
| }); |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
backend/src/entities/user/user-password/password-reset.entity.ts (1)
12-13: ⚡ Quick winSame as
email-verification.entity.ts: prefer@CreateDateColumn()and considertimestamptz.The identical idiomatic-TypeORM and timezone considerations apply to this
createdAtcolumn, sincefindPasswordResetWidthVerificationStringcompares it against a JSDatefromConstants.ONE_DAY_AGO().♻️ Proposed change
- `@Column`({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - createdAt: Date; + `@CreateDateColumn`({ type: 'timestamptz' }) + createdAt: Date;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/src/entities/user/user-password/password-reset.entity.ts` around lines 12 - 13, Replace the manual `@Column` createdAt declaration with TypeORM's `@CreateDateColumn` using a timezone-aware type (e.g., 'timestamptz' or 'timestamp with time zone') and ensure CreateDateColumn is imported so createdAt is auto-populated; this keeps behavior consistent with email-verification.entity and prevents timezone mismatch when findPasswordResetWidthVerificationString compares createdAt against the JS Date from Constants.ONE_DAY_AGO().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/entities/email/email-verification.entity.ts`:
- Around line 12-13: Replace the manual createdAt column declaration with
TypeORM's `@CreateDateColumn` to let the ORM populate insert time while keeping
the DB type aligned: in the EmailVerification entity
(email_verification.createdAt / createdAt property) swap the `@Column`({ type:
'timestamp', default: () => 'CURRENT_TIMESTAMP' }) declaration to
`@CreateDateColumn`({ type: 'timestamp' }) so the runtime uses insert-time
population and retains the existing TIMESTAMP semantics used by your ONE_DAY_AGO
cutoff; do not change to 'timestamptz' here — only migrate the column type after
adding an explicit DB migration if you later decide to use timestamptz.
---
Nitpick comments:
In `@backend/src/entities/user/user-password/password-reset.entity.ts`:
- Around line 12-13: Replace the manual `@Column` createdAt declaration with
TypeORM's `@CreateDateColumn` using a timezone-aware type (e.g., 'timestamptz' or
'timestamp with time zone') and ensure CreateDateColumn is imported so createdAt
is auto-populated; this keeps behavior consistent with email-verification.entity
and prevents timezone mismatch when findPasswordResetWidthVerificationString
compares createdAt against the JS Date from Constants.ONE_DAY_AGO().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 17782bfb-073a-489d-9a0c-7c620f275c2d
📒 Files selected for processing (5)
backend/src/entities/email/email-verification.entity.tsbackend/src/entities/email/repository/email-verification-custom-repository-extension.tsbackend/src/entities/user/user-password/password-reset.entity.tsbackend/src/entities/user/user-password/repository/user-password-custom-repository-extension.tsbackend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts
| @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) | ||
| createdAt: Date; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does TypeORM @CreateDateColumnsupport the type 'timestamptz' option, and how does it compare to@column with default CURRENT_TIMESTAMP?
💡 Result:
Yes, TypeORM's @CreateDateColumn decorator supports the type option, which allows you to specify 'timestamptz' (or 'timestamp with time zone') to ensure the database column is created with timezone awareness [1][2][3]. When you define @CreateDateColumn({ type: 'timestamptz' }), TypeORM configures the underlying database schema to use the appropriate timezone-aware timestamp type [1][2]. This is often preferred over the default behavior, which may create a naive 'timestamp' column that lacks timezone information, potentially leading to inconsistencies when storing and retrieving dates across different server or application timezones [4][2]. Comparison: @CreateDateColumn vs. @Column with default CURRENT_TIMESTAMP 1. Functional Purpose @CreateDateColumn is a specialized decorator designed specifically for tracking entity creation [5]. It automatically sets the field to the current date and time upon insertion without requiring manual intervention [5]. In contrast, @Column is a general-purpose decorator [6]. When combined with default: => 'CURRENT_TIMESTAMP', it functions similarly to @CreateDateColumn but requires you to manually define the column attributes [7][8][9]. 2. Configuration and Automation @CreateDateColumn handles metadata and behavior automatically (such as ensuring the value is set on insert) [5]. While you can override the type (e.g., to 'timestamptz'), it is intended to be used as-is [5]. Using @Column requires more verbose configuration [7]. You must explicitly provide the type, the default value (often using a SQL expression like => 'CURRENT_TIMESTAMP'), and potentially manage other behaviors manually [7][8][9]. 3. Recommendation If you need a standard "created at" field, @CreateDateColumn is the recommended approach for its simplicity and specialized integration [5]. However, if you require specific database-level configuration (such as custom default expressions, specific timezone handling beyond the default, or database-specific column types that @CreateDateColumn does not cleanly expose), using @Column with a custom default is a viable and flexible alternative [1][7]. If you encounter issues with time offsets or timezone mismatches, explicitly setting type: 'timestamptz' in either decorator is the standard fix to ensure the database preserves timezone information correctly [4][2].
Citations:
- 1: Postgres createdAt / updatedAt decorators use tz-aware NOW() as value for tz-unaware timestamp columns typeorm/typeorm#5841
- 2: Timezone in CreateDateColumn error using localized PostgreSQL instance typeorm/typeorm#6306
- 3: CreateDateColumn, UpdateDateColumn, VersionColumn not present in some case typeorm/typeorm#3125
- 4: https://stackoverflow.com/questions/79812429/typeorm-createdatecolumn-returns-wrong-time-compared-to-postgresql
- 5: https://typeorm.io/docs/entity/entities/
- 6: https://typeorm.io/
- 7: https://www.codestudy.net/blog/how-can-i-create-columns-with-type-date-and-type-datetime-in-nestjs-with-typeorm/
- 8: https://stackoverflow.com/questions/69599334/current-date-in-typeorm-and-postgresql
- 9: How to set Current time stamp as default value typeorm/typeorm#877
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print the target entity file with line numbers
FILE="backend/src/entities/email/email-verification.entity.ts"
if [ -f "$FILE" ]; then
echo "=== $FILE (head) ==="
nl -ba "$FILE" | sed -n '1,200p'
else
echo "File not found: $FILE" >&2
fi
# Search for findVerificationWithVerificationString usage and its definition
echo "=== Searching for findVerificationWithVerificationString ==="
rg -n "findVerificationWithVerificationString" backend || true
# If found, print surrounding implementation
# (Limit output via --max-count per match)
matches=$(rg -n "findVerificationWithVerificationString" backend -l || true)
if [ -n "${matches}" ]; then
echo "=== Files with findVerificationWithVerificationString ==="
echo "$matches"
for f in $matches; do
echo "=== $f (relevant section) ==="
rg -n "findVerificationWithVerificationString" "$f" -n -C 5 || true
done
fi
# Check existing TypeORM imports to see if CreateDateColumn is already imported/used elsewhere
echo "=== Searching TypeORM imports in the entity file ==="
if [ -f "$FILE" ]; then
rg -n "from 'typeorm'" "$FILE" && rg -n "CreateDateColumn|timestamptz|timestamp" "$FILE" || true
fiRepository: rocket-admin/rocketadmin
Length of output: 200
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print the target entity file with line numbers
FILE="backend/src/entities/email/email-verification.entity.ts"
if [ -f "$FILE" ]; then
echo "=== $FILE (head) ==="
nl -ba "$FILE" | sed -n '1,200p'
else
echo "File not found: $FILE" >&2
fi
# Search for findVerificationWithVerificationString usage and its definition
echo "=== Searching for findVerificationWithVerificationString ==="
rg -n "findVerificationWithVerificationString" backend || true
# Print surrounding implementation if it exists
for f in $(rg -n "findVerificationWithVerificationString" backend -l || true); do
echo "=== $f (relevant section) ==="
rg -n "findVerificationWithVerificationString" "$f" -C 5 || true
done
# Check existing TypeORM imports and timestamp usage in the entity file
echo "=== TypeORM imports / timestamp usage in $FILE ==="
if [ -f "$FILE" ]; then
rg -n "from 'typeorm'" "$FILE" || true
rg -n "createdAt|CreateDateColumn|timestamptz|timestamp" "$FILE" || true
fiRepository: rocket-admin/rocketadmin
Length of output: 200
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="backend/src/entities/email/email-verification.entity.ts"
if [ -f "$FILE" ]; then
echo "=== $FILE (first 200 lines with numbers) ==="
awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,200p'
else
echo "File not found: $FILE" >&2
fi
echo "=== Search: findVerificationWithVerificationString ==="
# Show matches and the surrounding implementation for each defining/using file
rg -n "findVerificationWithVerificationString" backend || true
for f in $(rg -n "findVerificationWithVerificationString" backend -l || true); do
echo "=== $f (context) ==="
rg -n "findVerificationWithVerificationString" "$f" -C 5 || true
done
echo "=== TypeORM import / timestamp usage in $FILE ==="
if [ -f "$FILE" ]; then
rg -n "from 'typeorm'" "$FILE" || true
rg -n "createdAt|CreateDateColumn|timestamptz|timestamp" "$FILE" || true
fiRepository: rocket-admin/rocketadmin
Length of output: 3699
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Show email-verification-custom-repository-extension.ts (relevant parts) ==="
FILE="backend/src/entities/email/repository/email-verification-custom-repository-extension.ts"
if [ -f "$FILE" ]; then
awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'
else
echo "File not found: $FILE" >&2
fi
echo "=== Search for ONE_DAY_AGO and createdAt window usage ==="
rg -n "ONE_DAY_AGO" backend || true
rg -n "createdAt" backend/src/entities/email backend/src/entities/user backend || true
rg -n "timestamptz|timestamp'|CURRENT_TIMESTAMP" backend/src/entities/email backend || trueRepository: rocket-admin/rocketadmin
Length of output: 50380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Print ONE_DAY_AGO implementation
FILE="backend/src/helpers/constants/constants.ts"
if [ -f "$FILE" ]; then
echo "=== ONE_DAY_AGO in $FILE ==="
rg -n "ONE_DAY_AGO" "$FILE"
# Show nearby lines around ONE_DAY_AGO (limit output)
rg -n -C 5 "ONE_DAY_AGO" "$FILE"
else
echo "File not found: $FILE" >&2
fi
# Inspect migration that adds createdAt to email_verification/password_reset
MIG="backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts"
if [ -f "$MIG" ]; then
echo "=== Migration $MIG ==="
awk '{printf "%5d\t%s\n", NR, $0}' "$MIG" | sed -n '1,120p'
else
echo "File not found: $MIG" >&2
fiRepository: rocket-admin/rocketadmin
Length of output: 1505
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Print ONE_DAY_AGO implementation
FILE="backend/src/helpers/constants/constants.ts"
if [ -f "$FILE" ]; then
echo "=== ONE_DAY_AGO in $FILE ==="
rg -n "ONE_DAY_AGO" "$FILE"
rg -n -C 5 "ONE_DAY_AGO" "$FILE"
else
echo "File not found: $FILE" >&2
fi
# Inspect migration that adds createdAt to email_verification/password_reset
MIG="backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts"
if [ -f "$MIG" ]; then
echo "=== Migration $MIG ==="
awk '{printf "%5d\t%s\n", NR, $0}' "$MIG" | sed -n '1,120p'
else
echo "File not found: $MIG" >&2
fiRepository: rocket-admin/rocketadmin
Length of output: 1505
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "synchronize\\s*:\\s*(true|false)" backend || true
rg -n "typeorm.*synchronize|synchronize\\s*:" backend || true
rg -n "dataSource|DataSourceOptions|ormconfig" backend -S || true
# List likely typeorm config files
fd -t f "ormconfig*.{js,ts}" backend || true
fd -t f "*data-source*.{js,ts}" backend || true
fd -t f "*typeorm*.{js,ts}" backend || trueRepository: rocket-admin/rocketadmin
Length of output: 13261
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "synchronize\\s*:\\s*(true|false)" backend || true
rg -n "typeorm.*synchronize|synchronize\\s*:" backend || true
rg -n "dataSource|DataSourceOptions|ormconfig" backend -S || true
fd -t f "ormconfig*.{js,ts}" backend || true
fd -t f "*data-source*.{js,ts}" backend || true
fd -t f "*typeorm*.{js,ts}" backend || trueRepository: rocket-admin/rocketadmin
Length of output: 13261
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "type:\\s*['\"](postgres|mysql|mariadb|mssql|sqlite|cockroachdb)['\"]" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts backend/src/shared/database -S || true
rg -n "postgres|mysql|mariadb" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts -S || true
# show the getTypeOrmConfig block around baseConfig creation (limit)
FILE="backend/src/shared/config/app-config.ts"
if [ -f "$FILE" ]; then
echo "=== app-config.ts around getTypeOrmConfig ==="
rg -n "getTypeOrmConfig" "$FILE"
# print lines around 296-340
awk 'NR>=240 && NR<=360 {print NR"\t"$0}' "$FILE" | sed -n '1,140p'
fiRepository: rocket-admin/rocketadmin
Length of output: 5371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "type:\\s*['\"](postgres|mysql|mariadb|mssql|sqlite|cockroachdb)['\"]" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts backend/src/shared/database -S || true
rg -n "postgres|mysql|mariadb" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts -S || true
FILE="backend/src/shared/config/app-config.ts"
if [ -f "$FILE" ]; then
echo "=== app-config.ts around getTypeOrmConfig ==="
awk 'NR>=240 && NR<=360 {print NR"\t"$0}' "$FILE" | sed -n '1,140p'
fiRepository: rocket-admin/rocketadmin
Length of output: 5319
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "timestamptz" backend || true
rg -n "`@CreateDateColumn`\\(\\{[^}]*type:\\s*'timestamp'" backend/src/entities || trueRepository: rocket-admin/rocketadmin
Length of output: 1256
Prefer @CreateDateColumn() for createdAt (keep DB type aligned).
- Replace the manual
@Column(... DEFAULT CURRENT_TIMESTAMP)with@CreateDateColumn()for idiomatic insert-time population. email_verification.createdAtis currentlyTIMESTAMP ... DEFAULT now()(and is used for the 1-day cutoff viacreatedAt >= Constants.ONE_DAY_AGO()), so switch totype: 'timestamp'to match the existing schema; only usetype: 'timestamptz'after adding a migration to alter the column type.
♻️ Proposed change
-import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, Relation } from 'typeorm';
+import { Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, Relation } from 'typeorm';
...
- `@Column`({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
- createdAt: Date;
+ `@CreateDateColumn`({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+ createdAt: Date;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/entities/email/email-verification.entity.ts` around lines 12 -
13, Replace the manual createdAt column declaration with TypeORM's
`@CreateDateColumn` to let the ORM populate insert time while keeping the DB type
aligned: in the EmailVerification entity (email_verification.createdAt /
createdAt property) swap the `@Column`({ type: 'timestamp', default: () =>
'CURRENT_TIMESTAMP' }) declaration to `@CreateDateColumn`({ type: 'timestamp' })
so the runtime uses insert-time population and retains the existing TIMESTAMP
semantics used by your ONE_DAY_AGO cutoff; do not change to 'timestamptz' here —
only migrate the column type after adding an explicit DB migration if you later
decide to use timestamptz.
Summary by CodeRabbit