gaard-api 0.2.13__tar.gz → 0.2.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {gaard_api-0.2.13/src/gaard_api.egg-info → gaard_api-0.2.14}/PKG-INFO +1 -1
  2. {gaard_api-0.2.13 → gaard_api-0.2.14}/pyproject.toml +1 -1
  3. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/database_initial.sql +14 -0
  4. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/database_updates.sql +13 -0
  5. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/models.py +25 -0
  6. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/dashboards.py +343 -76
  7. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/main.py +1 -1
  8. {gaard_api-0.2.13 → gaard_api-0.2.14/src/gaard_api.egg-info}/PKG-INFO +1 -1
  9. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_admin_api.py +240 -0
  10. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_database_migrations.py +15 -5
  11. {gaard_api-0.2.13 → gaard_api-0.2.14}/MANIFEST.in +0 -0
  12. {gaard_api-0.2.13 → gaard_api-0.2.14}/README.md +0 -0
  13. {gaard_api-0.2.13 → gaard_api-0.2.14}/setup.cfg +0 -0
  14. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/__init__.py +0 -0
  15. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/__init__.py +0 -0
  16. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/database.py +0 -0
  17. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/database_legacy_updates.sql +0 -0
  18. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/defaults.py +0 -0
  19. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/migration_runner.py +0 -0
  20. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/prompt_runtime.py +0 -0
  21. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/security.py +0 -0
  22. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin/services.py +0 -0
  23. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/assets/getgaard.svg +0 -0
  24. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/assets/identity.js +0 -0
  25. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/assets/main.js +0 -0
  26. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/assets/styles.css +0 -0
  27. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack-all.js +0 -0
  28. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack.min.css +0 -0
  29. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/index.html +0 -0
  30. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/admin-web/package.json +0 -0
  31. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/__init__.py +0 -0
  32. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/__init__.py +0 -0
  33. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/admin.py +0 -0
  34. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/analysis.py +0 -0
  35. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/prompts.py +0 -0
  36. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/query.py +0 -0
  37. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api/v1/schema.py +0 -0
  38. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/api_registry.py +0 -0
  39. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/auth_dependencies.py +0 -0
  40. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/auth_hooks.py +0 -0
  41. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/cli.py +0 -0
  42. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/cli_commands.py +0 -0
  43. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/conversations.py +0 -0
  44. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/core/__init__.py +0 -0
  45. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/core/error_handlers.py +0 -0
  46. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/core/schema_cache.py +0 -0
  47. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/core/settings.py +0 -0
  48. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/example_data/__init__.py +0 -0
  49. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
  50. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
  51. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/example_data/medical_poc/seed.sql +0 -0
  52. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/example_database.py +0 -0
  53. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/extension_services.py +0 -0
  54. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/extensions.py +0 -0
  55. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/license.py +0 -0
  56. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/package_updates.py +0 -0
  57. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/py.typed +0 -0
  58. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/query_hooks.py +0 -0
  59. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/server_cli.py +0 -0
  60. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/siem.py +0 -0
  61. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api/tls_http.py +0 -0
  62. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api.egg-info/SOURCES.txt +0 -0
  63. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api.egg-info/dependency_links.txt +0 -0
  64. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api.egg-info/entry_points.txt +0 -0
  65. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api.egg-info/requires.txt +0 -0
  66. {gaard_api-0.2.13 → gaard_api-0.2.14}/src/gaard_api.egg-info/top_level.txt +0 -0
  67. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_analysis_api.py +0 -0
  68. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_api_cli.py +0 -0
  69. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_api_registry.py +0 -0
  70. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_conversation_api.py +0 -0
  71. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_error_handlers.py +0 -0
  72. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_license_api.py +0 -0
  73. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_license_service.py +0 -0
  74. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_prompt_runtime.py +0 -0
  75. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_query_error_messages.py +0 -0
  76. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_server_cli.py +0 -0
  77. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_settings.py +0 -0
  78. {gaard_api-0.2.13 → gaard_api-0.2.14}/tests/test_tls_http.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaard-api
