# Slice 1: Multi-Tenant Foundation ## Admin Creates Vendor → Vendor Owner Logs In **Status**: 🔄 IN PROGRESS **Timeline**: Week 1 (5 days) **Current Progress**: Backend ~90%, Frontend ~60% ## 🎯 Slice Objectives Establish the multi-tenant foundation with complete vendor isolation and admin capabilities. ### User Stories - ✅ As a Super Admin, I can create vendors through the admin interface - ✅ As a Super Admin, I can manage vendor accounts (verify, activate, deactivate) - ⏳ As a Vendor Owner, I can log into my vendor-specific admin interface - ✅ The system correctly isolates vendor contexts (subdomain + path-based) ### Success Criteria - [ ] Admin can log into admin interface - [ ] Admin can create new vendors with auto-generated owner accounts - [ ] System generates secure temporary passwords - [ ] Vendor owner can log into vendor-specific interface - [ ] Vendor context detection works in dev (path) and prod (subdomain) modes - [ ] Database properly isolates vendor data - [ ] All API endpoints protected with JWT authentication - [ ] Frontend integrates seamlessly with backend ## 📋 Backend Implementation ### Database Models (✅ Complete) #### User Model (`models/database/user.py`) ```python class User(Base, TimestampMixin): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, nullable=False, index=True) username = Column(String, unique=True, nullable=False, index=True) hashed_password = Column(String, nullable=False) role = Column(String, nullable=False) # 'admin' or 'user' is_active = Column(Boolean, default=True) # Relationships owned_vendors = relationship("Vendor", back_populates="owner") vendor_memberships = relationship("VendorUser", back_populates="user") ``` #### Vendor Model (`models/database/vendor.py`) ```python class Vendor(Base, TimestampMixin): __tablename__ = "vendors" id = Column(Integer, primary_key=True, index=True) vendor_code = Column(String, unique=True, nullable=False, index=True) subdomain = Column(String(100), unique=True, nullable=False, index=True) name = Column(String, nullable=False) description = Column(Text) owner_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Business info business_email = Column(String) business_phone = Column(String) website = Column(String) business_address = Column(Text) tax_number = Column(String) # Status is_active = Column(Boolean, default=True) is_verified = Column(Boolean, default=False) verified_at = Column(DateTime, nullable=True) # Configuration theme_config = Column(JSON, default=dict) letzshop_csv_url_fr = Column(String) letzshop_csv_url_en = Column(String) letzshop_csv_url_de = Column(String) # Relationships owner = relationship("User", back_populates="owned_vendors") roles = relationship("Role", back_populates="vendor", cascade="all, delete-orphan") team_members = relationship("VendorUser", back_populates="vendor") ``` #### Role Model (`models/database/vendor.py`) ```python class Role(Base, TimestampMixin): __tablename__ = "roles" id = Column(Integer, primary_key=True, index=True) vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) name = Column(String, nullable=False) # Owner, Manager, Editor, Viewer permissions = Column(JSON, default=list) vendor = relationship("Vendor", back_populates="roles") vendor_users = relationship("VendorUser", back_populates="role") ``` ### Pydantic Schemas (✅ Complete) #### Vendor Schemas (`models/schema/vendor.py`) ```python class VendorCreate(BaseModel): vendor_code: str = Field(..., min_length=2, max_length=50) name: str = Field(..., min_length=2, max_length=200) subdomain: str = Field(..., min_length=2, max_length=100) owner_email: EmailStr # NEW for Slice 1 description: Optional[str] = None business_email: Optional[EmailStr] = None business_phone: Optional[str] = None website: Optional[str] = None @validator('vendor_code') def vendor_code_uppercase(cls, v): return v.upper() @validator('subdomain') def subdomain_lowercase(cls, v): return v.lower().strip() class VendorCreateResponse(BaseModel): """Response after creating vendor - includes generated credentials""" id: int vendor_code: str subdomain: str name: str owner_user_id: int owner_email: str owner_username: str temporary_password: str # Shown only once! is_active: bool is_verified: bool created_at: datetime ``` ### Service Layer (✅ Complete) #### Admin Service (`app/services/admin_service.py`) **Key Method**: `create_vendor_with_owner()` ```python async def create_vendor_with_owner( self, vendor_data: VendorCreate, db: Session ) -> Dict[str, Any]: """ Creates vendor + owner user + default roles Returns vendor details with temporary password """ # 1. Generate owner username owner_username = f"{vendor_data.subdomain}_owner" # 2. Generate secure temporary password temp_password = self._generate_temp_password() # 3. Create owner user owner_user = User( email=vendor_data.owner_email, username=owner_username, hashed_password=self.auth_manager.hash_password(temp_password), role="user", is_active=True ) db.add(owner_user) db.flush() # Get owner_user.id # 4. Create vendor vendor = Vendor( vendor_code=vendor_data.vendor_code, name=vendor_data.name, subdomain=vendor_data.subdomain, owner_user_id=owner_user.id, is_verified=True, # Auto-verify admin-created vendors verified_at=datetime.utcnow(), # ... other fields ) db.add(vendor) db.flush() # Get vendor.id # 5. Create default roles default_roles = ["Owner", "Manager", "Editor", "Viewer"] for role_name in default_roles: role = Role( vendor_id=vendor.id, name=role_name, permissions=self._get_default_permissions(role_name) ) db.add(role) # 6. Link owner to Owner role owner_role = db.query(Role).filter( Role.vendor_id == vendor.id, Role.name == "Owner" ).first() vendor_user = VendorUser( vendor_id=vendor.id, user_id=owner_user.id, role_id=owner_role.id, is_active=True ) db.add(vendor_user) db.commit() return { "vendor": vendor, "owner_username": owner_username, "temporary_password": temp_password } ``` ### API Endpoints (✅ Complete) #### Admin Endpoints (`app/api/v1/admin.py`) ```python @router.post("/vendors", response_model=VendorCreateResponse) async def create_vendor( vendor_data: VendorCreate, current_user: User = Depends(get_current_admin_user), db: Session = Depends(get_db) ): """Create new vendor with owner account""" result = await admin_service.create_vendor_with_owner(vendor_data, db) return VendorCreateResponse( id=result["vendor"].id, vendor_code=result["vendor"].vendor_code, subdomain=result["vendor"].subdomain, name=result["vendor"].name, owner_user_id=result["vendor"].owner_user_id, owner_email=vendor_data.owner_email, owner_username=result["owner_username"], temporary_password=result["temporary_password"], is_active=result["vendor"].is_active, is_verified=result["vendor"].is_verified, created_at=result["vendor"].created_at ) @router.get("/vendors", response_model=VendorListResponse) async def list_vendors( skip: int = 0, limit: int = 100, is_active: Optional[bool] = None, is_verified: Optional[bool] = None, current_user: User = Depends(get_current_admin_user), db: Session = Depends(get_db) ): """List all vendors with filtering""" return await admin_service.get_vendors(db, skip, limit, is_active, is_verified) @router.get("/dashboard", response_model=AdminDashboardResponse) async def get_admin_dashboard( current_user: User = Depends(get_current_admin_user), db: Session = Depends(get_db) ): """Get admin dashboard statistics""" return await admin_service.get_dashboard_stats(db) @router.put("/vendors/{vendor_id}/verify") async def verify_vendor( vendor_id: int, current_user: User = Depends(get_current_admin_user), db: Session = Depends(get_db) ): """Verify/unverify vendor""" return await admin_service.toggle_vendor_verification(vendor_id, db) @router.put("/vendors/{vendor_id}/status") async def toggle_vendor_status( vendor_id: int, current_user: User = Depends(get_current_admin_user), db: Session = Depends(get_db) ): """Activate/deactivate vendor""" return await admin_service.toggle_vendor_status(vendor_id, db) ``` ### Middleware (✅ Complete) #### Vendor Context Detection (`middleware/vendor_context.py`) ```python async def vendor_context_middleware(request: Request, call_next): """ Detects vendor context from: 1. Subdomain: vendor.platform.com (production) 2. Path: /vendor/VENDOR_CODE/ (development) """ vendor_context = None # Skip for admin/API routes if request.url.path.startswith(("/api/", "/admin/", "/static/")): response = await call_next(request) return response # 1. Try subdomain detection (production) host = request.headers.get("host", "").split(":")[0] parts = host.split(".") if len(parts) > 2: subdomain = parts[0] vendor = get_vendor_by_subdomain(subdomain) if vendor: vendor_context = vendor # 2. Try path detection (development) if not vendor_context: path_parts = request.url.path.split("/") if len(path_parts) > 2 and path_parts[1] == "vendor": vendor_code = path_parts[2].upper() vendor = get_vendor_by_code(vendor_code) if vendor: vendor_context = vendor request.state.vendor = vendor_context response = await call_next(request) return response ``` ## 🎨 Frontend Implementation ### Template Structure (Jinja2) #### Base Template (`templates/base.html`) ```html {% block title %}Multi-Tenant Platform{% endblock %} {% block extra_css %}{% endblock %} {% block content %}{% endblock %} {% block extra_scripts %}{% endblock %} ``` ### Admin Pages (🔄 In Progress) #### 1. Admin Login (`templates/admin/login.html`) **Status**: ✅ Complete ```html {% extends "base.html" %} {% block title %}Admin Login{% endblock %} {% block extra_css %} {% endblock %} {% block content %}
{% endblock %} {% block extra_scripts %} {% endblock %} ``` #### 2. Admin Dashboard (`templates/admin/dashboard.html`) **Status**: ⏳ In Progress ```html {% extends "admin/base_admin.html" %} {% block title %}Admin Dashboard{% endblock %} {% block content %}
Total Vendors
🏪
active
Total Users
👥
active
Verified
vendors verified

