feat: add multi-tenant system with properties, organizations, and public booking

Implement complete multi-property architecture:
- Properties (groups of spaces) with public/private visibility
- Property managers (many-to-many) with role-based permissions
- Organizations with member management
- Anonymous/guest booking support via public API (/api/public/*)
- Property-scoped spaces, bookings, and settings
- Frontend: property selector, organization management, public booking views
- Migration script and updated seed data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-15 00:17:21 +00:00
parent d637513d92
commit e21cf03a16
51 changed files with 6324 additions and 273 deletions

View File

@@ -28,6 +28,12 @@ const router = createRouter({
component: () => import('@/views/VerifyEmail.vue'),
meta: { requiresAuth: false }
},
{
path: '/book/:propertyId?',
name: 'PublicBooking',
component: () => import('@/views/PublicBooking.vue'),
meta: { requiresAuth: false, isPublic: true }
},
{
path: '/dashboard',
name: 'Dashboard',
@@ -62,11 +68,29 @@ const router = createRouter({
component: () => import('@/views/UserProfile.vue'),
meta: { requiresAuth: true }
},
{
path: '/properties',
name: 'Properties',
component: () => import('@/views/Properties.vue'),
meta: { requiresAuth: true, requiresManager: true }
},
{
path: '/properties/:id',
name: 'PropertyDetail',
component: () => import('@/views/PropertyDetail.vue'),
meta: { requiresAuth: true, requiresManager: true }
},
{
path: '/organization',
name: 'Organization',
component: () => import('@/views/Organization.vue'),
meta: { requiresAuth: true }
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
meta: { requiresAuth: true, requiresManager: true }
},
{
path: '/users',
@@ -103,9 +127,13 @@ const router = createRouter({
router.beforeEach((to, _from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
if (to.meta.isPublic) {
next()
} else if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next('/login')
} else if (to.meta.requiresAdmin && !authStore.isAdmin) {
} else if (to.meta.requiresAdmin && !authStore.isSuperadmin) {
next('/dashboard')
} else if (to.meta.requiresManager && !authStore.isAdminOrManager) {
next('/dashboard')
} else if (to.path === '/login' && authStore.isAuthenticated) {
next('/dashboard')