Files
TX/scripts/src/seed.ts
T
Riyadh d148c013fd Task #121: Hide Executive Meetings icon from non-executive users
Gate the home-screen "Executive Meetings" app icon behind a new
`executive_meetings.access` permission so only admins and the five
executive_* roles see it. Reuses the existing `app_permissions` →
`getVisibleAppsForUser` machinery; no route or middleware changes.

Changes
- scripts/src/seed.ts
  * Added permission `executive_meetings.access`.
  * Granted it to: admin, executive_ceo, executive_office_manager,
    executive_coord_lead, executive_coordinator, executive_viewer.
  * Linked it to apps.slug = 'executive-meetings' via app_permissions.
  * All inserts use onConflictDoNothing — re-running the seeder is safe.
- artifacts/api-server/scripts/gate-executive-meetings.mjs (new)
  * One-shot, idempotent pg migration that performs the same three
    inserts in a single transaction (BEGIN/ROLLBACK on error). Prints
    JSON summary with grant counts. Already executed against dev DB
    (newRoleGrantsThisRun=6, newAppLinksThisRun=1).
- artifacts/api-server/tests/executive-meetings-visibility.test.mjs (new)
  * Regression: creates a fresh `order_receiver`-only user, asserts
    /api/apps does NOT include `executive-meetings`; then grants
    `executive_coordinator` and asserts it does. Per-run username
    prefix + defensive sweep + after-hook cleanup.

Out of scope (not modified)
- artifacts/api-server/src/routes/apps.ts (getVisibleAppsForUser)
- artifacts/api-server/src/routes/executive-meetings.ts
  (requireExecutiveAccess)
- Any UI files

Verification
- `node artifacts/api-server/scripts/gate-executive-meetings.mjs` →
  ok=true, 6 role grants, 1 app link.
- `node --test tests/executive-meetings-visibility.test.mjs` →
  2/2 pass.
- TypeScript clean for scripts package.
- Architect review: PASS, no blocking issues.
2026-04-29 07:13:55 +00:00

829 lines
26 KiB
TypeScript

