Basic Architecture of Sport Web

This commit is contained in:
Nông Đức Huy
2026-08-11 13:37:25 +07:00
commit 8032fff6ac
262 changed files with 20348 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
/**
* Idempotent seed. Safe to run on every environment, including production.
*
* It reconciles the *code-owned* parts of the schema — the permission catalog
* and the system roles — with the database. It deliberately does NOT create
* users: account provisioning belongs to the auth milestone, and baking a
* default admin password into a repository is how stores get compromised.
*/
import { ALL_PERMISSIONS, PERMISSIONS, SYSTEM_ROLES, type Permission } from '@sport/types';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
/** Which permissions each system role starts with. Editable later at runtime. */
const ROLE_DEFINITIONS: Record<string, { name: string; permissions: readonly Permission[] }> = {
[SYSTEM_ROLES.SUPER_ADMIN]: {
name: 'Super Admin',
permissions: ALL_PERMISSIONS,
},
[SYSTEM_ROLES.ADMIN]: {
name: 'Admin',
permissions: ALL_PERMISSIONS.filter(
(permission) =>
permission !== PERMISSIONS.ROLE_MANAGE && permission !== PERMISSIONS.USER_MANAGE,
),
},
[SYSTEM_ROLES.CATALOG_MANAGER]: {
name: 'Catalog Manager',
permissions: [
PERMISSIONS.PRODUCT_READ,
PERMISSIONS.PRODUCT_CREATE,
PERMISSIONS.PRODUCT_UPDATE,
PERMISSIONS.PRODUCT_PUBLISH,
PERMISSIONS.CATEGORY_READ,
PERMISSIONS.CATEGORY_MANAGE,
PERMISSIONS.COLLECTION_READ,
PERMISSIONS.COLLECTION_MANAGE,
PERMISSIONS.BRAND_READ,
PERMISSIONS.BRAND_MANAGE,
PERMISSIONS.INVENTORY_READ,
PERMISSIONS.INVENTORY_UPDATE,
PERMISSIONS.MEDIA_READ,
PERMISSIONS.MEDIA_UPLOAD,
],
},
[SYSTEM_ROLES.ORDER_MANAGER]: {
name: 'Order Manager',
permissions: [
PERMISSIONS.ORDER_READ,
PERMISSIONS.ORDER_UPDATE,
PERMISSIONS.ORDER_CANCEL,
PERMISSIONS.PAYMENT_READ,
PERMISSIONS.CUSTOMER_READ,
PERMISSIONS.INVENTORY_READ,
PERMISSIONS.PRODUCT_READ,
],
},
[SYSTEM_ROLES.SUPPORT_AGENT]: {
name: 'Support Agent',
permissions: [
PERMISSIONS.ORDER_READ,
PERMISSIONS.CUSTOMER_READ,
PERMISSIONS.PRODUCT_READ,
PERMISSIONS.REVIEW_MODERATE,
],
},
[SYSTEM_ROLES.CUSTOMER]: {
name: 'Customer',
permissions: [],
},
};
async function seedPermissions(): Promise<Map<string, string>> {
for (const key of ALL_PERMISSIONS) {
const [resource = key, action = 'unknown'] = key.split('.');
await prisma.permission.upsert({
where: { key },
update: { resource, action },
create: { key, resource, action },
});
}
// Anything in the table but no longer in the catalog is dead configuration.
const removed = await prisma.permission.deleteMany({
where: { key: { notIn: [...ALL_PERMISSIONS] } },
});
if (removed.count > 0) {
console.log(`Removed ${removed.count} stale permission(s).`);
}
const rows = await prisma.permission.findMany({ select: { id: true, key: true } });
return new Map(rows.map((row) => [row.key, row.id]));
}
async function seedRoles(permissionIds: Map<string, string>): Promise<void> {
for (const [key, definition] of Object.entries(ROLE_DEFINITIONS)) {
const role = await prisma.role.upsert({
where: { key },
update: { name: definition.name, isSystem: true },
create: { key, name: definition.name, isSystem: true },
});
// Replace the grant set wholesale — the code definition wins for system roles.
await prisma.rolePermission.deleteMany({ where: { roleId: role.id } });
const grants = definition.permissions
.map((permission) => permissionIds.get(permission))
.filter((id): id is string => Boolean(id))
.map((permissionId) => ({ roleId: role.id, permissionId }));
if (grants.length > 0) {
await prisma.rolePermission.createMany({ data: grants, skipDuplicates: true });
}
}
}
async function seedInventoryLocation(): Promise<void> {
await prisma.inventoryLocation.upsert({
where: { code: 'MAIN' },
update: {},
create: { code: 'MAIN', name: 'Main Warehouse', isDefault: true },
});
}
async function main(): Promise<void> {
const permissionIds = await seedPermissions();
await seedRoles(permissionIds);
await seedInventoryLocation();
console.log(
`Seed complete: ${permissionIds.size} permissions, ${Object.keys(ROLE_DEFINITIONS).length} roles.`,
);
}
main()
.catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
})
.finally(() => {
void prisma.$disconnect();
});