adapt-server 0.1.4__tar.gz → 0.2.2__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 (107) hide show
  1. adapt_server-0.2.2/PKG-INFO +176 -0
  2. adapt_server-0.2.2/README.md +131 -0
  3. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/__init__.py +1 -1
  4. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/groups.py +13 -4
  5. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/models.py +23 -5
  6. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/resources.py +5 -0
  7. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/ui.py +2 -1
  8. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/users.py +20 -5
  9. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/app.py +219 -10
  10. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/check.py +2 -0
  11. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/list_endpoints.py +5 -0
  12. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/serve.py +3 -0
  13. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/config.py +2 -1
  14. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/dataset_plugin.py +65 -2
  15. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/media_plugin.py +5 -6
  16. adapt_server-0.2.2/adapt/security.py +97 -0
  17. adapt_server-0.2.2/adapt/security_urls.py +32 -0
  18. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/static/admin/app.js +25 -17
  19. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/static/admin/style.css +1 -1
  20. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/admin/index.html +0 -1
  21. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/admin_base.html +23 -0
  22. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/base.html +31 -2
  23. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/datatable.html +11 -3
  24. adapt_server-0.2.2/adapt/templates/landing.html +93 -0
  25. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/login.html +30 -3
  26. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/profile.html +12 -3
  27. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/utils/__init__.py +4 -10
  28. adapt_server-0.2.2/adapt_server.egg-info/PKG-INFO +176 -0
  29. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt_server.egg-info/SOURCES.txt +5 -1
  30. {adapt_server-0.1.4 → adapt_server-0.2.2}/pyproject.toml +1 -1
  31. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_admin.py +19 -0
  32. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_auth.py +111 -1
  33. adapt_server-0.2.2/tests/test_cli_integration.py +43 -0
  34. adapt_server-0.2.2/tests/test_integration.py +527 -0
  35. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_phase3.py +8 -3
  36. adapt_server-0.2.2/tests/test_readonly.py +120 -0
  37. adapt_server-0.2.2/tests/test_security_urls.py +23 -0
  38. adapt_server-0.1.4/PKG-INFO +0 -582
  39. adapt_server-0.1.4/README.md +0 -537
  40. adapt_server-0.1.4/adapt/templates/landing.html +0 -56
  41. adapt_server-0.1.4/adapt_server.egg-info/PKG-INFO +0 -582
  42. adapt_server-0.1.4/tests/test_integration.py +0 -270
  43. adapt_server-0.1.4/tests/test_readonly.py +0 -70
  44. {adapt_server-0.1.4 → adapt_server-0.2.2}/LICENSE +0 -0
  45. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/__main__.py +0 -0
  46. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/__init__.py +0 -0
  47. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/api_keys.py +0 -0
  48. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/audit_logs.py +0 -0
  49. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/cache.py +0 -0
  50. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/locks.py +0 -0
  51. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/admin/permissions.py +0 -0
  52. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/api_keys.py +0 -0
  53. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/audit.py +0 -0
  54. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/auth/__init__.py +0 -0
  55. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/auth/dependencies.py +0 -0
  56. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/auth/password.py +0 -0
  57. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/auth/routes.py +0 -0
  58. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/auth/session.py +0 -0
  59. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/cache.py +0 -0
  60. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/cli.py +0 -0
  61. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/__init__.py +0 -0
  62. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/addsuperuser.py +0 -0
  63. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/__init__.py +0 -0
  64. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/add_to_group.py +0 -0
  65. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/create_group.py +0 -0
  66. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/create_permissions.py +0 -0
  67. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/create_user.py +0 -0
  68. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/delete_group.py +0 -0
  69. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/delete_user.py +0 -0
  70. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/list_groups.py +0 -0
  71. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/list_resources.py +0 -0
  72. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/list_users.py +0 -0
  73. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/commands/admin/remove_from_group.py +0 -0
  74. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/discovery.py +0 -0
  75. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/locks.py +0 -0
  76. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/models.py +0 -0
  77. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/permissions.py +0 -0
  78. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/__init__.py +0 -0
  79. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/base.py +0 -0
  80. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/csv_plugin.py +0 -0
  81. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/excel_plugin.py +0 -0
  82. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/html_plugin.py +0 -0
  83. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/markdown_plugin.py +0 -0
  84. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/parquet_plugin.py +0 -0
  85. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/plugins/python_plugin.py +0 -0
  86. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/routes.py +0 -0
  87. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/storage.py +0 -0
  88. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/media_gallery.html +0 -0
  89. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/templates/media_player.html +0 -0
  90. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt/utils/query.py +0 -0
  91. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt_server.egg-info/dependency_links.txt +0 -0
  92. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt_server.egg-info/entry_points.txt +0 -0
  93. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt_server.egg-info/requires.txt +0 -0
  94. {adapt_server-0.1.4 → adapt_server-0.2.2}/adapt_server.egg-info/top_level.txt +0 -0
  95. {adapt_server-0.1.4 → adapt_server-0.2.2}/setup.cfg +0 -0
  96. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_config.py +0 -0
  97. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_constraints.py +0 -0
  98. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_csv_plugin.py +0 -0
  99. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_dataset_plugin.py +0 -0
  100. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_discovery.py +0 -0
  101. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_html_plugin.py +0 -0
  102. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_locks.py +0 -0
  103. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_markdown_plugin.py +0 -0
  104. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_on_delete.py +0 -0
  105. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_parquet_plugin.py +0 -0
  106. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_plugin_interface.py +0 -0
  107. {adapt_server-0.1.4 → adapt_server-0.2.2}/tests/test_python_plugin.py +0 -0
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: adapt-server
3
+ Version: 0.2.2
4
+ Summary: Adaptive file-backed FastAPI server that turns datasets into CRUD APIs and UIs.
5
+ Author-email: notesofcliff <notesofcliff@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/McInci/adapt
8
+ Project-URL: Repository, https://github.com/McIndi/adapt
9
+ Project-URL: Issues, https://github.com/McIndi/adapt/issues
10
+ Keywords: fastapi,api,csv,excel,parquet,markdown,media
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: fastapi
25
+ Requires-Dist: uvicorn
26
+ Requires-Dist: sqlmodel
27
+ Requires-Dist: pendulum
28
+ Requires-Dist: watchfiles
29
+ Requires-Dist: jinja2
30
+ Requires-Dist: openpyxl
31
+ Requires-Dist: markdown
32
+ Requires-Dist: python-multipart
33
+ Requires-Dist: mutagen
34
+ Requires-Dist: moviepy
35
+ Requires-Dist: pillow
36
+ Requires-Dist: python-json-logger
37
+ Requires-Dist: pandas
38
+ Requires-Dist: fastparquet
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest; extra == "dev"
41
+ Requires-Dist: httpx; extra == "dev"
42
+ Requires-Dist: build; extra == "dev"
43
+ Requires-Dist: twine; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # Adapt
47
+
48
+ Adapt is a FastAPI server that turns files in a directory into APIs and UIs.
49
+
50
+ - Datasets (`.csv`, `.xlsx`, `.parquet`) become CRUD endpoints and DataTables UIs
51
+ - Markdown/HTML become browsable pages
52
+ - Media files become streaming endpoints and player/gallery UIs
53
+ - Python files can register custom routers
54
+
55
+ ## Quick Start
56
+
57
+ ```bash
58
+ pip install adapt-server
59
+ adapt addsuperuser --username admin /path/to/docroot
60
+ adapt serve /path/to/docroot
61
+
62
+ # Generate permissions for all discovered resources
63
+ adapt admin create-permissions /path/to/docroot __all__
64
+
65
+ # Everything below here can be done in the admin UI at
66
+ # http://localhost:8000/admin/ after logging in with the superuser account.
67
+ #
68
+ # Create a regular user
69
+ adapt admin create-user --username editor --password secret /path/to/docroot
70
+
71
+ # By default, the editor user has no permissions.
72
+ # See available groups (created by `adapt admin create-permissions`) and assign user to desired group
73
+ adapt admin list-groups /path/to/docroot
74
+ adapt admin add-to-group --username editor --group <group_name> /path/to/docroot
75
+ ```
76
+
77
+ Useful URLs:
78
+
79
+ - `/` landing page
80
+ - `/admin/` admin UI
81
+ - `/api/<resource>` resource API
82
+ - `/ui/<resource>` resource UI
83
+ - `/schema/<resource>` resource schema
84
+
85
+ ## What Adapt Generates
86
+
87
+ From files in your docroot, Adapt auto-discovers resources and mounts routes with extensionless URLs where possible.
88
+
89
+ Example:
90
+
91
+ ```text
92
+ data/
93
+ employees.csv
94
+ sales.xlsx
95
+ video.mp4
96
+ readme.md
97
+ stats.py
98
+ ```
99
+
100
+ Rough output:
101
+
102
+ - `/api/employees`, `/ui/employees`, `/schema/employees`
103
+ - `/api/sales/<sheet>`, `/ui/sales/<sheet>`
104
+ - `/media/video.mp4`, `/ui/video.mp4`, `/ui/media`
105
+ - `/readme`
106
+ - `/api/stats/*`
107
+
108
+ ## Current Security Posture
109
+
110
+ This reflects the current implementation in the codebase.
111
+
112
+ ### In Place
113
+
114
+ - **Authentication:** session cookies and API keys (`X-API-Key`)
115
+ - **Authorization:** RBAC (users, groups, permissions), plus superuser bypass
116
+ - **Password security:** PBKDF2 hashing with per-user salts
117
+ - **Session security:** expiration enforcement, sliding renewal, cleanup task
118
+ - **CSRF protection:** enforced for cookie-authenticated unsafe methods (`POST/PUT/PATCH/DELETE`), including mixed session + API-key requests
119
+ - **Redirect hardening:** login `next` paths are validated as local relative paths
120
+ - **Response hardening:** CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, HSTS (when TLS is enabled)
121
+ - **Host header hardening:** Trusted Host middleware
122
+ - **Data integrity:** lock-based, atomic writes for mutable dataset plugins
123
+ - **Auditability:** admin/audit logging for security-relevant actions
124
+ - **Sensitive response cleanup:** admin user APIs no longer expose `password_hash`
125
+
126
+ ### Important Deployment Notes
127
+
128
+ - Use TLS in non-local environments (`--tls-cert` + `--tls-key`) so secure cookies and HSTS protections are effective.
129
+ - API-key-only clients are exempt from CSRF checks by design; cookie-auth browser flows require CSRF tokens.
130
+
131
+ ## Core Features
132
+
133
+ - Adaptive discovery and route generation
134
+ - Dataset CRUD with schema exposure
135
+ - Caching with invalidation on mutations
136
+ - Built-in admin UI for users/groups/permissions/locks/cache/api keys/audit logs
137
+ - Plugin architecture with companion overrides in `.adapt/`
138
+
139
+ ## Dataset Mutation Envelope
140
+
141
+ For dataset endpoints, write operations use this payload structure:
142
+
143
+ ```json
144
+ {
145
+ "action": "create|update|delete",
146
+ "data": []
147
+ }
148
+ ```
149
+
150
+ Use object data for `update`/`delete` as needed (for example, with `_row_id`).
151
+
152
+ ## CLI (Common Commands)
153
+
154
+ ```bash
155
+ adapt serve <root> [--host ... --port ... --tls-cert ... --tls-key ... --reload --readonly --debug]
156
+ adapt check <root>
157
+ adapt addsuperuser <root> --username <name>
158
+ adapt list-endpoints <root>
159
+ adapt admin list-resources <root>
160
+ adapt admin create-permissions <root> __all__
161
+ ```
162
+
163
+ ## Documentation
164
+
165
+ Detailed docs live under `docs/manual/`.
166
+
167
+ - Manual index: [docs/manual/index.md](docs/manual/index.md)
168
+ - Security: [docs/manual/security.md](docs/manual/security.md)
169
+ - Quick start: [docs/manual/quick_start.md](docs/manual/quick_start.md)
170
+ - Configuration: [docs/manual/configuration.md](docs/manual/configuration.md)
171
+ - API reference: [docs/manual/api_reference.md](docs/manual/api_reference.md)
172
+ - Plugin development: [docs/manual/plugin_development.md](docs/manual/plugin_development.md)
173
+
174
+ ## License
175
+
176
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,131 @@
1
+ # Adapt
2
+
3
+ Adapt is a FastAPI server that turns files in a directory into APIs and UIs.
4
+
5
+ - Datasets (`.csv`, `.xlsx`, `.parquet`) become CRUD endpoints and DataTables UIs
6
+ - Markdown/HTML become browsable pages
7
+ - Media files become streaming endpoints and player/gallery UIs
8
+ - Python files can register custom routers
9
+
10
+ ## Quick Start
11
+
12
+ ```bash
13
+ pip install adapt-server
14
+ adapt addsuperuser --username admin /path/to/docroot
15
+ adapt serve /path/to/docroot
16
+
17
+ # Generate permissions for all discovered resources
18
+ adapt admin create-permissions /path/to/docroot __all__
19
+
20
+ # Everything below here can be done in the admin UI at
21
+ # http://localhost:8000/admin/ after logging in with the superuser account.
22
+ #
23
+ # Create a regular user
24
+ adapt admin create-user --username editor --password secret /path/to/docroot
25
+
26
+ # By default, the editor user has no permissions.
27
+ # See available groups (created by `adapt admin create-permissions`) and assign user to desired group
28
+ adapt admin list-groups /path/to/docroot
29
+ adapt admin add-to-group --username editor --group <group_name> /path/to/docroot
30
+ ```
31
+
32
+ Useful URLs:
33
+
34
+ - `/` landing page
35
+ - `/admin/` admin UI
36
+ - `/api/<resource>` resource API
37
+ - `/ui/<resource>` resource UI
38
+ - `/schema/<resource>` resource schema
39
+
40
+ ## What Adapt Generates
41
+
42
+ From files in your docroot, Adapt auto-discovers resources and mounts routes with extensionless URLs where possible.
43
+
44
+ Example:
45
+
46
+ ```text
47
+ data/
48
+ employees.csv
49
+ sales.xlsx
50
+ video.mp4
51
+ readme.md
52
+ stats.py
53
+ ```
54
+
55
+ Rough output:
56
+
57
+ - `/api/employees`, `/ui/employees`, `/schema/employees`
58
+ - `/api/sales/<sheet>`, `/ui/sales/<sheet>`
59
+ - `/media/video.mp4`, `/ui/video.mp4`, `/ui/media`
60
+ - `/readme`
61
+ - `/api/stats/*`
62
+
63
+ ## Current Security Posture
64
+
65
+ This reflects the current implementation in the codebase.
66
+
67
+ ### In Place
68
+
69
+ - **Authentication:** session cookies and API keys (`X-API-Key`)
70
+ - **Authorization:** RBAC (users, groups, permissions), plus superuser bypass
71
+ - **Password security:** PBKDF2 hashing with per-user salts
72
+ - **Session security:** expiration enforcement, sliding renewal, cleanup task
73
+ - **CSRF protection:** enforced for cookie-authenticated unsafe methods (`POST/PUT/PATCH/DELETE`), including mixed session + API-key requests
74
+ - **Redirect hardening:** login `next` paths are validated as local relative paths
75
+ - **Response hardening:** CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, HSTS (when TLS is enabled)
76
+ - **Host header hardening:** Trusted Host middleware
77
+ - **Data integrity:** lock-based, atomic writes for mutable dataset plugins
78
+ - **Auditability:** admin/audit logging for security-relevant actions
79
+ - **Sensitive response cleanup:** admin user APIs no longer expose `password_hash`
80
+
81
+ ### Important Deployment Notes
82
+
83
+ - Use TLS in non-local environments (`--tls-cert` + `--tls-key`) so secure cookies and HSTS protections are effective.
84
+ - API-key-only clients are exempt from CSRF checks by design; cookie-auth browser flows require CSRF tokens.
85
+
86
+ ## Core Features
87
+
88
+ - Adaptive discovery and route generation
89
+ - Dataset CRUD with schema exposure
90
+ - Caching with invalidation on mutations
91
+ - Built-in admin UI for users/groups/permissions/locks/cache/api keys/audit logs
92
+ - Plugin architecture with companion overrides in `.adapt/`
93
+
94
+ ## Dataset Mutation Envelope
95
+
96
+ For dataset endpoints, write operations use this payload structure:
97
+
98
+ ```json
99
+ {
100
+ "action": "create|update|delete",
101
+ "data": []
102
+ }
103
+ ```
104
+
105
+ Use object data for `update`/`delete` as needed (for example, with `_row_id`).
106
+
107
+ ## CLI (Common Commands)
108
+
109
+ ```bash
110
+ adapt serve <root> [--host ... --port ... --tls-cert ... --tls-key ... --reload --readonly --debug]
111
+ adapt check <root>
112
+ adapt addsuperuser <root> --username <name>
113
+ adapt list-endpoints <root>
114
+ adapt admin list-resources <root>
115
+ adapt admin create-permissions <root> __all__
116
+ ```
117
+
118
+ ## Documentation
119
+
120
+ Detailed docs live under `docs/manual/`.
121
+
122
+ - Manual index: [docs/manual/index.md](docs/manual/index.md)
123
+ - Security: [docs/manual/security.md](docs/manual/security.md)
124
+ - Quick start: [docs/manual/quick_start.md](docs/manual/quick_start.md)
125
+ - Configuration: [docs/manual/configuration.md](docs/manual/configuration.md)
126
+ - API reference: [docs/manual/api_reference.md](docs/manual/api_reference.md)
127
+ - Plugin development: [docs/manual/plugin_development.md](docs/manual/plugin_development.md)
128
+
129
+ ## License
130
+
131
+ MIT. See [LICENSE](LICENSE).
@@ -1,3 +1,3 @@
1
1
  """Adapt package namespace."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.2.2"
@@ -7,7 +7,7 @@ from ..auth import require_superuser
7
7
  from ..storage import User, Group, UserGroup, get_db_session
8
8
  from ..audit import log_action
9
9
  from . import router
10
- from .models import GroupCreate, GroupRead
10
+ from .models import GroupCreate, GroupReadSafe, GroupUserRead
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -58,7 +58,7 @@ def list_groups(
58
58
  logger.debug("Listed %d groups with query params", len(result))
59
59
  return result
60
60
 
61
- @router.get("/groups/{group_id}", response_model=GroupRead)
61
+ @router.get("/groups/{group_id}", response_model=GroupReadSafe)
62
62
  def get_group(group_id: int, db: Session = Depends(get_db_session), user = Depends(require_superuser)):
63
63
  """Get a group by ID, including its users."""
64
64
  group = db.get(Group, group_id)
@@ -72,11 +72,20 @@ def get_group(group_id: int, db: Session = Depends(get_db_session), user = Depen
72
72
  users = db.exec(stmt).all()
73
73
 
74
74
  logger.debug("Retrieved group %s with %d users", group.name, len(users))
75
- return GroupRead(
75
+ return GroupReadSafe(
76
76
  id=group.id,
77
77
  name=group.name,
78
78
  description=group.description,
79
- users=users
79
+ users=[
80
+ GroupUserRead(
81
+ id=u.id,
82
+ username=u.username,
83
+ is_active=u.is_active,
84
+ is_superuser=u.is_superuser,
85
+ created_at=u.created_at,
86
+ )
87
+ for u in users
88
+ ]
80
89
  )
81
90
 
82
91
  @router.post("/groups", response_model=Group)
@@ -1,9 +1,8 @@
1
+ from datetime import datetime
1
2
  from pydantic import BaseModel
2
3
  from typing import List, Optional
3
4
  import logging
4
5
 
5
- from ..storage import User
6
-
7
6
  logger = logging.getLogger(__name__)
8
7
 
9
8
  class UserCreate(BaseModel):
@@ -17,12 +16,31 @@ class GroupCreate(BaseModel):
17
16
  name: str
18
17
  description: Optional[str] = None
19
18
 
20
- class GroupRead(BaseModel):
21
- """Model for reading group data with users."""
19
+
20
+ class UserPublic(BaseModel):
21
+ """Safe user response model without sensitive fields."""
22
+ id: int
23
+ username: str
24
+ is_active: bool
25
+ is_superuser: bool
26
+ created_at: datetime
27
+
28
+
29
+ class GroupUserRead(BaseModel):
30
+ """Safe group user representation."""
31
+ id: int
32
+ username: str
33
+ is_active: bool
34
+ is_superuser: bool
35
+ created_at: datetime
36
+
37
+
38
+ class GroupReadSafe(BaseModel):
39
+ """Safe model for reading group data with users."""
22
40
  id: int
23
41
  name: str
24
42
  description: Optional[str] = None
25
- users: List[User] = []
43
+ users: List[GroupUserRead] = []
26
44
 
27
45
  class PermissionCreate(BaseModel):
28
46
  """Model for creating a new permission."""
@@ -2,8 +2,10 @@ from pathlib import Path
2
2
  from typing import List
3
3
  import logging
4
4
 
5
+ from .. import cache
5
6
  from ..config import AdaptConfig
6
7
  from ..discovery import discover_resources
8
+ from ..storage import init_database
7
9
 
8
10
  logger = logging.getLogger(__name__)
9
11
 
@@ -14,6 +16,9 @@ def list_resources(root: Path) -> List[str]:
14
16
  Returns a list of resource identifiers including sub-namespaces for multi-resource files.
15
17
  """