import { db } from "@workspace/db";
import {
usersTable,
rolesTable,
userRolesTable,
permissionsTable,
rolePermissionsTable,
appsTable,
appPermissionsTable,
serviceCategoriesTable,
servicesTable,
groupsTable,
userGroupsTable,
groupAppsTable,
groupRolesTable,
executiveMeetingsTable,
executiveMeetingAttendeesTable,
executiveMeetingNotificationsTable,
} from "@workspace/db";
import { eq } from "drizzle-orm";
import bcrypt from "bcryptjs";
async function main() {
console.log("Seeding database...");
// Create roles
const [adminRole] = await db
.insert(rolesTable)
.values({ name: "admin", descriptionAr: "مدير النظام", descriptionEn: "System Administrator", isSystem: 1 })
.onConflictDoNothing()
.returning();
const [userRole] = await db
.insert(rolesTable)
.values({ name: "user", descriptionAr: "مستخدم عادي", descriptionEn: "Regular User", isSystem: 1 })
.onConflictDoNothing()
.returning();
console.log("Roles created");
// Create permissions
const permissions = [
{ name: "apps:manage", descriptionAr: "إدارة التطبيقات", descriptionEn: "Manage Apps" },
{ name: "services:manage", descriptionAr: "إدارة الخدمات", descriptionEn: "Manage Services" },
{ name: "users:manage", descriptionAr: "إدارة المستخدمين", descriptionEn: "Manage Users" },
{ name: "chat:access", descriptionAr: "الوصول للمحادثات", descriptionEn: "Access Chat" },
{ name: "orders.receive", descriptionAr: "استلام الطلبات", descriptionEn: "Receive service orders" },
];
await db.insert(permissionsTable).values(permissions).onConflictDoNothing();
const allInsertedPerms = await db.select().from(permissionsTable);
console.log("Permissions created");
// Create order_receiver role
const [orderReceiverRole] = await db
.insert(rolesTable)
.values({
name: "order_receiver",
descriptionAr: "مستلم الطلبات",
descriptionEn: "Order Receiver",
isSystem: 1,
})
.onConflictDoNothing()
.returning();
const allRoles = await db.select().from(rolesTable);
const adminRoleResolved = adminRole ?? allRoles.find((r) => r.name === "admin");
const userRoleResolved = userRole ?? allRoles.find((r) => r.name === "user");
const orderReceiverResolved =
orderReceiverRole ?? allRoles.find((r) => r.name === "order_receiver");
// Assign all permissions to admin role
if (adminRoleResolved && allInsertedPerms.length > 0) {
await db
.insert(rolePermissionsTable)
.values(allInsertedPerms.map((p) => ({ roleId: adminRoleResolved.id, permissionId: p.id })))
.onConflictDoNothing();
}
// Assign chat permission to user role
if (userRoleResolved && allInsertedPerms.length > 0) {
const chatPerm = allInsertedPerms.find((p) => p.name === "chat:access");
if (chatPerm) {
await db
.insert(rolePermissionsTable)
.values({ roleId: userRoleResolved.id, permissionId: chatPerm.id })
.onConflictDoNothing();
}
}
// Assign orders:receive permission to order_receiver role
if (orderReceiverResolved && allInsertedPerms.length > 0) {
const ordersPerm = allInsertedPerms.find((p) => p.name === "orders.receive");
if (ordersPerm) {
await db
.insert(rolePermissionsTable)
.values({ roleId: orderReceiverResolved.id, permissionId: ordersPerm.id })
.onConflictDoNothing();
}
}
// Create admin user
const adminHash = await bcrypt.hash("admin123", 10);
const [adminUser] = await db
.insert(usersTable)
.values({
username: "admin",
email: "admin@tx.local",
passwordHash: adminHash,
displayNameAr: "مدير النظام",
displayNameEn: "System Admin",
preferredLanguage: "ar",
isActive: true,
})
.onConflictDoNothing()
.returning();
// Create regular user
const userHash = await bcrypt.hash("user123", 10);
const [regularUser] = await db
.insert(usersTable)
.values({
username: "ahmed",
email: "ahmed@tx.local",
passwordHash: userHash,
displayNameAr: "أحمد محمد",
displayNameEn: "Ahmed Mohammed",
preferredLanguage: "ar",
isActive: true,
})
.onConflictDoNothing()
.returning();
console.log("Users created");
// Assign roles
const roles = await db.select().from(rolesTable);
const adminRoleRow = roles.find((r) => r.name === "admin");
const userRoleRow = roles.find((r) => r.name === "user");
if (adminUser && adminRoleRow) {
await db
.insert(userRolesTable)
.values({ userId: adminUser.id, roleId: adminRoleRow.id })
.onConflictDoNothing();
}
if (regularUser && userRoleRow) {
await db
.insert(userRolesTable)
.values({ userId: regularUser.id, roleId: userRoleRow.id })
.onConflictDoNothing();
}
console.log("Roles assigned");
// Create apps
const apps = [
{
slug: "services",
nameAr: "خدماتي",
nameEn: "My Services",
descriptionAr: "اطلب خدماتك الداخلية بسهولة",
descriptionEn: "Order your internal services easily",
iconName: "Coffee",
route: "/services",
color: "#f59e0b",
isActive: true,
isSystem: true,
sortOrder: 1,
},
{
slug: "chat",
nameAr: "المحادثات",
nameEn: "Chat",
descriptionAr: "تواصل مع زملائك",
descriptionEn: "Connect with your colleagues",
iconName: "MessageCircle",
route: "/chat",
color: "#22c55e",
isActive: true,
isSystem: true,
sortOrder: 2,
},
{
slug: "notifications",
nameAr: "الإشعارات",
nameEn: "Notifications",
descriptionAr: "إدارة الإشعارات والتنبيهات",
descriptionEn: "Manage your notifications and alerts",
iconName: "Bell",
route: "/notifications",
color: "#8b5cf6",
isActive: true,
isSystem: true,
sortOrder: 3,
},
{
slug: "admin",
nameAr: "لوحة التحكم",
nameEn: "Admin Panel",
descriptionAr: "إدارة النظام والمستخدمين",
descriptionEn: "System and user management",
iconName: "Settings",
route: "/admin",
color: "#ef4444",
isActive: true,
isSystem: true,
sortOrder: 4,
},
{
slug: "calendar",
nameAr: "التقويم",
nameEn: "Calendar",
descriptionAr: "الاجتماعات والمواعيد",
descriptionEn: "Meetings and appointments",
iconName: "Calendar",
route: "/calendar",
color: "#3b82f6",
isActive: true,
isSystem: false,
sortOrder: 5,
},
{
slug: "documents",
nameAr: "المستندات",
nameEn: "Documents",
descriptionAr: "مستودع المستندات والملفات",
descriptionEn: "Documents and files repository",
iconName: "FileText",
route: "/documents",
color: "#64748b",
isActive: true,
isSystem: false,
sortOrder: 6,
},
{
slug: "executive-meetings",
nameAr: "إدارة الاجتماعات التنفيذية",
nameEn: "Executive Meetings",
descriptionAr: "جدولة اجتماعات المكتب التنفيذي ومتابعتها",
descriptionEn: "Schedule and track executive office meetings",
iconName: "CalendarClock",
route: "/executive-meetings",
color: "#0B1E3F",
isActive: true,
isSystem: false,
sortOrder: 7,
},
];
const insertedApps = await db.insert(appsTable).values(apps).onConflictDoNothing().returning();
console.log("Apps created");
// Restrict admin app to users with apps:manage permission
const allPerms = await db.select().from(permissionsTable);
const appsManagePerm = allPerms.find((p) => p.name === "apps:manage");
const adminApp = insertedApps.find((a) => a.slug === "admin")
?? (await db.select().from(appsTable).where(eq(appsTable.slug, "admin")))[0];
if (adminApp && appsManagePerm) {
await db
.insert(appPermissionsTable)
.values({ appId: adminApp.id, permissionId: appsManagePerm.id })
.onConflictDoNothing();
console.log("App permissions set: admin app restricted to apps:manage permission");
}
// Create service categories
const categories = [
{ nameAr: "مشروبات", nameEn: "Beverages", sortOrder: 1 },
{ nameAr: "أغذية", nameEn: "Food", sortOrder: 2 },
{ nameAr: "أخرى", nameEn: "Other", sortOrder: 3 },
];
const insertedCategories = await db
.insert(serviceCategoriesTable)
.values(categories)
.onConflictDoNothing()
.returning();
const beveragesCat = insertedCategories[0];
// Create services
const services = [
{
categoryId: beveragesCat?.id ?? null,
nameAr: "شاي",
nameEn: "Tea",
price: "0.00",
isAvailable: true,
sortOrder: 1,
},
{
categoryId: beveragesCat?.id ?? null,
nameAr: "قهوة عربية",
nameEn: "Arabic Coffee",
price: "0.00",
isAvailable: true,
sortOrder: 2,
},
{
categoryId: beveragesCat?.id ?? null,
nameAr: "ماء",
nameEn: "Water",
price: "0.00",
isAvailable: true,
sortOrder: 3,
},
{
categoryId: beveragesCat?.id ?? null,
nameAr: "قهوة نسكافيه",
nameEn: "Nescafe",
price: "0.00",
isAvailable: true,
sortOrder: 4,
},
{
categoryId: beveragesCat?.id ?? null,
nameAr: "عصير طازج",
nameEn: "Fresh Juice",
price: "5.00",
isAvailable: true,
sortOrder: 5,
},
{
categoryId: beveragesCat?.id ?? null,
nameAr: "مشروب غازي",
nameEn: "Soft Drink",
price: "3.00",
isAvailable: false,
sortOrder: 6,
},
];
await db.insert(servicesTable).values(services).onConflictDoNothing({ target: servicesTable.nameEn });
console.log("Services created");
// ----- Groups: idempotent migration -----
await db
.insert(groupsTable)
.values([
{
name: "Admins",
descriptionAr: "مديرو النظام",
descriptionEn: "System administrators",
isSystem: 1,
},
{
name: "Tx",
descriptionAr: "فريق إعداد وتقديم الطلبات",
descriptionEn: "Team that prepares and delivers orders",
isSystem: 1,
},
{
name: "Everyone",
descriptionAr: "كل المستخدمين",
descriptionEn: "All users",
isSystem: 1,
},
])
.onConflictDoNothing();
// One-time migration: rename legacy "TeaBoy" group to "Tx" if it still exists
// and the new "Tx" group hasn't been created yet.
const existingGroups = await db.select().from(groupsTable);
const legacy = existingGroups.find((g) => g.name === "TeaBoy");
const newOne = existingGroups.find((g) => g.name === "Tx");
if (legacy && !newOne) {
await db
.update(groupsTable)
.set({ name: "Tx" })
.where(eq(groupsTable.id, legacy.id));
}
const allGroups = await db.select().from(groupsTable);
const adminsGroup = allGroups.find((g) => g.name === "Admins");
const txGroup = allGroups.find((g) => g.name === "Tx");
const everyoneGroup = allGroups.find((g) => g.name === "Everyone");
const allRolesNow = await db.select().from(rolesTable);
const adminRoleNow = allRolesNow.find((r) => r.name === "admin");
const orderReceiverNow = allRolesNow.find((r) => r.name === "order_receiver");
// Group → roles
if (adminsGroup && adminRoleNow) {
await db
.insert(groupRolesTable)
.values({ groupId: adminsGroup.id, roleId: adminRoleNow.id })
.onConflictDoNothing();
}
if (txGroup && orderReceiverNow) {
await db
.insert(groupRolesTable)
.values({ groupId: txGroup.id, roleId: orderReceiverNow.id })
.onConflictDoNothing();
}
// Group → apps
const allAppsNow = await db.select().from(appsTable);
if (adminsGroup) {
await db
.insert(groupAppsTable)
.values(allAppsNow.map((a) => ({ groupId: adminsGroup.id, appId: a.id })))
.onConflictDoNothing();
}
// The Tx receivers group gets the services app (where receivers see incoming orders)
if (txGroup) {
const servicesApp = allAppsNow.find((a) => a.slug === "services");
if (servicesApp) {
await db
.insert(groupAppsTable)
.values({ groupId: txGroup.id, appId: servicesApp.id })
.onConflictDoNothing();
}
}
// Map existing users to groups idempotently
const allUsersNow = await db.select({ id: usersTable.id }).from(usersTable);
if (everyoneGroup && allUsersNow.length > 0) {
await db
.insert(userGroupsTable)
.values(allUsersNow.map((u) => ({ userId: u.id, groupId: everyoneGroup.id })))
.onConflictDoNothing();
}
if (adminsGroup && adminRoleNow) {
const adminUserRows = await db
.select({ userId: userRolesTable.userId })
.from(userRolesTable)
.where(eq(userRolesTable.roleId, adminRoleNow.id));
if (adminUserRows.length > 0) {
await db
.insert(userGroupsTable)
.values(adminUserRows.map((r) => ({ userId: r.userId, groupId: adminsGroup.id })))
.onConflictDoNothing();
}
}
if (txGroup && orderReceiverNow) {
const receiverRows = await db
.select({ userId: userRolesTable.userId })
.from(userRolesTable)
.where(eq(userRolesTable.roleId, orderReceiverNow.id));
if (receiverRows.length > 0) {
await db
.insert(userGroupsTable)
.values(receiverRows.map((r) => ({ userId: r.userId, groupId: txGroup.id })))
.onConflictDoNothing();
}
}
console.log("Groups created and existing users mapped");
// Executive meetings: roles
const execRoles = [
{
name: "executive_ceo",
descriptionAr: "الرئيس التنفيذي",
descriptionEn: "Chief Executive Officer",
isSystem: 1,
},
{
name: "executive_office_manager",
descriptionAr: "مدير المكتب التنفيذي",
descriptionEn: "Executive Office Manager",
isSystem: 1,
},
{
name: "executive_coord_lead",
descriptionAr: "رئيس التنسيق والتوثيق",
descriptionEn: "Coordination Lead",
isSystem: 1,
},
{
name: "executive_coordinator",
descriptionAr: "منسق المكتب التنفيذي",
descriptionEn: "Executive Coordinator",
isSystem: 1,
},
{
name: "executive_viewer",
descriptionAr: "مشاهد الاجتماعات التنفيذية",
descriptionEn: "Executive Meetings Viewer",
isSystem: 1,
},
];
await db.insert(rolesTable).values(execRoles).onConflictDoNothing();
console.log("Executive Meetings roles created");
// Executive meetings: gate the home-screen icon to executive roles only.
// This adds a single permission, grants it to admin + the 5 executive
// roles, and links it to the `executive-meetings` app via app_permissions
// so getVisibleAppsForUser will hide the icon for everyone else.
// All three inserts are idempotent.
await db
.insert(permissionsTable)
.values({
name: "executive_meetings.access",
descriptionAr: "الوصول إلى وحدة الاجتماعات التنفيذية",
descriptionEn: "Access the Executive Meetings module",
})
.onConflictDoNothing();
const [execAccessPerm] = await db
.select()
.from(permissionsTable)
.where(eq(permissionsTable.name, "executive_meetings.access"));
if (execAccessPerm) {
const execRoleNames = [
"admin",
"executive_ceo",
"executive_office_manager",
"executive_coord_lead",
"executive_coordinator",
"executive_viewer",
];
const allRolesNow = await db.select().from(rolesTable);
const execRoleRows = allRolesNow.filter((r) => execRoleNames.includes(r.name));
if (execRoleRows.length > 0) {
await db
.insert(rolePermissionsTable)
.values(
execRoleRows.map((r) => ({
roleId: r.id,
permissionId: execAccessPerm.id,
})),
)
.onConflictDoNothing();
}
const [execApp] = await db
.select()
.from(appsTable)
.where(eq(appsTable.slug, "executive-meetings"));
if (execApp) {
await db
.insert(appPermissionsTable)
.values({ appId: execApp.id, permissionId: execAccessPerm.id })
.onConflictDoNothing();
console.log(
"App permissions set: executive-meetings app restricted to executive_meetings.access permission",
);
}
}
// Executive meetings: sample data for today (idempotent per-date)
const today = new Date().toISOString().slice(0, 10);
const existingForToday = await db
.select({ id: executiveMeetingsTable.id })
.from(executiveMeetingsTable)
.where(eq(executiveMeetingsTable.meetingDate, today));
if (existingForToday.length === 0) {
{
const sampleMeetings: Array<{
meeting: {
dailyNumber: number;
titleAr: string;
titleEn: string;
meetingDate: string;
startTime: string;
endTime: string;
platform: "none" | "webex" | "teams" | "zoom" | "other";
isHighlighted: number;
status: string;
};
attendees: Array<{
name: string;
attendanceType: "internal" | "virtual" | "external";
}>;
}> = [
{
meeting: {
dailyNumber: 1,
titleAr: "اجتماع افتتاحي للمكتب التنفيذي",
titleEn: "Executive Office Opening Meeting",
meetingDate: today,
startTime: "10:00",
endTime: "10:30",
platform: "none",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "أ. سعد العتيبي", attendanceType: "internal" },
{ name: "أ. خالد الشهري", attendanceType: "internal" },
],
},
{
meeting: {
dailyNumber: 2,
titleAr: "اجتماع بشأن خطة التحول الرقمي",
titleEn: "Digital Transformation Plan Meeting",
meetingDate: today,
startTime: "11:00",
endTime: "11:30",
platform: "webex",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "د. محمد الزهراني", attendanceType: "virtual" },
{ name: "أ. عبدالعزيز القحطاني", attendanceType: "virtual" },
{ name: "أ. ناصر الدوسري", attendanceType: "virtual" },
{ name: "م. فهد المالكي", attendanceType: "virtual" },
],
},
{
meeting: {
dailyNumber: 3,
titleAr: "اجتماع تحضيري لاجتماع مجلس الإدارة",
titleEn: "Board Meeting Preparation",
meetingDate: today,
startTime: "11:40",
endTime: "12:10",
platform: "none",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "أ. تركي السبيعي", attendanceType: "internal" },
{ name: "أ. ماجد الحربي", attendanceType: "internal" },
{ name: "أ. عبدالله البقمي", attendanceType: "internal" },
{ name: "أ. سلطان الغامدي", attendanceType: "internal" },
],
},
{
meeting: {
dailyNumber: 4,
titleAr: "اجتماع بشأن عرض الخطة الخاصة بالقطاعين",
titleEn: "Two-Sector Plan Presentation",
meetingDate: today,
startTime: "13:00",
endTime: "13:30",
platform: "none",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "أ. بدر العنزي", attendanceType: "internal" },
{ name: "أ. هاني المطيري", attendanceType: "internal" },
{ name: "أ. وليد الرشيدي", attendanceType: "internal" },
{ name: "أ. يوسف الجهني", attendanceType: "internal" },
{ name: "أ. عبدالرحمن الشمري", attendanceType: "internal" },
],
},
{
meeting: {
dailyNumber: 5,
titleAr: "اجتماع مع أ. عبدالله بشأن المتابعة",
titleEn: "Follow-up Meeting with Mr. Abdullah",
meetingDate: today,
startTime: "13:30",
endTime: "13:50",
platform: "none",
isHighlighted: 0,
status: "scheduled",
},
attendees: [{ name: "أ. عبدالله الزهراني", attendanceType: "internal" }],
},
{
meeting: {
dailyNumber: 6,
titleAr: "اجتماع دوري بشأن مواضيع مكتب الإدارة",
titleEn: "Periodic Office Topics Meeting",
meetingDate: today,
startTime: "14:00",
endTime: "14:30",
platform: "none",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "أ. مازن الفيفي", attendanceType: "internal" },
{ name: "أ. سامي العسيري", attendanceType: "internal" },
{ name: "أ. أحمد البلوي", attendanceType: "internal" },
],
},
{
meeting: {
dailyNumber: 7,
titleAr: "اجتماع بشأن إطار اعتماد البرامج",
titleEn: "Program Accreditation Framework",
meetingDate: today,
startTime: "14:40",
endTime: "15:10",
platform: "none",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "د. عبدالمجيد الحارثي", attendanceType: "internal" },
{ name: "د. منصور القرني", attendanceType: "internal" },
{ name: "أ. فيصل الزهراني", attendanceType: "internal" },
],
},
{
meeting: {
dailyNumber: 8,
titleAr: "اجتماع بشأن استراتيجية الموارد البشرية",
titleEn: "HR Strategy Meeting",
meetingDate: today,
startTime: "15:30",
endTime: "16:30",
platform: "webex",
isHighlighted: 1,
status: "scheduled",
},
attendees: [
{ name: "د. سعود الشهراني", attendanceType: "virtual" },
{ name: "أ. عبدالإله الحربي", attendanceType: "virtual" },
{ name: "أ. خالد العنزي", attendanceType: "virtual" },
{ name: "أ. أحمد المالكي", attendanceType: "internal" },
{ name: "أ. ناصر القحطاني", attendanceType: "internal" },
{ name: "أ. سعيد الغامدي", attendanceType: "internal" },
{ name: "أ. علي الزهراني", attendanceType: "internal" },
{ name: "أ. محمد البقمي", attendanceType: "internal" },
],
},
{
meeting: {
dailyNumber: 9,
titleAr: "اجتماع بشأن متابعة المشاريع المشتركة",
titleEn: "Joint Projects Follow-up",
meetingDate: today,
startTime: "16:40",
endTime: "17:10",
platform: "webex",
isHighlighted: 0,
status: "scheduled",
},
attendees: [
{ name: "أ. يزيد العتيبي", attendanceType: "virtual" },
{ name: "أ. تركي السهلي", attendanceType: "virtual" },
{ name: "أ. عبدالله الزيد", attendanceType: "internal" },
],
},
];
const insertedMeetings: number[] = [];
for (const { meeting, attendees } of sampleMeetings) {
const [inserted] = await db
.insert(executiveMeetingsTable)
.values(meeting)
.returning();
if (!inserted) continue;
insertedMeetings.push(inserted.id);
await db.insert(executiveMeetingAttendeesTable).values(
attendees.map((a, idx) => ({
meetingId: inserted.id,
name: a.name,
attendanceType: a.attendanceType,
sortOrder: idx,
})),
);
}
console.log("Executive Meetings sample data created for today");
// Seed example notification rules so the Notifications tab has
// representative content (one row per type/status combination so
// the page never appears empty in a fresh install).
if (insertedMeetings.length > 0) {
const adminId =
(
await db
.select({ id: usersTable.id })
.from(usersTable)
.where(eq(usersTable.username, "admin"))
)[0]?.id ?? null;
const now = new Date();
const minutes = (m: number) =>
new Date(now.getTime() + m * 60 * 1000);
const samples: Array<{
meetingId: number;
userId: number | null;
notificationType: string;
scheduledAt: Date | null;
sentAt: Date | null;
status: string;
}> = [
{
meetingId: insertedMeetings[0]!,
userId: adminId,
notificationType: "meeting_reminder_15m",
scheduledAt: minutes(15),
sentAt: null,
status: "pending",
},
{
meetingId: insertedMeetings[0]!,
userId: adminId,
notificationType: "meeting_reminder_60m",
scheduledAt: minutes(60),
sentAt: null,
status: "pending",
},
];
if (insertedMeetings[1]) {
samples.push({
meetingId: insertedMeetings[1]!,
userId: adminId,
notificationType: "meeting_invite",
scheduledAt: minutes(-30),
sentAt: minutes(-25),
status: "sent",
});
}
await db.insert(executiveMeetingNotificationsTable).values(samples);
console.log("Executive Meetings notification samples created");
}
}
} else {
console.log("Executive Meetings sample data already exists for today, skipping");
}
console.log("\n✅ Seeding complete!");
console.log("\nDemo accounts:");
console.log(" Admin: username=admin, password=admin123");
console.log(" User: username=ahmed, password=user123");
process.exit(0);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});