A full-stack web application with comprehensive user management, organization hierarchy, and invite-based registration system.
- User Authentication: JWT-based authentication with secure password hashing
- Web Interface: Clean, responsive UI built with vanilla HTML/CSS/JavaScript
- Site Administration: Site admins can create and manage organizations
- Organizations: Multi-tenant organization structure
- Invite System: UUID-based invite codes for organization membership
- Role-Based Access: Site admins and organization-level admins/users
- RESTful API: Clean API design with automatic OpenAPI documentation
- Flexible Table System: Reusable table components with pagination, sorting, and filtering powered by Tabulator
- Framework: FastAPI
- Database: SQLAlchemy (SQLite for development, PostgreSQL ready for production)
- Migrations: Alembic
- Package Management: Poetry
- Server: Uvicorn
- Authentication: JWT tokens with python-jose
- Password Hashing: Passlib with bcrypt
- UI: Vanilla HTML, CSS, JavaScript
- Styling: TailwindCSS (via CDN)
- API Client: Fetch API
- State Management: LocalStorage for auth tokens
obtree/
├── app/
│ ├── api/
│ │ ├── routes/ # API route handlers
│ │ │ ├── auth.py # Authentication endpoints
│ │ │ ├── organizations.py
│ │ │ ├── invites.py
│ │ │ └── table_config.py # Table configuration endpoints
│ │ └── deps.py # Dependency injection (auth, permissions)
│ ├── core/
│ │ ├── security.py # Password hashing, JWT tokens
│ │ └── permissions.py # Permission checking utilities
│ ├── models/ # SQLAlchemy models
│ │ ├── mixins.py # TableConfigMixin for table support
│ ├── schemas/ # Pydantic schemas
│ ├── config.py # Configuration management
│ ├── database.py # Database connection
│ └── main.py # FastAPI application
├── frontend/
│ ├── js/
│ │ ├── api.js # API client
│ │ ├── auth.js # Authentication utilities
│ │ ├── utils.js # Helper functions
│ │ ├── table.js # DataTable class (Tabulator wrapper)
│ │ └── tableBuilder.js # Table builder utilities
│ ├── login.html # Login page
│ ├── signup.html # Signup page
│ ├── organization.html # Organization details (with tables)
│ ├── admin.html # Site admin page
│ └── tables-demo.html # Table component demo
├── scripts/
│ └── seed_admin.py # Create site admin users
├── alembic/ # Database migrations
├── .env # Environment variables (not in git)
├── .env.example # Example environment variables
├── Makefile # Useful commands
└── pyproject.toml # Poetry dependencies
- Python 3.10+
- Poetry
Note: SQLite is used for local development and requires no additional setup. PostgreSQL is only needed for production deployments.
-
Clone the repository and navigate to the project directory
-
Install dependencies:
make install- Copy the example environment file and configure it:
cp .env.example .env
# Edit .env with your settings (SQLite is already configured for development)- Create the database tables:
make db-create- Create a site admin user:
make seed-admin- Run the development server:
make runThe application will be available at:
- Web App:
http://localhost:8000(redirects to login or home page) - API Docs:
http://localhost:8000/docs - API:
http://localhost:8000/api/
- After running the server, visit
http://localhost:8000 - You'll be redirected to the login page
- Since you created a site admin with
make seed-admin, login with those credentials - You'll be taken to the admin page
- Login with your site admin account
- Click Admin in the navigation bar
- Create an Organization using the form
- Click on the organization to view details
- Generate an Invite by selecting a role (Admin/User) and clicking "Generate Invite"
- Copy the invite link and share it with users
- Users can signup using the invite link to automatically join the organization
- Receive an invite link from an organization admin
- Click the invite link or paste the invite code during signup
- The signup form will show which organization you're joining
- Complete signup with your email and password
- Login and access your organization
- View organization details and members
- Login and navigate to your organization
- View all members and their roles
- Create invites to add new members
- Manage invite codes - copy links to share with new users
/login.html- User login/signup.html- User registration (with optional invite code)/organization.html- Organization details, members, and invite management/admin.html- Site admin panel (create organizations)/tables-demo.html- Interactive demo of the table component system
Once the server is running, visit:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
POST /api/auth/signup- Register a new user (with optional invite code)POST /api/auth/login- Login and receive JWT tokenGET /api/auth/me- Get current user informationGET /api/auth/users- List all users (site admin only)
POST /api/organizations- Create organization (site admin only)GET /api/organizations- List user's organizationsGET /api/organizations/{id}- Get organization details with members
POST /api/invites- Create invite code (site/org admin)GET /api/invites/organization/{id}- List organization invites (admins only)GET /api/invites/validate/{uuid}- Validate an invite code (public)
GET /api/table-config/{model_name}- Get table configuration for a model (User, Organization, OrganizationMembership, Invite)
- Can create organizations
- Can invite users to any organization
- Has all organization admin permissions
- Can invite users to their organization
- Can view organization members
- Can manage organization invites
- Can view organization details
- Can view organization members
- Standard member access
RedBuds App includes a flexible, reusable table component system built on Tabulator that makes it easy to display database objects with pagination, sorting, and filtering.
- Backend-driven configuration: Table columns and settings are defined in SQLAlchemy models
- Frontend rendering: Uses Tabulator.js for rich, interactive tables
- Modern TailwindCSS styling: Custom CSS that perfectly matches your site's design
- Built-in formatters: datetime, date, boolean, badge, email, link, money, and more
- Pagination & sorting: Client-side pagination and column sorting out of the box
- Powerful filtering: Global search, header filters, and custom filter controls
- Responsive design: Works seamlessly with TailwindCSS and mobile devices
- Customizable: Easy to add custom formatters and action columns
from app.models.mixins import TableConfigMixin
class MyModel(Base, TableConfigMixin):
__tablename__ = "my_table"
# ... your columns ...
# Table configuration for frontend display
__table_config__ = {
'columns': [
{
'field': 'id',
'label': 'ID',
'visible': False, # Hidden from table
'sortable': True,
'formatter': 'plaintext'
},
{
'field': 'name',
'label': 'Name',
'visible': True,
'sortable': True,
'width': 200,
'formatter': 'plaintext'
},
{
'field': 'email',
'label': 'Email',
'visible': True,
'sortable': True,
'width': 250,
'formatter': 'email' # Renders as clickable mailto link
},
{
'field': 'is_active',
'label': 'Active',
'visible': True,
'sortable': True,
'width': 100,
'formatter': 'boolean' # Renders as Yes/No
},
{
'field': 'created_at',
'label': 'Created',
'visible': True,
'sortable': True,
'width': 180,
'formatter': 'datetime' # Formats datetime nicely
}
],
'default_sort': {'field': 'created_at', 'dir': 'desc'}
}Edit app/api/routes/table_config.py and add your model to ALLOWED_MODELS:
ALLOWED_MODELS = {
'User': User,
'Organization': Organization,
'MyModel': MyModel, # Add your model here
}// Fetches table config from model and data from API
const table = await buildTableFromConfig(
'#my-table', // Container selector
'/api/my-endpoint', // API endpoint for data
'MyModel' // Model name (must match ALLOWED_MODELS)
);const columns = [
{ field: 'name', label: 'Name', visible: true, sortable: true, formatter: 'plaintext' },
{ field: 'email', label: 'Email', visible: true, sortable: true, formatter: 'email' }
];
const data = [
{ name: 'John Doe', email: 'john@example.com' },
{ name: 'Jane Smith', email: 'jane@example.com' }
];
const table = buildTableManual('#my-table', data, columns);<!-- In <head> -->
<link href="https://unpkg.com/tabulator-tables@6.2.5/dist/css/tabulator.min.css" rel="stylesheet">
<link href="/css/table-custom.css" rel="stylesheet"> <!-- Custom TailwindCSS styling -->
<script src="https://unpkg.com/tabulator-tables@6.2.5/dist/js/tabulator.min.js"></script>
<!-- Before your scripts -->
<script src="/js/table.js"></script>
<script src="/js/tableBuilder.js"></script>
<!-- In your page -->
<div id="my-table"></div>| Formatter | Description | Example Output |
|---|---|---|
plaintext |
Plain text display | Sample text |
datetime |
Formatted date and time | Jan 15, 2025, 10:30 AM |
date |
Formatted date only | Jan 15, 2025 |
boolean |
Yes/No display | Yes or No |
badge |
Colored badge | admin user active |
email |
Clickable mailto link | user@example.com |
link |
External link | Link → |
money |
Currency formatting | $1,234.56 |
const columns = [
{ field: 'name', label: 'Name', visible: true, sortable: true, formatter: 'plaintext' },
{
field: 'actions',
label: 'Actions',
visible: true,
sortable: false,
width: 180,
formatter: (cell) => {
const row = cell.getData();
return `
<div class="flex gap-2">
<button onclick="editItem(${row.id})" class="text-blue-600 hover:text-blue-800 text-sm">Edit</button>
<button onclick="deleteItem(${row.id})" class="text-red-600 hover:text-red-800 text-sm">Delete</button>
</div>
`;
}
}
];The table system supports three types of filtering:
Add a search input that searches across all columns:
const table = buildTableManual('#my-table', data, columns);
// Add search input above the table
addSearchToTable('#my-table', table, {
placeholder: 'Search...'
});
// Or create custom search input
const searchInput = createSearchInput(table, {
placeholder: 'Type to search...',
debounce: 300 // Milliseconds to wait before searching
});
document.getElementById('my-container').appendChild(searchInput);Enable filters in the table header for each column:
const table = new DataTable('#my-table', {
headerFilter: true // Enable header filters
});
table.init(data, config);For backend configurations, columns will automatically get appropriate filter types based on their formatter:
boolean→ Checkbox filterdate/datetime→ Text inputmoney→ Number input- Others → Text input
To disable filtering for specific columns, add filterable: false:
{
'field': 'actions',
'label': 'Actions',
'visible': True,
'sortable': False,
'filterable': False # No filter for this column
}Create custom filter dropdowns and inputs above your table:
const table = buildTableManual('#my-table', data, columns);
const filters = [
{
field: 'status',
label: 'Status',
type: 'select',
options: [
{ value: 'active', label: 'Active' },
{ value: 'pending', label: 'Pending' }
]
},
{
field: 'name',
label: 'Name',
type: 'text',
placeholder: 'Search by name...',
filterType: 'like' // Use 'like' for partial matches
}
];
const filterControls = createFilterControls(table, filters);
document.getElementById('my-container').appendChild(filterControls);You can also programmatically control filters:
// Set a single filter (replaces all existing filters)
table.setFilter('status', '=', 'active');
// Add a filter to existing filters
table.addFilter('role', '=', 'admin');
// Clear all filters
table.clearFilter();
// Global search
table.search('search term');
// Get current filters
const currentFilters = table.getFilters();Filter types:
=- Exact match!=- Not equallike- Partial match (case insensitive)<- Less than>- Greater than<=- Less than or equal>=- Greater than or equal
Visit /tables-demo.html to see live examples of:
- Simple tables with manual configuration
- Tables using all available formatters
- Tables loaded from backend configurations
- Tables with custom action buttons
- Tables with global search
- Tables with header filters
- Tables with custom filter controls
- Code examples and best practices
Backend:
app/models/mixins.py- TableConfigMixin for modelsapp/api/routes/table_config.py- API endpoint for table configsapp/models/*.py- Models with table_config definitions
Frontend:
frontend/js/table.js- DataTable class (Tabulator wrapper)frontend/js/tableBuilder.js- Helper functions for building tablesfrontend/css/table-custom.css- Custom TailwindCSS-themed table stylingfrontend/tables-demo.html- Interactive demo page
- Only mark necessary fields as visible: Hidden fields (visible: False) won't clutter the table but are still available in the data
- Use appropriate formatters: Choose the right formatter for your data type for better UX
- Set reasonable widths: Specify column widths to prevent layout issues
- Enable sorting where useful: Make frequently-sorted columns sortable
- Whitelist models carefully: Only add models to ALLOWED_MODELS that should be accessible via the table config API
- Site admin logs in
- Creates organization via
POST /api/organizations - Automatically becomes admin of the organization
- Site admin or org admin creates invite via
POST /api/invites - Receives UUID invite code
- Shares invite code with user (e.g., via email)
- New user validates invite code via
GET /api/invites/validate/{uuid} - User signs up with invite code via
POST /api/auth/signup - User automatically becomes member of organization with specified role
make help # Show all available commands
make install # Install dependencies
make run # Run development server
make migrate # Create new migration (usage: make migrate MSG='message')
make upgrade # Apply pending migrations
make downgrade # Rollback last migration
make seed-admin # Create site admin user
make shell # Open poetry shell
make db-create # Create/update database tables
make reset # Reset database (WARNING: deletes all data)Edit .env file to configure:
DATABASE_URL- Database connection string- Development:
sqlite:///./obtree.db(default, no setup required) - Production:
postgresql://user:password@localhost:5432/obtree_db
- Development:
SECRET_KEY- Secret key for JWT tokens (use a secure random key in production)ALGORITHM- JWT algorithm (default: HS256)ACCESS_TOKEN_EXPIRE_MINUTES- Token expiration timeINVITE_EXPIRATION_DAYS- Invite code validity period
After modifying models:
make migrate MSG="description of changes"
make upgradepoetry run pytest-
Update
.envwith production settings:- Change
DATABASE_URLto PostgreSQL:postgresql://user:password@localhost:5432/obtree_db - Set strong
SECRET_KEY(generate with:openssl rand -hex 32) - Set
DEBUG=False - Configure proper CORS origins in
app/main.py
- Change
-
Run migrations:
make upgrade- Use a production ASGI server (Uvicorn with workers or Gunicorn):
poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4- Never commit
.envfile to version control - Use strong, random
SECRET_KEYin production - Always use HTTPS in production
- Configure CORS properly for production origins
- Regularly rotate invite codes and tokens
- Review and audit site admin permissions
MIT