3
- Version: 0.2.13
3
+ Version: 0.2.14
4
4
  Summary: GAARD backend web services providing admin interface
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gaard-api"
7
- version = "0.2.13"
7
+ version = "0.2.14"
8
8
  description = "GAARD backend web services providing admin interface"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -16,6 +16,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS ix_conversation_turns_turn_id ON conversation_
16
16
  CREATE TABLE IF NOT EXISTS conversations (id INTEGER NOT NULL, conversation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL, datasource_id VARCHAR(255) NOT NULL, datasource_ids_json TEXT NOT NULL, title VARCHAR(255) NOT NULL, summary_json TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
17
17
  CREATE UNIQUE INDEX IF NOT EXISTS ix_conversations_conversation_id ON conversations (conversation_id);
18
18
  CREATE TABLE IF NOT EXISTS dashboard_user_states (owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, active_dashboard_id VARCHAR(64) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (owner_user_id));
19
+ CREATE TABLE IF NOT EXISTS dashboard_shares (id INTEGER NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
19
20
  CREATE TABLE IF NOT EXISTS dashboard_widgets (id INTEGER NOT NULL, widget_id VARCHAR(64) NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, metric_widget_key VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, visualization_type VARCHAR(50) NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, w INTEGER NOT NULL, h INTEGER NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_widgets_widget_id UNIQUE (widget_id));
20
21
  CREATE TABLE IF NOT EXISTS dashboards (id INTEGER NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
21
22
  CREATE UNIQUE INDEX IF NOT EXISTS ix_dashboards_dashboard_id ON dashboards (dashboard_id);
@@ -63,6 +64,12 @@ CREATE INDEX IF NOT EXISTS ix_conversations_owner_username ON conversations (own
63
64
  CREATE INDEX IF NOT EXISTS ix_conversations_status ON conversations (status);
64
65
  CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_active_dashboard_id ON dashboard_user_states (active_dashboard_id);
65
66
  CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_owner_username ON dashboard_user_states (owner_username);
67
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_access_level ON dashboard_shares (access_level);
68
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_user_id ON dashboard_shares (created_by_user_id);
69
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_username ON dashboard_shares (created_by_username);
70
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_dashboard_id ON dashboard_shares (dashboard_id);
71
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_user_id ON dashboard_shares (target_user_id);
72
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_username ON dashboard_shares (target_username);
66
73
  CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_dashboard_id ON dashboard_widgets (dashboard_id);
67
74
  CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_metric_widget_key ON dashboard_widgets (metric_widget_key);
68
75
  CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_owner_user_id ON dashboard_widgets (owner_user_id);
@@ -97,6 +104,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS ix_conversation_turns_turn_id ON conversation_
97
104
  CREATE TABLE IF NOT EXISTS conversations (id SERIAL NOT NULL, conversation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL, datasource_id VARCHAR(255) NOT NULL, datasource_ids_json TEXT NOT NULL, title VARCHAR(255) NOT NULL, summary_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
98
105
  CREATE UNIQUE INDEX IF NOT EXISTS ix_conversations_conversation_id ON conversations (conversation_id);
99
106
  CREATE TABLE IF NOT EXISTS dashboard_user_states (owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, active_dashboard_id VARCHAR(64) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (owner_user_id));
107
+ CREATE TABLE IF NOT EXISTS dashboard_shares (id SERIAL NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
100
108
  CREATE TABLE IF NOT EXISTS dashboard_widgets (id SERIAL NOT NULL, widget_id VARCHAR(64) NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, metric_widget_key VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, visualization_type VARCHAR(50) NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, w INTEGER NOT NULL, h INTEGER NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_widgets_widget_id UNIQUE (widget_id));
101
109
  CREATE TABLE IF NOT EXISTS dashboards (id SERIAL NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
102
110
  CREATE UNIQUE INDEX IF NOT EXISTS ix_dashboards_dashboard_id ON dashboards (dashboard_id);
@@ -144,6 +152,12 @@ CREATE INDEX IF NOT EXISTS ix_conversations_owner_username ON conversations (own
144
152
  CREATE INDEX IF NOT EXISTS ix_conversations_status ON conversations (status);
145
153
  CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_active_dashboard_id ON dashboard_user_states (active_dashboard_id);
146
154
  CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_owner_username ON dashboard_user_states (owner_username);
155
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_access_level ON dashboard_shares (access_level);
156
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_user_id ON dashboard_shares (created_by_user_id);
157
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_username ON dashboard_shares (created_by_username);
158
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_dashboard_id ON dashboard_shares (dashboard_id);
159
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_user_id ON dashboard_shares (target_user_id);
160
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_username ON dashboard_shares (target_username);
147
161
  CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_dashboard_id ON dashboard_widgets (dashboard_id);
148
162
  CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_metric_widget_key ON dashboard_widgets (metric_widget_key);
149
163
  CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_owner_user_id ON dashboard_widgets (owner_user_id);
@@ -8,3 +8,16 @@ UPDATE identity_privilege_datasource_permissions AS permission SET identity_id =
8
8
  -- table: identity_privilege_table_permissions
9
9
  DELETE FROM identity_privilege_table_permissions AS legacy WHERE EXISTS (SELECT 1 FROM admin_users AS mapped_user WHERE legacy.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username) AND EXISTS (SELECT 1 FROM identity_privilege_table_permissions AS duplicate WHERE duplicate.connector_id = legacy.connector_id AND duplicate.table_name = legacy.table_name AND duplicate.id <> legacy.id AND (duplicate.identity_id = CAST(mapped_user.id AS VARCHAR(255)) OR (duplicate.id < legacy.id AND duplicate.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username)))));
10
10
  UPDATE identity_privilege_table_permissions AS permission SET identity_id = (SELECT CAST(mapped_user.id AS VARCHAR(255)) FROM admin_users AS mapped_user WHERE permission.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username) ORDER BY mapped_user.id LIMIT 1) WHERE EXISTS (SELECT 1 FROM admin_users AS mapped_user WHERE permission.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username));
11
+
12
+ -- tag: 2026-08-01.dashboard-sharing.v1
13
+ -- dialect: sqlite
14
+ CREATE TABLE IF NOT EXISTS dashboard_shares (id INTEGER NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
15
+ -- dialect: postgresql
16
+ CREATE TABLE IF NOT EXISTS dashboard_shares (id SERIAL NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
17
+ -- dialect: all
18
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_access_level ON dashboard_shares (access_level);
19
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_user_id ON dashboard_shares (created_by_user_id);
20
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_username ON dashboard_shares (created_by_username);
21
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_dashboard_id ON dashboard_shares (dashboard_id);
22
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_user_id ON dashboard_shares (target_user_id);
23
+ CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_username ON dashboard_shares (target_username);
@@ -209,6 +209,31 @@ class DashboardUserState(Base):
209
209
  )
210
210
 
211
211
 
212
+ class DashboardShare(Base):
213
+ __tablename__ = "dashboard_shares"
214
+ __table_args__ = (
215
+ UniqueConstraint(
216
+ "dashboard_id",
217
+ "target_user_id",
218
+ name="uq_dashboard_shares_dashboard_target",
219
+ ),
220
+ )
221
+
222
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
223
+ dashboard_id: Mapped[str] = mapped_column(String(64), index=True)
224
+ target_user_id: Mapped[str] = mapped_column(String(255), index=True)
225
+ target_username: Mapped[str] = mapped_column(String(255), index=True, default="")
226
+ access_level: Mapped[str] = mapped_column(String(20), index=True)
227
+ created_by_user_id: Mapped[str] = mapped_column(String(255), index=True)
228
+ created_by_username: Mapped[str] = mapped_column(String(255), index=True, default="")
229
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
230
+ updated_at: Mapped[datetime] = mapped_column(
231
+ DateTime(timezone=True),
232
+ default=utc_now,
233
+ onupdate=utc_now,
234
+ )
235
+
236
+
212
237
  class UserDatasourceSelection(Base):
213
238
  """Client datasource selections, scoped to one authenticated user."""
214
239