16
18
  config = AdaptConfig(root=root)
19
+ config.load_from_file()
20
+ init_database(config.db_path)
21
+ cache.configure(str(config.db_path))
17
22
  resources = discover_resources(root, config)
18
23
  resource_names = []
19
24
  for r in resources:
@@ -4,6 +4,7 @@ import logging
4
4
 
5
5
  from ..auth import get_current_user
6
6
  from ..utils import build_ui_links
7
+ from ..security_urls import login_redirect_url
7
8
  from . import router
8
9
 
9
10
  logger = logging.getLogger(__name__)
@@ -14,7 +15,7 @@ def admin_ui(request: Request):
14
15
  user = get_current_user(request)
15
16
  if not user or not getattr(user, "is_superuser", False):
16
17
  logger.debug("Non-superuser access to admin UI, redirecting to login")
17
- return RedirectResponse(url="/auth/login?next=/admin/", status_code=302)
18
+ return RedirectResponse(url=login_redirect_url("/admin/"), status_code=302)
18
19
 
19
20
  # Build context for template
20
21
  is_superuser = getattr(user, 'is_superuser', False)
@@ -7,11 +7,11 @@ from ..auth import require_superuser, hash_password
7
7
  from ..storage import User, get_db_session
8
8
  from ..audit import log_action
