Task #517: App image upload, external links, built-in route lock
Admin Add/Edit App now supports: - Custom image upload (or fall back to Lucide icon) via the existing ServiceImageUploader; rendered on the home launcher when set. - Open mode picker: internal (default), external_tab (window.open), external_iframe (renders inside /embedded/:id). External URL input shown conditionally and required when an external mode is chosen. Internal route field is hidden entirely when an external mode is selected, and locked (readOnly + lock hint) when editing a built-in app whose path is hardcoded in the SPA. Backend: - apps schema gains image_url, external_url, open_mode (default 'internal'); drizzle-kit push applied. - New lib/db/src/built-in-apps.ts exports BUILTIN_APP_SLUGS + isBuiltinAppSlug. Exposed via subpath export `@workspace/db/built-in-apps`; the file has zero imports so the browser bundle can use it without pulling in `pg`. tx-os now imports it directly — duplicate FE constant removed. - Built-in slug list: services, notifications, admin, notes, my-orders, orders-incoming, executive-meetings (everything with a hardcoded <Route> in artifacts/tx-os/src/App.tsx). calendar / documents are seeded but admin-defined and remain editable. - PATCH /apps/:id rejects route changes whose previous slug is built-in with 400 + code='builtin_route_locked'. Same-route no-op is allowed; non-route updates on built-ins still work. Other: - New SPA route /embedded/:id and embedded-app page (iframe host with back + open-in-new-tab + error/not-embeddable states). - OpenAPI App / CreateAppBody / UpdateAppBody extended; codegen ran. - en/ar locales: admin.appImage, appExternalUrl, appOpenMode.*, builtinPathLocked, embeddedFrame.*. - scripts/src/seed.ts: drift guard throws if a seeded built-in slug uses a route that does not match the hardcoded SPA route. Tests: - New API test apps-builtin-route-lock.test.mjs (4/4 pass): reject built-in route change, allow non-route built-in updates, allow non-builtin route changes, allow built-in same-route no-op. - New Playwright E2E admin-app-image-external-embedded.spec.mjs (passes): launcher renders custom image_url, external_tab opens external URL via window.open, external_iframe navigates to /embedded/:id and renders <iframe src=externalUrl>. Out of scope (pre-existing, not introduced here): - 3 failing tests in executive-meetings-* and tsc errors in api-server/src/routes/executive-meetings.ts.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { db } from "@workspace/db";
|
||||
import { BUILTIN_APP_SLUGS } from "@workspace/db/built-in-apps";
|
||||
import {
|
||||
usersTable,
|
||||
rolesTable,
|
||||
@@ -225,6 +226,33 @@ async function main() {
|
||||
},
|
||||
];
|
||||
|
||||
// Drift guard: every built-in slug (those with hardcoded routes in the
|
||||
// SPA, see lib/db/src/built-in-apps.ts) that this seed actually creates
|
||||
// here must use the same `route` the SPA expects. We don't require ALL
|
||||
// built-in slugs to be seeded (some, like "notes" / "my-orders" /
|
||||
// "orders-incoming", are user-created later), but if we DO seed one,
|
||||
// the route must match the hardcoded SPA route.
|
||||
const expectedBuiltinRoutes: Record<string, string> = {
|
||||
services: "/services",
|
||||
notifications: "/notifications",
|
||||
admin: "/admin",
|
||||
notes: "/notes",
|
||||
"my-orders": "/my-orders",
|
||||
"orders-incoming": "/orders/incoming",
|
||||
"executive-meetings": "/executive-meetings",
|
||||
};
|
||||
for (const a of apps) {
|
||||
if ((BUILTIN_APP_SLUGS as readonly string[]).includes(a.slug)) {
|
||||
const expected = expectedBuiltinRoutes[a.slug];
|
||||
if (expected && a.route !== expected) {
|
||||
throw new Error(
|
||||
`seed: built-in app "${a.slug}" must use route "${expected}" (got "${a.route}"). ` +
|
||||
`Built-in slugs are listed in lib/db/src/built-in-apps.ts and have hardcoded SPA routes in artifacts/tx-os/src/App.tsx.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const insertedApps = await db.insert(appsTable).values(apps).onConflictDoNothing().returning();
|
||||
console.log("Apps created");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user