diff --git a/scripts/src/seed.ts b/scripts/src/seed.ts index ab3df7a5..0946e13e 100644 --- a/scripts/src/seed.ts +++ b/scripts/src/seed.ts @@ -248,6 +248,19 @@ async function main() { isSystem: true, sortOrder: 4, }, + { + slug: "notes", + nameAr: "الملاحظات", + nameEn: "Notes", + descriptionAr: "ملاحظاتك الشخصية ورسائلك بين الزملاء", + descriptionEn: "Personal notes and messages with colleagues", + iconName: "StickyNote", + route: "/notes", + color: "#eab308", + isActive: true, + isSystem: false, + sortOrder: 2, + }, { slug: "calendar", nameAr: "التقويم", @@ -276,10 +289,10 @@ async function main() { }, { slug: "executive-meetings", - nameAr: "إدارة الاجتماعات التنفيذية", - nameEn: "Executive Meetings", - descriptionAr: "جدولة اجتماعات المكتب التنفيذي ومتابعتها", - descriptionEn: "Schedule and track executive office meetings", + nameAr: "الاجتماعات", + nameEn: "Meetings", + descriptionAr: "جدولة الاجتماعات ومتابعتها", + descriptionEn: "Schedule and track meetings", iconName: "CalendarClock", route: "/meetings", color: "#0B1E3F", @@ -321,17 +334,26 @@ async function main() { const insertedApps = await db.insert(appsTable).values(apps).onConflictDoNothing().returning(); console.log("Apps created"); - // Force the executive-meetings row onto the canonical /meetings route. - // The slug is in BUILTIN_APP_SLUGS, so the SPA owns its route — any - // drift in the DB (including legacy /executive-meetings or other - // experimental values) would launch the home tile at a path that - // has no React route and silently break the app. `onConflictDoNothing` - // above skips updating existing rows, so this UPDATE is what carries - // the route migration into already-deployed environments. The WHERE - // by slug keeps it idempotent across re-seeds. + // Force the executive-meetings row onto the canonical /meetings route + // AND onto the current display name. The slug is in BUILTIN_APP_SLUGS, + // so the SPA owns its route — any drift in the DB (including legacy + // /executive-meetings or other experimental values) would launch the + // home tile at a path that has no React route and silently break the + // app. `onConflictDoNothing` above skips updating existing rows, so + // this UPDATE is what carries the route + name migration into + // already-deployed environments. The WHERE by slug keeps it + // idempotent across re-seeds. Names are kept in sync with the + // hardcoded values in the `apps` array above so admins on existing + // deployments don't have to rename the tile by hand. await db .update(appsTable) - .set({ route: "/meetings" }) + .set({ + route: "/meetings", + nameAr: "الاجتماعات", + nameEn: "Meetings", + descriptionAr: "جدولة الاجتماعات ومتابعتها", + descriptionEn: "Schedule and track meetings", + }) .where(eq(appsTable.slug, "executive-meetings")); // Restrict admin app to users with apps:manage permission @@ -646,6 +668,54 @@ async function main() { } } + // Notes: gate the home-screen icon behind a dedicated permission so a + // freshly seeded deployment does NOT auto-show Notes to every regular + // user. By default only the `admin` role gets the permission; admins + // then grant `notes.access` to whichever roles/users they want from + // the admin panel. Same three-step pattern as executive_meetings.access + // above — all inserts are idempotent. + await db + .insert(permissionsTable) + .values({ + name: "notes.access", + descriptionAr: "الوصول إلى تطبيق الملاحظات", + descriptionEn: "Access the Notes app", + }) + .onConflictDoNothing(); + + const [notesAccessPerm] = await db + .select() + .from(permissionsTable) + .where(eq(permissionsTable.name, "notes.access")); + + if (notesAccessPerm) { + const adminRoleForNotes = (await db.select().from(rolesTable)) + .find((r) => r.name === "admin"); + if (adminRoleForNotes) { + await db + .insert(rolePermissionsTable) + .values({ + roleId: adminRoleForNotes.id, + permissionId: notesAccessPerm.id, + }) + .onConflictDoNothing(); + } + + const [notesApp] = await db + .select() + .from(appsTable) + .where(eq(appsTable.slug, "notes")); + if (notesApp) { + await db + .insert(appPermissionsTable) + .values({ appId: notesApp.id, permissionId: notesAccessPerm.id }) + .onConflictDoNothing(); + console.log( + "App permissions set: notes app restricted to notes.access permission", + ); + } + } + // Executive meetings: sample data for today (idempotent per-date). // // Opt-in only — a vanilla self-hosted install should start with an