Spaces:
Running
Running
Commit ·
1687bb1
1
Parent(s): 683d259
feat(auth): add case-insensitive email matching and return user in auth response
Browse files- Normalize email to lowercase for all auth operations to ensure case-insensitive matching
- Include user profile data in login/refresh token response payload
- Add error handling for missing user profiles in inactivity monitoring
- Standardize import quotes to double quotes in monitoring scheduler
src/auth/auth.service.ts
CHANGED
|
@@ -54,7 +54,10 @@ export class AuthService {
|
|
| 54 |
|
| 55 |
// Find user — build conditions dynamically to avoid empty {} matching all rows
|
| 56 |
const conditions: any[] = [];
|
| 57 |
-
if (email)
|
|
|
|
|
|
|
|
|
|
| 58 |
if (phone) conditions.push({ phone });
|
| 59 |
|
| 60 |
const user = await this.prisma.userAuth.findFirst({
|
|
@@ -148,7 +151,11 @@ export class AuthService {
|
|
| 148 |
|
| 149 |
// Check for existing user — build conditions dynamically
|
| 150 |
const conditions: any[] = [];
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
if (phone) conditions.push({ phone });
|
| 153 |
|
| 154 |
const existingUser = await this.prisma.userAuth.findFirst({
|
|
@@ -168,7 +175,7 @@ export class AuthService {
|
|
| 168 |
try {
|
| 169 |
const user = await this.prisma.userAuth.create({
|
| 170 |
data: {
|
| 171 |
-
email,
|
| 172 |
phone,
|
| 173 |
passwordHash,
|
| 174 |
},
|
|
@@ -202,7 +209,10 @@ export class AuthService {
|
|
| 202 |
|
| 203 |
// Find user — build conditions dynamically
|
| 204 |
const loginConditions: any[] = [];
|
| 205 |
-
if (email)
|
|
|
|
|
|
|
|
|
|
| 206 |
if (phone) loginConditions.push({ phone });
|
| 207 |
|
| 208 |
const user = await this.prisma.userAuth.findFirst({
|
|
@@ -308,15 +318,22 @@ export class AuthService {
|
|
| 308 |
});
|
| 309 |
|
| 310 |
// Store refresh token in database
|
| 311 |
-
await this.prisma.userAuth.update({
|
| 312 |
where: { id: userId },
|
| 313 |
data: { refreshToken },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
});
|
| 315 |
|
| 316 |
return {
|
| 317 |
accessToken,
|
| 318 |
refreshToken,
|
| 319 |
userId,
|
|
|
|
| 320 |
};
|
| 321 |
}
|
| 322 |
|
|
|
|
| 54 |
|
| 55 |
// Find user — build conditions dynamically to avoid empty {} matching all rows
|
| 56 |
const conditions: any[] = [];
|
| 57 |
+
if (email)
|
| 58 |
+
conditions.push({
|
| 59 |
+
email: { equals: email.toLowerCase(), mode: "insensitive" },
|
| 60 |
+
});
|
| 61 |
if (phone) conditions.push({ phone });
|
| 62 |
|
| 63 |
const user = await this.prisma.userAuth.findFirst({
|
|
|
|
| 151 |
|
| 152 |
// Check for existing user — build conditions dynamically
|
| 153 |
const conditions: any[] = [];
|
| 154 |
+
const normalizedEmail = email?.toLowerCase();
|
| 155 |
+
if (normalizedEmail)
|
| 156 |
+
conditions.push({
|
| 157 |
+
email: { equals: normalizedEmail, mode: "insensitive" },
|
| 158 |
+
});
|
| 159 |
if (phone) conditions.push({ phone });
|
| 160 |
|
| 161 |
const existingUser = await this.prisma.userAuth.findFirst({
|
|
|
|
| 175 |
try {
|
| 176 |
const user = await this.prisma.userAuth.create({
|
| 177 |
data: {
|
| 178 |
+
email: normalizedEmail,
|
| 179 |
phone,
|
| 180 |
passwordHash,
|
| 181 |
},
|
|
|
|
| 209 |
|
| 210 |
// Find user — build conditions dynamically
|
| 211 |
const loginConditions: any[] = [];
|
| 212 |
+
if (email)
|
| 213 |
+
loginConditions.push({
|
| 214 |
+
email: { equals: email.toLowerCase(), mode: "insensitive" },
|
| 215 |
+
});
|
| 216 |
if (phone) loginConditions.push({ phone });
|
| 217 |
|
| 218 |
const user = await this.prisma.userAuth.findFirst({
|
|
|
|
| 318 |
});
|
| 319 |
|
| 320 |
// Store refresh token in database
|
| 321 |
+
const user = await this.prisma.userAuth.update({
|
| 322 |
where: { id: userId },
|
| 323 |
data: { refreshToken },
|
| 324 |
+
select: {
|
| 325 |
+
id: true,
|
| 326 |
+
email: true,
|
| 327 |
+
phone: true,
|
| 328 |
+
isActive: true,
|
| 329 |
+
},
|
| 330 |
});
|
| 331 |
|
| 332 |
return {
|
| 333 |
accessToken,
|
| 334 |
refreshToken,
|
| 335 |
userId,
|
| 336 |
+
user,
|
| 337 |
};
|
| 338 |
}
|
| 339 |
|
src/auth/dto/auth-response.dto.ts
CHANGED
|
@@ -7,4 +7,5 @@ export class AuthResponseDto {
|
|
| 7 |
accessToken!: string;
|
| 8 |
refreshToken!: string;
|
| 9 |
userId!: string;
|
|
|
|
| 10 |
}
|
|
|
|
| 7 |
accessToken!: string;
|
| 8 |
refreshToken!: string;
|
| 9 |
userId!: string;
|
| 10 |
+
user?: any;
|
| 11 |
}
|
src/monitoring-engine/monitoring-scheduler.service.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
| 1 |
-
import { Injectable, Logger } from
|
| 2 |
-
import { Cron, CronExpression } from
|
| 3 |
-
import { PrismaService } from
|
| 4 |
-
import { MonitoringEngineService } from
|
| 5 |
import {
|
| 6 |
FollowUpTaskStatus,
|
| 7 |
FollowUpTaskType,
|
| 8 |
MonitoringState,
|
| 9 |
-
} from
|
| 10 |
-
import { NotificationsService } from
|
| 11 |
-
import { UserProfileService } from
|
| 12 |
-
import { AppNotificationType } from
|
| 13 |
|
| 14 |
@Injectable()
|
| 15 |
export class MonitoringSchedulerService {
|
|
@@ -24,7 +24,7 @@ export class MonitoringSchedulerService {
|
|
| 24 |
|
| 25 |
@Cron(CronExpression.EVERY_HOUR)
|
| 26 |
async handleOverdueFollowUps() {
|
| 27 |
-
this.logger.log(
|
| 28 |
const now = new Date();
|
| 29 |
|
| 30 |
const overdueTasks = await this.prisma.followUpTask.findMany({
|
|
@@ -52,7 +52,7 @@ export class MonitoringSchedulerService {
|
|
| 52 |
// Trigger notification
|
| 53 |
await this.notificationsService.sendPushNotification(
|
| 54 |
task.userId,
|
| 55 |
-
|
| 56 |
`Your ${task.type} follow-up task was missed. Current state: ${result.state}.`,
|
| 57 |
{ type: AppNotificationType.FOLLOW_UP_MISSED },
|
| 58 |
);
|
|
@@ -62,9 +62,9 @@ export class MonitoringSchedulerService {
|
|
| 62 |
);
|
| 63 |
}
|
| 64 |
|
| 65 |
-
@Cron(
|
| 66 |
async handleInactivity() {
|
| 67 |
-
this.logger.log(
|
| 68 |
const now = new Date();
|
| 69 |
const fiveDaysAgo = new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000);
|
| 70 |
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
@@ -73,7 +73,7 @@ export class MonitoringSchedulerService {
|
|
| 73 |
select: {
|
| 74 |
id: true,
|
| 75 |
bloodPressureReadings: {
|
| 76 |
-
orderBy: { recordedAt:
|
| 77 |
take: 1,
|
| 78 |
},
|
| 79 |
},
|
|
@@ -83,16 +83,25 @@ export class MonitoringSchedulerService {
|
|
| 83 |
const lastBpReading = user.bloodPressureReadings[0];
|
| 84 |
if (!lastBpReading) {
|
| 85 |
// No BP ever logged, send initial reminder after 5 days of account creation
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
await this.notificationsService.sendPushNotification(
|
| 89 |
user.id,
|
| 90 |
-
'BP Log Reminder',
|
| 91 |
-
"You haven't logged your blood pressure yet. Please log your first reading.",
|
| 92 |
-
{ type: AppNotificationType.INACTIVITY_REMINDER },
|
| 93 |
);
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
);
|
| 97 |
}
|
| 98 |
continue;
|
|
@@ -102,7 +111,7 @@ export class MonitoringSchedulerService {
|
|
| 102 |
// No BP logged in >7 days -> escalate messaging urgency
|
| 103 |
await this.notificationsService.sendPushNotification(
|
| 104 |
user.id,
|
| 105 |
-
|
| 106 |
"It's been over 7 days since your last blood pressure reading. Please log it now.",
|
| 107 |
{ type: AppNotificationType.URGENT_INACTIVITY_REMINDER },
|
| 108 |
);
|
|
@@ -113,7 +122,7 @@ export class MonitoringSchedulerService {
|
|
| 113 |
// No BP logged in >5 days -> send reminder
|
| 114 |
await this.notificationsService.sendPushNotification(
|
| 115 |
user.id,
|
| 116 |
-
|
| 117 |
"It's been over 5 days since your last blood pressure reading. Please log it soon.",
|
| 118 |
{ type: AppNotificationType.INACTIVITY_REMINDER },
|
| 119 |
);
|
|
@@ -122,6 +131,6 @@ export class MonitoringSchedulerService {
|
|
| 122 |
);
|
| 123 |
}
|
| 124 |
}
|
| 125 |
-
this.logger.log(
|
| 126 |
}
|
| 127 |
}
|
|
|
|
| 1 |
+
import { Injectable, Logger } from "@nestjs/common";
|
| 2 |
+
import { Cron, CronExpression } from "@nestjs/schedule";
|
| 3 |
+
import { PrismaService } from "../database/prisma.service";
|
| 4 |
+
import { MonitoringEngineService } from "./monitoring-engine.service";
|
| 5 |
import {
|
| 6 |
FollowUpTaskStatus,
|
| 7 |
FollowUpTaskType,
|
| 8 |
MonitoringState,
|
| 9 |
+
} from "@prisma/client";
|
| 10 |
+
import { NotificationsService } from "../notifications/notifications.service";
|
| 11 |
+
import { UserProfileService } from "../user-profile/user-profile.service";
|
| 12 |
+
import { AppNotificationType } from "../common/enums/app-notification-type.enum";
|
| 13 |
|
| 14 |
@Injectable()
|
| 15 |
export class MonitoringSchedulerService {
|
|
|
|
| 24 |
|
| 25 |
@Cron(CronExpression.EVERY_HOUR)
|
| 26 |
async handleOverdueFollowUps() {
|
| 27 |
+
this.logger.log("Checking for overdue follow-up tasks...");
|
| 28 |
const now = new Date();
|
| 29 |
|
| 30 |
const overdueTasks = await this.prisma.followUpTask.findMany({
|
|
|
|
| 52 |
// Trigger notification
|
| 53 |
await this.notificationsService.sendPushNotification(
|
| 54 |
task.userId,
|
| 55 |
+
"Follow-up Missed",
|
| 56 |
`Your ${task.type} follow-up task was missed. Current state: ${result.state}.`,
|
| 57 |
{ type: AppNotificationType.FOLLOW_UP_MISSED },
|
| 58 |
);
|
|
|
|
| 62 |
);
|
| 63 |
}
|
| 64 |
|
| 65 |
+
@Cron("0 9 * * *") // Every day at 9 AM
|
| 66 |
async handleInactivity() {
|
| 67 |
+
this.logger.log("Checking for user inactivity...");
|
| 68 |
const now = new Date();
|
| 69 |
const fiveDaysAgo = new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000);
|
| 70 |
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
|
| 73 |
select: {
|
| 74 |
id: true,
|
| 75 |
bloodPressureReadings: {
|
| 76 |
+
orderBy: { recordedAt: "desc" },
|
| 77 |
take: 1,
|
| 78 |
},
|
| 79 |
},
|
|
|
|
| 83 |
const lastBpReading = user.bloodPressureReadings[0];
|
| 84 |
if (!lastBpReading) {
|
| 85 |
// No BP ever logged, send initial reminder after 5 days of account creation
|
| 86 |
+
try {
|
| 87 |
+
const userProfile = await this.userProfileService.findByUserId(
|
|
|
|
| 88 |
user.id,
|
|
|
|
|
|
|
|
|
|
| 89 |
);
|
| 90 |
+
if (userProfile && userProfile.createdAt < fiveDaysAgo) {
|
| 91 |
+
await this.notificationsService.sendPushNotification(
|
| 92 |
+
user.id,
|
| 93 |
+
"BP Log Reminder",
|
| 94 |
+
"You haven't logged your blood pressure yet. Please log your first reading.",
|
| 95 |
+
{ type: AppNotificationType.INACTIVITY_REMINDER },
|
| 96 |
+
);
|
| 97 |
+
this.logger.log(
|
| 98 |
+
`Inactivity reminder sent to user ${user.id} (no BP ever logged).`,
|
| 99 |
+
);
|
| 100 |
+
}
|
| 101 |
+
} catch (error) {
|
| 102 |
+
// If profile not found, skip for now. Profiling is needed for notifications.
|
| 103 |
+
this.logger.debug(
|
| 104 |
+
`Skipping inactivity check for user ${user.id}: No profile found.`,
|
| 105 |
);
|
| 106 |
}
|
| 107 |
continue;
|
|
|
|
| 111 |
// No BP logged in >7 days -> escalate messaging urgency
|
| 112 |
await this.notificationsService.sendPushNotification(
|
| 113 |
user.id,
|
| 114 |
+
"Urgent BP Reminder",
|
| 115 |
"It's been over 7 days since your last blood pressure reading. Please log it now.",
|
| 116 |
{ type: AppNotificationType.URGENT_INACTIVITY_REMINDER },
|
| 117 |
);
|
|
|
|
| 122 |
// No BP logged in >5 days -> send reminder
|
| 123 |
await this.notificationsService.sendPushNotification(
|
| 124 |
user.id,
|
| 125 |
+
"BP Log Reminder",
|
| 126 |
"It's been over 5 days since your last blood pressure reading. Please log it soon.",
|
| 127 |
{ type: AppNotificationType.INACTIVITY_REMINDER },
|
| 128 |
);
|
|
|
|
| 131 |
);
|
| 132 |
}
|
| 133 |
}
|
| 134 |
+
this.logger.log("Finished checking for user inactivity.");
|
| 135 |
}
|
| 136 |
}
|