9
9
  from . import router
10
- from .models import UserCreate
10
+ from .models import UserCreate, UserPublic
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
- @router.get("/users", response_model=List[User])
14
+ @router.get("/users", response_model=List[UserPublic])
15
15
  def list_users(
16
16
  db: Session = Depends(get_db_session),
17
17
  limit: int = Query(None, ge=1, le=1000),
@@ -59,9 +59,18 @@ def list_users(
59
59
  result.append(u)
60
60
 
61
61
  logger.debug("Listed %d users with query params", len(result))
62
- return result
62
+ return [
63
+ UserPublic(
64
+ id=u.id,
65
+ username=u.username,
66
+ is_active=u.is_active,
67
+ is_superuser=u.is_superuser,
68
+ created_at=u.created_at,
69
+ )
70
+ for u in result
71
+ ]
63
72
 
64
- @router.post("/users", response_model=User)
73
+ @router.post("/users", response_model=UserPublic)
65
74
  def create_user(user_data: UserCreate, request: Request, db: Session = Depends(get_db_session), user: User = Depends(require_superuser)):
66
75
  """Create a new user."""
67
76
  # Check if server is in read-only mode
@@ -87,7 +96,13 @@ def create_user(user_data: UserCreate, request: Request, db: Session = Depends(g
87
96
  log_action(request, "create_user", "user", f"Created user {new_user.username}", user.id)
88
97
  logger.info("Created user %s", new_user.username)
89
98
 
90
- return new_user
99
+ return UserPublic(
100
+ id=new_user.id,
101
+ username=new_user.username,
102
+ is_active=new_user.is_active,
103
+ is_superuser=new_user.is_superuser,
104
+ created_at=new_user.created_at,
105
+ )
91
106
 
92
107
  @router.delete("/users/{user_id}")
93
108
  def delete_user(user_id: int, request: Request, db: Session = Depends(get_db_session), user: User = Depends(require_superuser)):