Compare commits
No commits in common. "c787caa821d845438b8723e90327a55d97bcda07" and "797c970d81edc506ab09b2aab91dae7e64ed2d32" have entirely different histories.
c787caa821
...
797c970d81
7 changed files with 62 additions and 4916 deletions
|
|
@ -11,7 +11,6 @@
|
||||||
"@auth/drizzle-adapter": "^1.11.2",
|
"@auth/drizzle-adapter": "^1.11.2",
|
||||||
"@aws-sdk/client-s3": "^3.1045.0",
|
"@aws-sdk/client-s3": "^3.1045.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.1045.0",
|
"@aws-sdk/s3-request-presigner": "^3.1045.0",
|
||||||
"@react-pdf/renderer": "^4.5.1",
|
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|
@ -20,7 +19,6 @@
|
||||||
"nanoid": "^5.1.11",
|
"nanoid": "^5.1.11",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
"next-auth": "5.0.0-beta.31",
|
"next-auth": "5.0.0-beta.31",
|
||||||
"next-pwa": "^5.6.0",
|
|
||||||
"nodemailer": "^7.0.13",
|
"nodemailer": "^7.0.13",
|
||||||
"openai": "^6.37.0",
|
"openai": "^6.37.0",
|
||||||
"postgres": "^3.4.9",
|
"postgres": "^3.4.9",
|
||||||
|
|
@ -28,9 +26,6 @@
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-chartjs-2": "^5.3.1",
|
"react-chartjs-2": "^5.3.1",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"recharts": "^3.8.1",
|
|
||||||
"resend": "^6.12.3",
|
|
||||||
"sharp": "^0.34.5",
|
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -39,7 +34,6 @@
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@types/sharp": "^0.32.0",
|
|
||||||
"drizzle-kit": "^0.31.10",
|
"drizzle-kit": "^0.31.10",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
|
|
|
||||||
4875
pnpm-lock.yaml
generated
4875
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
1
src/app/api/admin/test/route.ts
Normal file
1
src/app/api/admin/test/route.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import { NextResponse } from "next/server"; export async function GET() { return NextResponse.json({ test: "success" }); }
|
||||||
|
|
@ -2,7 +2,6 @@ import { NextResponse } from "next/server";
|
||||||
import { sql } from "@/db";
|
import { sql } from "@/db";
|
||||||
import { detectMedicalIntent, ESCALATION_RULES } from "@/lib/ai/medical-triggers";
|
import { detectMedicalIntent, ESCALATION_RULES } from "@/lib/ai/medical-triggers";
|
||||||
import { logAudit } from "@/lib/audit";
|
import { logAudit } from "@/lib/audit";
|
||||||
import { requireFamily } from "@/lib/auth";
|
|
||||||
|
|
||||||
const LITELLM_URL = process.env.LITELLM_BASE_URL;
|
const LITELLM_URL = process.env.LITELLM_BASE_URL;
|
||||||
const LITELLM_KEY = process.env.LITELLM_API_KEY;
|
const LITELLM_KEY = process.env.LITELLM_API_KEY;
|
||||||
|
|
@ -14,15 +13,6 @@ export async function POST(request: Request) {
|
||||||
return NextResponse.json({ error: "AI service not configured" }, { status: 503 });
|
return NextResponse.json({ error: "AI service not configured" }, { status: 503 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require authentication
|
|
||||||
const auth = await requireFamily();
|
|
||||||
if (!auth.success) {
|
|
||||||
return NextResponse.json({ error: auth.error }, { status: auth.status });
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = auth.session!;
|
|
||||||
const familyId = session.familyId!;
|
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { messages, childId } = body;
|
const { messages, childId } = body;
|
||||||
|
|
||||||
|
|
@ -35,10 +25,11 @@ export async function POST(request: Request) {
|
||||||
// HARD GUARDRAIL: Check for medical intent BEFORE calling LLM
|
// HARD GUARDRAIL: Check for medical intent BEFORE calling LLM
|
||||||
const intent = detectMedicalIntent(lastUserMsg);
|
const intent = detectMedicalIntent(lastUserMsg);
|
||||||
if (intent.isMedical) {
|
if (intent.isMedical) {
|
||||||
// Fetch pediatrician phone using familyId
|
// Fetch pediatrician phone
|
||||||
|
const sessionToken = request.headers.get("cookie")?.match(/tia_session=([^;]+)/)?.[1] || "";
|
||||||
const families = await sql`
|
const families = await sql`
|
||||||
SELECT pediatrician_phone FROM families
|
SELECT pediatrician_phone FROM families
|
||||||
WHERE id = ${familyId}
|
WHERE id IN (SELECT family_id FROM family_members WHERE user_id IN (SELECT user_id FROM sessions WHERE session_token = ${sessionToken}))
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`;
|
`;
|
||||||
const pediatricianPhone = families[0]?.pediatrician_phone;
|
const pediatricianPhone = families[0]?.pediatrician_phone;
|
||||||
|
|
@ -58,8 +49,6 @@ export async function POST(request: Request) {
|
||||||
action: "ai_medical_redirect",
|
action: "ai_medical_redirect",
|
||||||
metadata: { category: intent.category, keyword: intent.matchedKeyword },
|
metadata: { category: intent.category, keyword: intent.matchedKeyword },
|
||||||
request,
|
request,
|
||||||
userId: session.userId,
|
|
||||||
familyId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|
|
||||||
42
src/app/api/auth/debug/route.ts
Normal file
42
src/app/api/auth/debug/route.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { sql } from "@/db";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const sessionToken = (await cookies()).get("tia_session")?.value;
|
||||||
|
|
||||||
|
if (!sessionToken) {
|
||||||
|
return NextResponse.json({ error: "no cookie" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user from session
|
||||||
|
const sessions = await sql.unsafe(
|
||||||
|
`SELECT s.user_id FROM sessions s WHERE s.session_token = $1`,
|
||||||
|
[sessionToken]
|
||||||
|
);
|
||||||
|
|
||||||
|
const userId = sessions?.[0]?.user_id;
|
||||||
|
|
||||||
|
// Check families table
|
||||||
|
const families = await sql.unsafe(
|
||||||
|
`SELECT id, name FROM families ORDER BY created_at DESC LIMIT 5`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check family_members table
|
||||||
|
const members = await sql.unsafe(
|
||||||
|
`SELECT id, family_id, user_id, role FROM family_members ORDER BY created_at DESC LIMIT 5`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check children table
|
||||||
|
const children = await sql.unsafe(
|
||||||
|
`SELECT id, family_id, name FROM children ORDER BY created_at DESC LIMIT 5`
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
cookie: sessionToken?.slice(0, 20) + "...",
|
||||||
|
userId,
|
||||||
|
families,
|
||||||
|
members,
|
||||||
|
children
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,6 @@ import { NextResponse } from "next/server";
|
||||||
import { sql } from "@/db";
|
import { sql } from "@/db";
|
||||||
import { rateLimit, getClientIp, getRateLimitKey } from "@/lib/rate-limit";
|
import { rateLimit, getClientIp, getRateLimitKey } from "@/lib/rate-limit";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import { Resend } from "resend";
|
|
||||||
|
|
||||||
const RESEND_API_KEY = process.env.RESEND_API_KEY;
|
|
||||||
const EMAIL_FROM = process.env.EMAIL_FROM || "Tia <noreply@tia.manohargupta.com>";
|
|
||||||
const RESET_URL = process.env.NEXT_PUBLIC_APP_URL || "https://tia.manohargupta.com";
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const ip = getClientIp(request);
|
const ip = getClientIp(request);
|
||||||
|
|
@ -41,34 +36,8 @@ export async function POST(request: Request) {
|
||||||
[user.id, token, expiresAt.toISOString()]
|
[user.id, token, expiresAt.toISOString()]
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetLink = `${RESET_URL}/reset-password?token=${token}`;
|
// In production, send email with reset link
|
||||||
|
console.log(`[RESET-TOKEN] user=${user.id} email=${email} token=reset_${token} expires=${expiresAt.toISOString()}`);
|
||||||
// Send email via Resend if API key is configured
|
|
||||||
if (RESEND_API_KEY) {
|
|
||||||
try {
|
|
||||||
const resend = new Resend(RESEND_API_KEY);
|
|
||||||
await resend.emails.send({
|
|
||||||
from: EMAIL_FROM,
|
|
||||||
to: email,
|
|
||||||
subject: "Reset your Tia password",
|
|
||||||
html: `
|
|
||||||
<div style="font-family: system-ui, sans-serif; max-width: 500px; margin: 0 auto;">
|
|
||||||
<h2>Reset your Tia password</h2>
|
|
||||||
<p>Click the button below to reset your password. This link expires in 1 hour.</p>
|
|
||||||
<a href="${resetLink}" style="display: inline-block; background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 16px 0;">Reset Password</a>
|
|
||||||
<p style="color: #6b7280; font-size: 14px;">If you didn't request this, you can safely ignore this email.</p>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
console.log(`[RESET-EMAIL-SENT] user=${user.id} email=${email}`);
|
|
||||||
} catch (emailError) {
|
|
||||||
console.error("[RESET-EMAIL-FAILED]", emailError);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Development fallback
|
|
||||||
console.log(`[RESET-TOKEN] user=${user.id} email=${email} token=reset_${token} expires=${expiresAt.toISOString()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ success: true, message: "If email exists, reset link sent" });
|
return NextResponse.json({ success: true, message: "If email exists, reset link sent" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Reset request error:", error);
|
console.error("Reset request error:", error);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ const publicRoutes = [
|
||||||
"/api/auth/signin",
|
"/api/auth/signin",
|
||||||
"/api/admin/auth",
|
"/api/admin/auth",
|
||||||
"/api/onboarding",
|
"/api/onboarding",
|
||||||
|
"/api/ai",
|
||||||
|
"/api/auth/debug",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Protected API routes that need authentication
|
// Protected API routes that need authentication
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue