import { appShellPlugin } from "@tailor-platform/app-shell-sdk-plugin"; import { collectPermissionScopes, userManagementPermissions, organizationPermissions, businessPartnerPermissions, itemManagementPermissions, inventoryPermissions, primitivesPermissions, purchasePermissions, productManagementPermissions, coaManagementPermissions, financialAccountingPermissions, accountPayablePermissions, accountReceivablePermissions, salesPermissions, } from "@tailor-platform/erp-kit/app"; // inbound/outbound-shipment permission scopes are exported from the module // barrel (not re-exported from /app in erp-kit 0.45). import { inboundShipmentPermissions, outboundShipmentPermissions, } from "@tailor-platform/erp-kit/module"; import { defineConfig, defineAuth, defineIdp, defineStaticWebSite, definePlugins, } from "@tailor-platform/sdk"; import { kyselyTypePlugin } from "@tailor-platform/sdk/plugin/kysely-type"; import { seedPlugin } from "@tailor-platform/sdk/plugin/seed"; import { getDB } from "@/generated/kysely-tailordb"; import { user } from "@/modules-db"; const idp = defineIdp("default", { permission: { create: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }], read: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }], update: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }], delete: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }], sendPasswordResetEmail: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }], }, clients: ["default"], userAuthPolicy: { allowSelfPasswordReset: true, // Enable Google Workspace sign-in. `allowedEmailDomains` restricts login to // these domains: tailor.tech for Google sign-in, and example.com so the // seeded demo user (admin@example.com) can still sign in with a password. // Trim this list for production. allowGoogleOauth: true, allowedEmailDomains: ["tailor.tech", "example.com"], }, gqlOperations: "query", }); const website = defineStaticWebSite("default-web", { description: "Default static website", }); const auth = defineAuth("default", { hooks: { // Just-in-time user provisioning. A Google (or password) sign-in only proves // the identity — without a matching User row the app returns "user not // found". On first login we create the User profile from the token claims so // the sign-in resolves. beforeLogin: { invoker: "developer", handler: async ({ claims }) => { const email = claims.email as string | undefined; if (!email) return; const db = getDB("main-db"); await db.transaction().execute(async (trx) => { const existing = await trx .selectFrom("User") .select("id") .where("email", "=", email) .executeTakeFirst(); if (existing) return; const name = (claims.name as string | undefined) ?? email; // Bootstrap: grant new sign-ins the Admin role so they can exercise the // full workspace. Tighten this for production (e.g. invite-based role // assignment). const adminRole = await trx .selectFrom("Role") .select(["id", "permissions"]) .where("name", "=", "Admin") .executeTakeFirst(); const newUser = await trx .insertInto("User") .values({ email, name, status: "ACTIVE", permissions: adminRole?.permissions ?? null }) .returningAll() .executeTakeFirstOrThrow(); if (adminRole) { await trx .insertInto("UserRole") .values({ userId: newUser.id, roleId: adminRole.id }) .execute(); } }); }, }, }, userProfile: { type: user, usernameField: "email", attributes: { permissions: true, }, }, machineUsers: { developer: { attributes: { permissions: collectPermissionScopes( userManagementPermissions, organizationPermissions, businessPartnerPermissions, itemManagementPermissions, productManagementPermissions, inventoryPermissions, inboundShipmentPermissions, outboundShipmentPermissions, primitivesPermissions, purchasePermissions, coaManagementPermissions, financialAccountingPermissions, accountPayablePermissions, accountReceivablePermissions, salesPermissions, ), }, }, }, oauth2Clients: { default: { redirectURIs: ["http://localhost:5173", website.url], grantTypes: ["authorization_code", "refresh_token"], clientType: "public", requireDpop: true, }, }, idProvider: idp.provider(idp.name, "default"), }); export default defineConfig({ name: "template-app", cors: ["http://localhost:5173", website.url], db: { "main-db": { files: [`./src/modules-db.ts`], gqlOperations: "query", }, }, resolver: { "main-resolver": { files: [`./src/resolver/**/*.ts`], // Every resolver in this namespace requires an authenticated caller. Without // this the SDK generates no guard, and the gateway issues an anonymous JWT // rather than rejecting the request, so anonymous calls would reach `body`. // A resolver that is public by design opts out with `permission: "allowAnonymous"`. defaultPermission: [{ conditions: [[{ user: "_loggedIn" }, "=", true]], permit: true }], }, }, executor: { files: [`./src/executor/**/*.ts`] }, auth: auth, idp: [idp], staticWebsites: [website], }); export const plugins = definePlugins( kyselyTypePlugin({ distPath: "./src/generated/kysely-tailordb.ts" }), seedPlugin({ distPath: "./seed", machineUserName: "developer" }), appShellPlugin({ dataTable: { metadataOutputPath: "../frontend/src/graphql/generated/app-shell-datatable.generated.ts", }, }), );