From 097276f8cc2ca9fd26a004cf04547304aba538d4 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Sat, 16 May 2026 08:35:15 +0000 Subject: [PATCH] =?UTF-8?q?Task=20#545:=20Add=20Notes=20app=20to=20seed=20?= =?UTF-8?q?+=20rename=20Executive=20Meetings=20=E2=86=92=20Meetings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: On the user's deployed instance the home screen was missing the Notes tile (notes was in BUILTIN_APP_SLUGS but had no row in the seed's apps array, so no apps row existed in the DB and the launcher had nothing to render). The Executive Meetings tile also showed the long name "إدارة الاجتماعات التنفيذية" / "Executive Meetings"; user asked for the shorter "الاجتماعات" / "Meetings". Changes (scripts/src/seed.ts): - Added "notes" entry to the apps array (slug=notes, route=/notes, iconName=StickyNote, color=#eab308, sortOrder=2, isActive=true, nameAr="الملاحظات", nameEn="Notes"). - Renamed executive-meetings entry: nameAr/En + descriptions shortened. - Extended the existing post-insert UPDATE for executive-meetings to also push the new name + descriptions, so already-deployed instances pick up the rename on re-seed (insert is onConflictDoNothing). - Added a notes.access permission gate mirroring executive_meetings.access: the permission is created, granted to the admin role only, and linked to the notes app via app_permissions. getVisibleAppsForUser hides the tile from anyone without notes.access — admins grant it from the admin panel per role/user. Why a permission gate (deviation from initial plan): Code review caught that without app_permissions, getVisibleAppsForUser shows every active app to non-admins by default, so a bare INSERT would have made Notes visible to everyone — opposite of what the user asked for ("only for those I choose"). Used the existing executive_meetings pattern to keep the model consistent. Also added replit.md documenting the workflows, the seed re-run step after git pull, and the per-permission grant flow for restricted apps. Verified: ran the seed; SQL confirms notes row + الاجتماعات rename + notes.access restricted to admin role only. --- scripts/src/seed.ts | 96 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 13 deletions(-) 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