Skip to content

Commit ee687b0

Browse files
TarasBlatnoiroot
authored andcommitted
feat: made handling of timezones
1 parent 59c5ab4 commit ee687b0

5 files changed

Lines changed: 68 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "Game" ALTER COLUMN "dices" SET DEFAULT '';
3+
4+
-- AlterTable
5+
ALTER TABLE "User" ADD COLUMN "timezone" TEXT NOT NULL DEFAULT 'UTC';

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ model User {
2121
isEmailConfirmed Boolean @default(false)
2222
emailConfirmationToken String @default("")
2323
24+
timezone String @default("UTC")
25+
2426
requestedBy Follow[] @relation("requestedBy")
2527
requested Follow[] @relation("requested")
2628
sentMessages Message[] @relation("sentMessages")

src/auth/auth.service.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class AuthService {
4747
email: dto.email,
4848
hash,
4949
emailConfirmationToken,
50+
timezone: dto.timezone,
5051
},
5152
});
5253
this.emailService.sendVerificationEmail(
@@ -58,6 +59,7 @@ export class AuthService {
5859
id: user.id,
5960
nickname: user.nickname,
6061
isEmailConfirmed: user.isEmailConfirmed,
62+
timezone: user.timezone,
6163
};
6264
} catch (error) {
6365
if (error instanceof PrismaClientKnownRequestError) {
@@ -87,10 +89,15 @@ export class AuthService {
8789
throw new ForbiddenException('Password is incorrect');
8890
}
8991
const tokens = await this.signTokens(user.id, user.email);
90-
await this.updateRtHash(user.id, tokens.refreshToken);
92+
93+
if (dto.timezone && dto.timezone !== user.timezone) {
94+
await this.updateLoginData(user.id, tokens.refreshToken, dto.timezone);
95+
} else {
96+
await this.updateLoginData(user.id, tokens.refreshToken);
97+
}
9198
return {
9299
tokens,
93-
user: { email: user.email, id: user.id },
100+
user: { email: user.email, id: user.id, timezone: user.timezone },
94101
};
95102
}
96103

@@ -101,6 +108,7 @@ export class AuthService {
101108
email: userFromDB.email,
102109
id: userFromDB.id,
103110
isEmailConfirmed: userFromDB.isEmailConfirmed,
111+
timezone: userFromDB.timezone,
104112
};
105113
}
106114

@@ -127,7 +135,7 @@ export class AuthService {
127135
if (!rtMatches) throw new ForbiddenException('Rt token does not match');
128136

129137
const tokens = await this.signTokens(user.id, user.email);
130-
await this.updateRtHash(user.id, tokens.refreshToken);
138+
await this.updateLoginData(user.id, tokens.refreshToken);
131139
return tokens;
132140
}
133141

@@ -173,9 +181,9 @@ export class AuthService {
173181
});
174182

175183
const tokens = await this.signTokens(user.id, user.email);
176-
await this.updateRtHash(user.id, tokens.refreshToken);
184+
await this.updateLoginData(user.id, tokens.refreshToken);
177185
this.sseService.sendToUser(user.id, { type: 'email_verified' });
178-
return { tokens, user: { email: user.email } };
186+
return { tokens, user: { email: user.email, timezone: user.timezone } };
179187
}
180188

181189
async changePassword(
@@ -262,15 +270,20 @@ export class AuthService {
262270
}
263271
}
264272

265-
async updateRtHash(id: string, rt: string) {
273+
async updateLoginData(id: string, rt: string, timezone?: string) {
266274
const hash = await argon2.hash(rt);
275+
const updateData = { hashedRt: hash } as {
276+
hashedRt: string;
277+
timezone?: string;
278+
};
279+
if (timezone) {
280+
updateData.timezone = timezone;
281+
}
267282
await this.userRepository.update({
268283
where: {
269284
id,
270285
},
271-
data: {
272-
hashedRt: hash,
273-
},
286+
data: updateData,
274287
});
275288
}
276289

src/auth/dto/auth.dto.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
1+
import { IsEmail, IsNotEmpty, IsString, IsTimeZone } from 'class-validator';
22

33
export class SignUpDto {
44
@IsEmail()
@@ -12,6 +12,10 @@ export class SignUpDto {
1212
@IsString()
1313
@IsNotEmpty()
1414
nickname: string;
15+
16+
@IsString()
17+
@IsTimeZone()
18+
timezone: string;
1519
}
1620

1721
export class SignInDto {
@@ -22,6 +26,10 @@ export class SignInDto {
2226
@IsString()
2327
@IsNotEmpty()
2428
password: string;
29+
30+
@IsString()
31+
@IsTimeZone()
32+
timezone: string;
2533
}
2634

2735
export class ChangePasswordDto {

src/auth/dto/isTimezone.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {
2+
registerDecorator,
3+
ValidationOptions,
4+
ValidationArguments,
5+
} from 'class-validator';
6+
7+
export function IsTimeZone(validationOptions?: ValidationOptions) {
8+
return function (object: object, propertyName: string) {
9+
registerDecorator({
10+
name: 'isTimeZone',
11+
target: object.constructor,
12+
propertyName,
13+
options: validationOptions,
14+
validator: {
15+
validate(value: any) {
16+
if (typeof value !== 'string') return false;
17+
try {
18+
new Intl.DateTimeFormat('en-US', { timeZone: value });
19+
return true;
20+
} catch {
21+
return false;
22+
}
23+
},
24+
defaultMessage(args: ValidationArguments) {
25+
return `${args.property} must be a valid IANA time zone identifier`;
26+
},
27+
},
28+
});
29+
};
30+
}

0 commit comments

Comments
 (0)