Add role-based visibility and management features for products

- Introduced a role-based visibility system for products, ensuring that only users with approved roles can view specific products.
- Added new database tables for roles, user roles, and product role visibility to manage access control.
- Implemented utility functions for role management, including fetching approved roles, checking product visibility, and assigning roles to users and products.
- Updated API endpoints to filter products based on user roles, enhancing security and user experience.
- Prepared the database schema for future role request and approval workflows in upcoming phases.
This commit is contained in:
Bastian Masanek
2025-11-02 10:17:40 +01:00
parent 6e4f858883
commit ff9960edef
10 changed files with 1865 additions and 26 deletions

View File

@@ -7,8 +7,42 @@
import 'dotenv/config'
import { drizzle } from 'drizzle-orm/postgres-js'
import { and, eq } from 'drizzle-orm'
import postgres from 'postgres'
import { products } from './schema'
import * as schema from './schema'
import { products, roles, productRoleVisibility } from './schema'
/**
* Standard roles for the system
* MVP: All roles are created, but assignment is manual
* Phase 2/3: educator and company roles require approval workflow
*/
const standardRoles = [
{
code: 'private' as const,
displayName: 'Privatperson',
description: 'Für private Besucher und Einzelpersonen',
requiresApproval: false,
sortOrder: 1,
active: true,
},
{
code: 'educator' as const,
displayName: 'Pädagoge',
description: 'Für Lehrer, Erzieher und pädagogische Fachkräfte',
requiresApproval: true,
sortOrder: 2,
active: true,
},
{
code: 'company' as const,
displayName: 'Unternehmen',
description: 'Für Firmenkunden und B2B-Partner',
requiresApproval: true,
sortOrder: 3,
active: true,
},
]
/**
* Sample annual pass products for experimenta
@@ -55,33 +89,108 @@ async function seed() {
console.log('🌱 Starting database seed...')
// Create database connection
// Create database connection with schema
const client = postgres(connectionString)
const db = drizzle(client)
const db = drizzle(client, { schema })
try {
// Insert products
console.log(`📦 Inserting ${mockProducts.length} products...`)
const insertedProducts = await db
.insert(products)
.values(mockProducts)
.onConflictDoUpdate({
target: products.navProductId,
set: {
name: mockProducts[0].name, // Drizzle requires a set object, using sql.excluded in real impl
description: mockProducts[0].description,
price: mockProducts[0].price,
stockQuantity: mockProducts[0].stockQuantity,
updatedAt: new Date(),
},
})
.returning()
// 1. Insert/Update Roles
console.log(`👥 Inserting ${standardRoles.length} roles...`)
const insertedRoles = []
for (const roleData of standardRoles) {
const [role] = await db
.insert(roles)
.values(roleData)
.onConflictDoUpdate({
target: roles.code,
set: {
displayName: roleData.displayName,
description: roleData.description,
requiresApproval: roleData.requiresApproval,
sortOrder: roleData.sortOrder,
active: roleData.active,
updatedAt: new Date(),
},
})
.returning()
insertedRoles.push(role)
}
console.log(`✅ Successfully inserted/updated ${insertedRoles.length} roles:`)
insertedRoles.forEach((role) => {
console.log(
` - ${role.displayName} (${role.code}) ${role.requiresApproval ? '[requires approval]' : '[auto-approved]'}`
)
})
// 2. Insert/Update Products
console.log(`\n📦 Inserting ${mockProducts.length} products...`)
const insertedProducts = []
for (const productData of mockProducts) {
const [product] = await db
.insert(products)
.values(productData)
.onConflictDoUpdate({
target: products.navProductId,
set: {
name: productData.name,
description: productData.description,
price: productData.price,
stockQuantity: productData.stockQuantity,
category: productData.category,
active: productData.active,
updatedAt: new Date(),
},
})
.returning()
insertedProducts.push(product)
}
console.log(`✅ Successfully inserted/updated ${insertedProducts.length} products:`)
insertedProducts.forEach((product) => {
console.log(` - ${product.name} (${product.navProductId}) - €${product.price}`)
})
// 3. Assign Roles to Products (Category-based mapping)
console.log(`\n🔗 Assigning roles to products based on category...`)
// Category to role mapping
const categoryRoleMapping: Record<string, string[]> = {
'makerspace-annual-pass': ['private', 'educator'],
'annual-pass': ['private'],
'educator-annual-pass': ['educator'],
}
let assignmentCount = 0
for (const product of insertedProducts) {
const roleCodes = categoryRoleMapping[product.category] || []
for (const roleCode of roleCodes) {
// Find role by code
const role = insertedRoles.find((r) => r.code === roleCode)
if (!role) continue
// Check if assignment already exists
const existing = await db.query.productRoleVisibility.findFirst({
where: and(
eq(productRoleVisibility.productId, product.id),
eq(productRoleVisibility.roleId, role.id)
),
})
if (!existing) {
await db.insert(productRoleVisibility).values({
productId: product.id,
roleId: role.id,
})
assignmentCount++
console.log(` - ${product.name}${role.displayName}`)
}
}
}
console.log(`✅ Created ${assignmentCount} product-role assignments`)
console.log('\n✨ Database seed completed successfully!')
} catch (error) {
console.error('❌ Error seeding database:', error)