feat: add dashboard API with quick links and widgets
Build and Push / build (release) Successful in 1m28s

- Dashboard domain with Quick Links CRUD + reorder endpoints
- Dashboard widgets management endpoints
- Database migrations for quick_links and dashboard_widgets tables
- Static file controller for Organizr widgets
- Default local user when OIDC is disabled
- Domain-based architecture refactor (src/domains/, src/shared/)
- Test suite updated for new structure (285 tests passing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-03 12:27:57 +01:00
co-authored by Claude Opus 4.5
parent e85c9a123d
commit 381d43b60b
51 changed files with 8215 additions and 784 deletions
+4 -3
View File
@@ -13,11 +13,12 @@ from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
# Import our models and config
from src.config import get_settings
from src.db.database import Base
from src.shared.config import get_settings
from src.shared.database import Base
# Import all models to ensure they're registered with Base.metadata
from src.db.models import User, Role, UserRole, UserPreferences, ApiKey # noqa: F401
from src.domains.auth.models import User, Role, UserRole, UserPreferences, ApiKey # noqa: F401
from src.domains.dashboard.models import QuickLink, DashboardWidget # noqa: F401
# Alembic Config object
config = context.config
@@ -0,0 +1,71 @@
"""Create dashboard tables
Revision ID: 003
Revises: 002
Create Date: 2026-01-03
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '003'
down_revision: Union[str, None] = '002'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Create quick_links and dashboard_widgets tables."""
# Create quick_links table
op.create_table(
'quick_links',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=100), nullable=False),
sa.Column('url', sa.String(length=500), nullable=False),
sa.Column('icon', sa.String(length=100), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('category', sa.String(length=50), nullable=True),
sa.Column('user_id', sa.String(length=255), nullable=True),
sa.Column('position', sa.Integer(), nullable=True, default=0),
sa.Column('is_visible', sa.Boolean(), nullable=True, default=True),
sa.Column('color', sa.String(length=20), nullable=True),
sa.Column('background_color', sa.String(length=20), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_quick_links_id'), 'quick_links', ['id'], unique=False)
op.create_index(op.f('ix_quick_links_user_id'), 'quick_links', ['user_id'], unique=False)
# Create dashboard_widgets table
op.create_table(
'dashboard_widgets',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('widget_type', sa.String(length=50), nullable=False),
sa.Column('user_id', sa.String(length=255), nullable=True),
sa.Column('position_x', sa.Integer(), nullable=True, default=0),
sa.Column('position_y', sa.Integer(), nullable=True, default=0),
sa.Column('width', sa.Integer(), nullable=True, default=1),
sa.Column('height', sa.Integer(), nullable=True, default=1),
sa.Column('config', sa.Text(), nullable=True),
sa.Column('is_visible', sa.Boolean(), nullable=True, default=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_dashboard_widgets_id'), 'dashboard_widgets', ['id'], unique=False)
op.create_index(op.f('ix_dashboard_widgets_user_id'), 'dashboard_widgets', ['user_id'], unique=False)
def downgrade() -> None:
"""Drop dashboard tables."""
op.drop_index(op.f('ix_dashboard_widgets_user_id'), table_name='dashboard_widgets')
op.drop_index(op.f('ix_dashboard_widgets_id'), table_name='dashboard_widgets')
op.drop_table('dashboard_widgets')
op.drop_index(op.f('ix_quick_links_user_id'), table_name='quick_links')
op.drop_index(op.f('ix_quick_links_id'), table_name='quick_links')
op.drop_table('quick_links')