mirror of
https://github.com/temetro/temetro.git
synced 2026-08-05 16:37:42 +00:00
90e6ec4cc0
Chat: history pill now shows a History icon + a Start-new-chat (SquarePen) button; removed the duplicate chat-history list from the sidebar. Email: deployment-wide email provider config (Resend/Postmark/SendGrid/SMTP) in Settings → Developers, with encrypted API key and a Send-test action. sendEmail dispatches via the chosen provider (REST via fetch; SMTP via nodemailer). Forgot password with no provider: alert the clinic admin(s) via a "System" message card in Messages + a bell notification (seeded system user + per-clinic System conversation); clicking deep-links to /settings?tab=careTeam&member=<id>. Admins can set a member's password directly from the employee dialog (PATCH /api/staff/:id/password via Better Auth's internal context — no admin plugin needed). Patient Portal: "New patient" booking path registers a demographics-only patient then books; bookings reject double-booked slots (409). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import { and, eq, inArray } from "drizzle-orm";
|
|
|
|
import { db } from "../db/index.js";
|
|
import { member } from "../db/schema/auth.js";
|
|
import { emitToUser } from "../realtime.js";
|
|
import { createSystemMessage } from "./messaging.js";
|
|
import { createNotification } from "./notifications.js";
|
|
|
|
// When an employee asks for a password reset but no email provider is configured,
|
|
// alert the admin(s) of each clinic the user belongs to: a "System" message card
|
|
// in Messages + a bell notification, both deep-linking to that member's settings
|
|
// so an admin can set a new password. The reset URL is never exposed.
|
|
export async function notifyAdminsPasswordReset(u: {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
}): Promise<void> {
|
|
const orgs = await db
|
|
.select({ organizationId: member.organizationId })
|
|
.from(member)
|
|
.where(eq(member.userId, u.id));
|
|
const orgIds = [...new Set(orgs.map((o) => o.organizationId))];
|
|
|
|
for (const orgId of orgIds) {
|
|
try {
|
|
const admins = await db
|
|
.select({ userId: member.userId })
|
|
.from(member)
|
|
.where(
|
|
and(
|
|
eq(member.organizationId, orgId),
|
|
inArray(member.role, ["owner", "admin"]),
|
|
),
|
|
);
|
|
const adminIds = admins.map((a) => a.userId).filter((id) => id !== u.id);
|
|
if (adminIds.length === 0) continue;
|
|
|
|
const body = `${u.name} requested a password reset, but no email provider is configured. Reset their password from their settings.`;
|
|
const { message, recipientIds } = await createSystemMessage(
|
|
orgId,
|
|
adminIds,
|
|
body,
|
|
{
|
|
kind: "passwordReset",
|
|
userId: u.id,
|
|
userName: u.name,
|
|
userEmail: u.email,
|
|
},
|
|
);
|
|
for (const rid of recipientIds) emitToUser(rid, "message:new", message);
|
|
|
|
for (const rid of adminIds) {
|
|
const n = await createNotification({
|
|
orgId,
|
|
userId: rid,
|
|
type: "password_reset",
|
|
text: `${u.name} needs a password reset`,
|
|
entityType: "user",
|
|
entityId: u.id,
|
|
actorName: u.name,
|
|
});
|
|
if (n) emitToUser(rid, "notification:new", n);
|
|
}
|
|
} catch (err) {
|
|
console.error("password-reset fallback failed:", err);
|
|
}
|
|
}
|
|
}
|