Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/src/entities/email/email-verification.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export class EmailVerificationEntity {
@Column({ default: null })
verification_string: string;

@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
Comment on lines +12 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 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:


🏁 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
fi

Repository: 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
fi

Repository: 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
fi

Repository: 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 || true

Repository: 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
fi

Repository: 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
fi

Repository: 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 || true

Repository: 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 || true

Repository: 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'
fi

Repository: 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'
fi

Repository: 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 || true

Repository: 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.createdAt is currently TIMESTAMP ... DEFAULT now() (and is used for the 1-day cutoff via createdAt >= Constants.ONE_DAY_AGO()), so switch to type: 'timestamp' to match the existing schema; only use type: '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.


@OneToOne(
() => UserEntity,
(user) => user.email_verification,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Constants } from '../../../helpers/constants/constants.js';
import { Encryptor } from '../../../helpers/encryption/encryptor.js';
import { UserEntity } from '../../user/user.entity.js';
import { EmailVerificationEntity } from '../email-verification.entity.js';
Expand All @@ -8,6 +9,9 @@ export const emailVerificationRepositoryExtension = {
.leftJoinAndSelect('email_verification.user', 'user')
.where('email_verification.verification_string = :ver_string', {
ver_string: verificationString,
})
.andWhere('email_verification.createdAt >= :valid_after', {
valid_after: Constants.ONE_DAY_AGO(),
});
Comment on lines +12 to 15
return await qb.getOne();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export class PasswordResetEntity {
@Column({ default: null })
verification_string: string;

@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;

@OneToOne(
() => UserEntity,
(user) => user.password_reset,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Constants } from '../../../../helpers/constants/constants.js';
import { Encryptor } from '../../../../helpers/encryption/encryptor.js';
import { UserEntity } from '../../user.entity.js';
import { PasswordResetEntity } from '../password-reset.entity.js';
Expand All @@ -8,6 +9,9 @@ export const userPasswordResetCustomRepositoryExtension = {
.leftJoinAndSelect('password_reset.user', 'user')
.where('password_reset.verification_string = :ver_string', {
ver_string: verificationString,
})
.andWhere('password_reset.createdAt >= :valid_after', {
valid_after: Constants.ONE_DAY_AGO(),
});
Comment on lines 10 to 15
return await qb.getOne();
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities1780411916141
implements MigrationInterface
{
name = 'AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities1780411916141';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "email_verification" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`);
await queryRunner.query(`ALTER TABLE "password_reset" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "password_reset" DROP COLUMN "createdAt"`);
await queryRunner.query(`ALTER TABLE "email_verification" DROP COLUMN "createdAt"`);
}
}
Loading