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:
@@ -95,6 +95,16 @@ export const orderStatusEnum = pgEnum('order_status', [
|
||||
'failed',
|
||||
])
|
||||
|
||||
// Role codes for user roles
|
||||
export const roleCodeEnum = pgEnum('role_code', ['private', 'educator', 'company'])
|
||||
|
||||
// Role request status (for approval workflow in Phase 2/3)
|
||||
export const roleRequestStatusEnum = pgEnum('role_request_status', [
|
||||
'pending',
|
||||
'approved',
|
||||
'rejected',
|
||||
])
|
||||
|
||||
/**
|
||||
* Users Table
|
||||
* Stores local user profiles linked to Cidaas authentication
|
||||
@@ -146,6 +156,93 @@ export const products = pgTable(
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Roles Table
|
||||
* Defines available user roles (private, educator, company)
|
||||
* Phase 2/3: Educator and Company roles require approval workflow
|
||||
*/
|
||||
export const roles = pgTable('roles', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
code: roleCodeEnum('code').unique().notNull(), // 'private', 'educator', 'company'
|
||||
displayName: text('display_name').notNull(), // "Privatperson", "Pädagoge", "Unternehmen"
|
||||
description: text('description').notNull(), // Role description
|
||||
requiresApproval: boolean('requires_approval').notNull().default(false), // false for 'private', true for 'educator'/'company'
|
||||
sortOrder: integer('sort_order').notNull().default(0), // Display order
|
||||
active: boolean('active').notNull().default(true), // Can be deactivated
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
})
|
||||
|
||||
/**
|
||||
* User Roles Table (Junction Table)
|
||||
* Many-to-Many relationship between users and roles
|
||||
* MVP: Roles assigned manually via DB, always status='approved'
|
||||
* Phase 2/3: Users can request roles, admin approves/rejects
|
||||
*/
|
||||
export const userRoles = pgTable(
|
||||
'user_roles',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
roleId: uuid('role_id')
|
||||
.notNull()
|
||||
.references(() => roles.id, { onDelete: 'cascade' }),
|
||||
|
||||
// Role request status (Phase 2/3 feature - prepared in MVP)
|
||||
status: roleRequestStatusEnum('status').notNull().default('pending'),
|
||||
|
||||
// Role request data (Phase 2/3 feature - prepared in MVP)
|
||||
organizationName: text('organization_name'), // School/Company name (freetext in MVP, FK to organizations in Phase 2/3)
|
||||
adminNotes: text('admin_notes'), // Admin comments on approval/rejection
|
||||
|
||||
// JSONB history of status changes (Phase 2/3 feature - prepared in MVP)
|
||||
// Format: [{ status, organizationName, adminNotes, changedAt, changedBy }, ...]
|
||||
statusHistory: jsonb('status_history').notNull().default('[]'),
|
||||
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
// Unique constraint: User can only have one entry per role
|
||||
userIdRoleIdUnique: index('user_roles_user_id_role_id_unique').on(
|
||||
table.userId,
|
||||
table.roleId
|
||||
),
|
||||
userIdIdx: index('user_roles_user_id_idx').on(table.userId),
|
||||
statusIdx: index('user_roles_status_idx').on(table.status),
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Product Role Visibility Table (Junction Table)
|
||||
* Many-to-Many relationship between products and roles
|
||||
* Defines which roles can see which products
|
||||
* Products WITHOUT role assignments are INVISIBLE (opt-in visibility)
|
||||
*/
|
||||
export const productRoleVisibility = pgTable(
|
||||
'product_role_visibility',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
productId: uuid('product_id')
|
||||
.notNull()
|
||||
.references(() => products.id, { onDelete: 'cascade' }),
|
||||
roleId: uuid('role_id')
|
||||
.notNull()
|
||||
.references(() => roles.id, { onDelete: 'cascade' }),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
// Unique constraint: Product-Role pair can only exist once
|
||||
productIdRoleIdUnique: index('product_role_visibility_product_id_role_id_unique').on(
|
||||
table.productId,
|
||||
table.roleId
|
||||
),
|
||||
productIdIdx: index('product_role_visibility_product_id_idx').on(table.productId),
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Carts Table
|
||||
* Shopping carts for both authenticated and guest users
|
||||
@@ -232,6 +329,7 @@ export const orderItems = pgTable('order_items', {
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
carts: many(carts),
|
||||
orders: many(orders),
|
||||
userRoles: many(userRoles),
|
||||
}))
|
||||
|
||||
export const cartsRelations = relations(carts, ({ one, many }) => ({
|
||||
@@ -275,4 +373,32 @@ export const orderItemsRelations = relations(orderItems, ({ one }) => ({
|
||||
export const productsRelations = relations(products, ({ many }) => ({
|
||||
cartItems: many(cartItems),
|
||||
orderItems: many(orderItems),
|
||||
roleVisibility: many(productRoleVisibility),
|
||||
}))
|
||||
|
||||
export const rolesRelations = relations(roles, ({ many }) => ({
|
||||
userRoles: many(userRoles),
|
||||
productVisibility: many(productRoleVisibility),
|
||||
}))
|
||||
|
||||
export const userRolesRelations = relations(userRoles, ({ one }) => ({
|
||||
user: one(users, {
|
||||
fields: [userRoles.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
role: one(roles, {
|
||||
fields: [userRoles.roleId],
|
||||
references: [roles.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export const productRoleVisibilityRelations = relations(productRoleVisibility, ({ one }) => ({
|
||||
product: one(products, {
|
||||
fields: [productRoleVisibility.productId],
|
||||
references: [products.id],
|
||||
}),
|
||||
role: one(roles, {
|
||||
fields: [productRoleVisibility.roleId],
|
||||
references: [roles.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user