Recent Vendors

View All
{% endblock %} {% block extra_scripts %} {% endblock %} ``` #### 3. Vendor Creation (`templates/admin/vendors.html`) **Status**: ⏳ In Progress Alpine.js component for creating vendors with real-time validation and credential display. ### Vendor Pages (📋 To Do) #### 1. Vendor Login (`templates/vendor/login.html`) **Status**: 📋 To Do ```html {% extends "base.html" %} {% block title %}Vendor Login{% endblock %} {% block extra_css %} {% endblock %} {% block content %}
{% endblock %} {% block extra_scripts %} {% endblock %} ``` #### 2. Vendor Dashboard (`templates/vendor/dashboard.html`) **Status**: 📋 To Do ```html {% extends "vendor/base_vendor.html" %} {% block title %}{{ vendor.name }} Dashboard{% endblock %} {% block content %}

Welcome to {{ vendor.name }}

Vendor Code: {{ vendor.vendor_code }}

${vendor.is_verified ? 'Verified' : 'Pending Verification'}
Products
📦
in catalog
Orders
🛒
total orders
Customers
👥
registered

🚀 Coming in Slice 2

Product import from Letzshop marketplace will be available soon!

{% endblock %} {% block extra_scripts %} {% endblock %} ``` ## ✅ Testing Checklist ### Backend Tests #### Authentication - [ ] Admin can login with valid credentials - [ ] Admin login rejects invalid credentials - [ ] Non-admin users cannot access admin endpoints - [ ] JWT tokens expire after configured time - [ ] Token refresh works correctly #### Vendor Management - [ ] Admin can create vendor with all required fields - [ ] System generates unique vendor code - [ ] System generates unique subdomain - [ ] Owner user account is created automatically - [ ] Temporary password is generated (12+ characters) - [ ] Default roles are created (Owner, Manager, Editor, Viewer) - [ ] Owner is linked to Owner role - [ ] Vendor is auto-verified when created by admin - [ ] Duplicate vendor code is rejected - [ ] Duplicate subdomain is rejected - [ ] Duplicate owner email is rejected #### Vendor Context Detection - [ ] Subdomain detection works: `vendor.platform.com` - [ ] Path detection works: `/vendor/VENDORCODE/` - [ ] Admin routes bypass vendor context - [ ] API routes bypass vendor context - [ ] Static routes bypass vendor context - [ ] Invalid vendor returns appropriate error #### API Endpoints - [ ] `POST /api/v1/admin/vendors` creates vendor - [ ] `GET /api/v1/admin/vendors` lists vendors with pagination - [ ] `GET /api/v1/admin/dashboard` returns statistics - [ ] `PUT /api/v1/admin/vendors/{id}/verify` toggles verification - [ ] `PUT /api/v1/admin/vendors/{id}/status` toggles active status - [ ] All endpoints require admin authentication - [ ] All endpoints return proper error messages ### Frontend Tests #### Admin Login Page - [ ] Page loads without errors - [ ] Form validation works - [ ] Loading state displays during login - [ ] Error messages display correctly - [ ] Successful login redirects to dashboard - [ ] Token is stored in localStorage - [ ] Page is responsive on mobile #### Admin Dashboard - [ ] Dashboard loads with statistics - [ ] Vendor count is accurate - [ ] User count is accurate - [ ] Recent vendors list displays - [ ] Navigation works correctly - [ ] Refresh button updates data - [ ] Loading states work correctly - [ ] No console errors #### Vendor Creation Page - [ ] Form loads correctly - [ ] All fields are validated - [ ] Vendor code auto-uppercases - [ ] Subdomain auto-lowercases - [ ] Email validation works - [ ] Submit creates vendor successfully - [ ] Credentials are displayed once - [ ] Can copy credentials - [ ] Form resets after creation - [ ] Error messages display correctly #### Vendor Login Page - [ ] Page loads with vendor context - [ ] Vendor name displays correctly - [ ] Form validation works - [ ] Login succeeds with correct credentials - [ ] Login fails with wrong credentials - [ ] Redirects to vendor dashboard - [ ] Token stored in localStorage - [ ] "Vendor Not Found" shows for invalid vendor #### Vendor Dashboard - [ ] Dashboard loads successfully - [ ] Vendor information displays - [ ] Statistics load correctly - [ ] Welcome message shows - [ ] Verification badge shows correct status - [ ] No console errors ### Database Tests #### Schema Verification ```sql -- Check tables exist SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'; -- Expected: users, vendors, roles, vendor_users -- Check admin user SELECT * FROM users WHERE role = 'admin'; -- Check vendor creation SELECT * FROM vendors WHERE vendor_code = 'TESTVENDOR'; -- Check owner user SELECT * FROM users WHERE email = 'owner@testvendor.com'; -- Check default roles SELECT * FROM roles WHERE vendor_id = ( SELECT id FROM vendors WHERE vendor_code = 'TESTVENDOR' ); -- Expected: 4 roles (Owner, Manager, Editor, Viewer) ``` ### Security Tests - [ ] Passwords are hashed with bcrypt - [ ] JWT tokens are properly signed - [ ] Admin endpoints reject non-admin users - [ ] Vendor endpoints require authentication - [ ] Cross-vendor access is prevented - [ ] SQL injection is prevented - [ ] XSS is prevented in forms - [ ] CSRF protection is enabled ### Performance Tests - [ ] Admin login responds < 1 second - [ ] Dashboard loads < 2 seconds - [ ] Vendor creation completes < 3 seconds - [ ] Vendor list loads < 1 second - [ ] No N+1 query problems ## 📝 Documentation Tasks - [ ] Update API documentation with new endpoints - [ ] Document Alpine.js component patterns - [ ] Document Jinja2 template structure - [ ] Create deployment guide - [ ] Update environment variables documentation - [ ] Document vendor context detection logic ## 🚀 Deployment Checklist ### Environment Setup - [ ] PostgreSQL database created - [ ] Environment variables configured - [ ] Static files directory created - [ ] Template directory created - [ ] Database migrations applied ### Configuration - [ ] `JWT_SECRET_KEY` set to strong random value - [ ] `DATABASE_URL` configured correctly - [ ] `DEBUG` set appropriately (False for production) - [ ] `ALLOWED_HOSTS` configured - [ ] CORS settings configured ### Security - [ ] Change default admin password - [ ] Enable HTTPS in production - [ ] Configure secure cookie settings - [ ] Set up rate limiting - [ ] Enable request logging ### DNS (Production Only) - [ ] Wildcard subdomain configured: `*.platform.com` - [ ] Admin subdomain configured: `admin.platform.com` - [ ] SSL certificates installed ## 🎯 Acceptance Criteria Slice 1 is complete when: 1. **Admin Workflow Works** - [ ] Admin can log in - [ ] Admin can view dashboard - [ ] Admin can create vendors - [ ] Admin can manage vendors 2. **Vendor Workflow Works** - [ ] Vendor owner receives credentials - [ ] Vendor owner can log in - [ ] Vendor dashboard displays correctly - [ ] Vendor context is properly isolated 3. **Technical Requirements Met** - [ ] All API endpoints implemented - [ ] All frontend pages created - [ ] Database schema complete - [ ] Tests pass - [ ] Documentation complete 4. **Quality Standards Met** - [ ] Code follows conventions - [ ] No security vulnerabilities - [ ] Performance acceptable - [ ] Mobile responsive - [ ] Browser compatible ## 🔄 Known Issues / To Do ### High Priority - [ ] Complete vendor login page - [ ] Complete vendor dashboard page - [ ] Test vendor context detection thoroughly - [ ] Add password reset functionality ### Medium Priority - [ ] Add vendor list page for admin - [ ] Add vendor detail page for admin - [ ] Improve error messages - [ ] Add loading skeletons ### Low Priority - [ ] Add dark mode support - [ ] Add keyboard shortcuts - [ ] Add export functionality - [ ] Add audit logging ## 📚 Related Documentation - `00_slices_overview.md` - Overview of all slices - `../quick_start_guide.md` - Quick setup guide - `../css_structure_guide.txt` - CSS organization - `../12.project_readme_final.md` - Complete README ## ➡️ Next Steps After completing Slice 1: 1. **Test Thoroughly**: Run through entire testing checklist 2. **Deploy to Staging**: Test in production-like environment 3. **Demo to Stakeholders**: Show admin → vendor creation flow 4. **Document Learnings**: Update this document with lessons learned 5. **Move to Slice 2**: Begin marketplace import implementation ## 💡 Tips & Best Practices ### Working with Alpine.js - Keep components focused and small - Use `x-data` for reactive state - Use `x-init` for loading data - Prefer `x-show` over `x-if` for toggles - Use `@click`, `@submit` for event handling ### Working with Jinja2 - Pass initial data from backend to avoid extra API calls - Use template inheritance (extends, blocks) - Keep logic in backend, not templates - Use filters for formatting ### API Integration - Always handle loading states - Always handle errors gracefully - Use the shared `apiClient` utility - Store tokens in localStorage - Clear tokens on logout --- **Slice 1 Status**: 🔄 In Progress **Next Milestone**: Complete vendor login and dashboard pages **Estimated Completion**: End of Week 1