dashtro 0.1.0__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.
- dashtro-0.1.0/LICENSE +21 -0
- dashtro-0.1.0/PKG-INFO +247 -0
- dashtro-0.1.0/README.md +228 -0
- dashtro-0.1.0/cms_backend/__init__.py +0 -0
- dashtro-0.1.0/cms_backend/api/__init__.py +0 -0
- dashtro-0.1.0/cms_backend/api/utils/__init__.py +49 -0
- dashtro-0.1.0/cms_backend/api/utils/actor.py +11 -0
- dashtro-0.1.0/cms_backend/api/utils/api_key_auth.py +31 -0
- dashtro-0.1.0/cms_backend/api/utils/audit_client.py +237 -0
- dashtro-0.1.0/cms_backend/api/utils/mongodb_client.py +183 -0
- dashtro-0.1.0/cms_backend/api/utils/postgres_audit_client.py +207 -0
- dashtro-0.1.0/cms_backend/api/utils/postgres_client.py +1258 -0
- dashtro-0.1.0/cms_backend/api/utils/schema.py +69 -0
- dashtro-0.1.0/cms_backend/api/utils/sqlite_client.py +1236 -0
- dashtro-0.1.0/cms_backend/api/utils/workspace_diff.py +44 -0
- dashtro-0.1.0/cms_backend/config.py +13 -0
- dashtro-0.1.0/cms_backend/main.py +78 -0
- dashtro-0.1.0/cms_backend/models/__init__.py +0 -0
- dashtro-0.1.0/cms_backend/models/collection.py +48 -0
- dashtro-0.1.0/cms_backend/models/field_types.py +75 -0
- dashtro-0.1.0/cms_backend/models/project.py +23 -0
- dashtro-0.1.0/cms_backend/models/rich_text_component.py +22 -0
- dashtro-0.1.0/cms_backend/models/schema.py +213 -0
- dashtro-0.1.0/cms_backend/scripts/__init__.py +0 -0
- dashtro-0.1.0/cms_backend/scripts/cms_schema.py +1036 -0
- dashtro-0.1.0/cms_backend/scripts/migrate_image_keys.py +54 -0
- dashtro-0.1.0/cms_mcp/__init__.py +0 -0
- dashtro-0.1.0/cms_mcp/server.py +316 -0
- dashtro-0.1.0/dashtro.egg-info/PKG-INFO +247 -0
- dashtro-0.1.0/dashtro.egg-info/SOURCES.txt +34 -0
- dashtro-0.1.0/dashtro.egg-info/dependency_links.txt +1 -0
- dashtro-0.1.0/dashtro.egg-info/entry_points.txt +3 -0
- dashtro-0.1.0/dashtro.egg-info/requires.txt +6 -0
- dashtro-0.1.0/dashtro.egg-info/top_level.txt +2 -0
- dashtro-0.1.0/pyproject.toml +68 -0
- dashtro-0.1.0/setup.cfg +4 -0
dashtro-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Atharva Devasthali
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dashtro-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dashtro
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DashTro CMS backup/restore CLI, plus a direct-database-access MCP server
|
|
5
|
+
Author: Atharva Devasthali
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/1atharvad/dashtro
|
|
8
|
+
Project-URL: Repository, https://github.com/1atharvad/dashtro
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pydantic>=2.9
|
|
13
|
+
Requires-Dist: python-decouple>=3.8
|
|
14
|
+
Requires-Dist: PyJWT>=2.8
|
|
15
|
+
Requires-Dist: psycopg2-binary>=2.9
|
|
16
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
17
|
+
Requires-Dist: httpx>=0.27.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# Dashtro
|
|
21
|
+
|
|
22
|
+
[](https://github.com/1atharvad/dashtro/actions/workflows/build-image.yml)
|
|
23
|
+
[](https://github.com/1atharvad/dashtro/pkgs/container/dashtro)
|
|
24
|
+
[](https://www.npmjs.com/package/@dashtro/client)
|
|
25
|
+
[](https://www.npmjs.com/package/@dashtro/mcp)
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
|
|
28
|
+
A self-hosted CMS with a project → workspace → collection → document model, a
|
|
29
|
+
FastAPI backend, and a React/TypeScript frontend. Ships as a single Docker
|
|
30
|
+
image, published to `ghcr.io/1atharvad/dashtro`. This repo only builds and
|
|
31
|
+
publishes that image — running it in production (nginx, tunnel/domain
|
|
32
|
+
routing, etc.) is owned by the consuming project (e.g. the portfolio site
|
|
33
|
+
that embeds Dashtro as its admin/CMS backend).
|
|
34
|
+
|
|
35
|
+
## Contents
|
|
36
|
+
|
|
37
|
+
- [Structure](#structure)
|
|
38
|
+
- [Packages](#packages)
|
|
39
|
+
- [Architecture](#architecture)
|
|
40
|
+
- [Data backends](#data-backends)
|
|
41
|
+
- [Running locally (dev)](#running-locally-dev)
|
|
42
|
+
- [Running the image elsewhere](#running-the-image-elsewhere)
|
|
43
|
+
- [Environment variables](#environment-variables)
|
|
44
|
+
- [Backup / restore CLI](#backup--restore-cli)
|
|
45
|
+
- [CI/CD](#cicd)
|
|
46
|
+
- [Tests](#tests)
|
|
47
|
+
|
|
48
|
+
## Structure
|
|
49
|
+
|
|
50
|
+
| Path | What it is |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| [`cms_backend/`](cms_backend/) | FastAPI backend — API, auth, schema engine, data clients. See its [README](cms_backend/README.md). |
|
|
53
|
+
| [`cms-frontend/`](cms-frontend/) | React + TypeScript + Vite frontend. See its [README](cms-frontend/README.md). |
|
|
54
|
+
| [`cms_mcp/`](cms_mcp/) | MCP server exposing Dashtro operations to MCP-compatible clients (Python, console script `dashtro-mcp`, direct database access). |
|
|
55
|
+
| [`sdk/js/`](sdk/js/) | `@dashtro/client` — JS/TS client SDK for `/api/sdk/*`. |
|
|
56
|
+
| [`sdk/python/`](sdk/python/) | `dashtro-client` — Python client SDK for `/api/sdk/*`, released in lockstep with `sdk/js/`. |
|
|
57
|
+
| [`sdk/mcp/`](sdk/mcp/) | `@dashtro/mcp` — npx-runnable MCP server (Node/TS port of `cms_mcp/`) for consuming projects; talks to `/api/cms/*` over HTTP, no Python required. |
|
|
58
|
+
| [`nginx/`](nginx/) | Reverse proxy config for local dev only. |
|
|
59
|
+
|
|
60
|
+
## Packages
|
|
61
|
+
|
|
62
|
+
Everything this repo publishes, and what it's for:
|
|
63
|
+
|
|
64
|
+
| Package | Registry | Install | What it's for |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `ghcr.io/1atharvad/dashtro` | [GHCR](https://github.com/1atharvad/dashtro/pkgs/container/dashtro) | `docker pull ghcr.io/1atharvad/dashtro` | The CMS itself — backend + built frontend in one image. See [Running the image elsewhere](#running-the-image-elsewhere). |
|
|
67
|
+
| [`@dashtro/client`](sdk/js/) | [npm](https://www.npmjs.com/package/@dashtro/client) | `npm install @dashtro/client` | JS/TS client SDK for `/api/sdk/*` — read/write a project's documents and RTDB from an external app. |
|
|
68
|
+
| [`dashtro-client`](sdk/python/) | [PyPI](https://pypi.org/project/dashtro-client/) | `pip install dashtro-client` | Python equivalent of `@dashtro/client`, released in lockstep with it. |
|
|
69
|
+
| [`@dashtro/mcp`](sdk/mcp/) | [npm](https://www.npmjs.com/package/@dashtro/mcp) | `npx @dashtro/mcp init` | npx-runnable MCP server — lets Claude/other MCP clients read and write your CMS content. Node port of `cms_mcp/`, no Python needed. |
|
|
70
|
+
| `dashtro` | not published | n/a — run from a repo checkout | Root package: the `dashtro` backup/restore CLI (`cms_backend/scripts/cms_schema.py`, see [Backup / restore CLI](#backup--restore-cli)). Also bundles `cms_mcp`/`dashtro-mcp`, but use `@dashtro/mcp` instead for MCP access — this package isn't on PyPI. |
|
|
71
|
+
|
|
72
|
+
## Architecture
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart LR
|
|
76
|
+
Browser["Browser SPA\n(cms-frontend)"] -->|"/api/*"| Backend["FastAPI backend\n(cms_backend)"]
|
|
77
|
+
Backend --> DB[("sqlite / postgres")]
|
|
78
|
+
ExternalApp["External app"] -->|"/api/sdk/*"| Backend
|
|
79
|
+
ExternalApp -.->|uses| ClientSDK["@dashtro/client\ndashtro-client"]
|
|
80
|
+
MCPClient["MCP client\n(Claude, etc.)"] -->|stdio, npx| MCPNode["@dashtro/mcp\n(Node)"]
|
|
81
|
+
MCPNode -->|"/api/cms/*"| Backend
|
|
82
|
+
MCPClient -.->|stdio, local install| MCPPy["dashtro-mcp\n(cms_mcp, Python)"]
|
|
83
|
+
MCPPy -->|"/api/cms/*"| Backend
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Everything ultimately talks to the same FastAPI backend — the frontend over
|
|
87
|
+
its own API, external apps over the API-key-scoped `/api/sdk/*` surface (via
|
|
88
|
+
either client SDK), and MCP clients over `/api/cms/*` through either MCP
|
|
89
|
+
server (`@dashtro/mcp` for a zero-install `npx` setup, or `cms_mcp`/`dashtro-mcp`
|
|
90
|
+
if you're already in a Python environment with direct database access).
|
|
91
|
+
|
|
92
|
+
## Data backends
|
|
93
|
+
|
|
94
|
+
`DB_TYPE` selects the storage backend, defaulting to `sqlite`:
|
|
95
|
+
|
|
96
|
+
- `sqlite` — single-file DB, zero external dependencies. Default.
|
|
97
|
+
- `postgres` — set `DB_HOST`/`DB_PORT`/`DB_NAME`/`DB_USER`/`DB_PASSWORD`. An optional
|
|
98
|
+
bundled `postgres` compose service is available (see below) if you don't want to
|
|
99
|
+
point at an external instance.
|
|
100
|
+
|
|
101
|
+
Both backends implement the same interface (`get_data_client()` / `get_auth_client()` /
|
|
102
|
+
`get_audit_client()` in `cms_backend/api/utils/__init__.py`), so routers don't change
|
|
103
|
+
depending on which one is active.
|
|
104
|
+
|
|
105
|
+
## Running locally (dev)
|
|
106
|
+
|
|
107
|
+
Hot-reloading, separate frontend/backend containers, Cloudflare tunnel support:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
cp .env.example .env # fill in real values
|
|
111
|
+
npm run dev # docker compose -f docker-compose.dev.yml up --build
|
|
112
|
+
npm run dev:down
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Optional local Postgres instead of an external one:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
docker compose -f docker-compose.dev.yml --profile postgres up
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Running the image elsewhere
|
|
122
|
+
|
|
123
|
+
The published image serves both the API and the built SPA on port 7312, and
|
|
124
|
+
reads its config from env vars (see below) — no other dependency beyond
|
|
125
|
+
whichever `DB_TYPE` backend you point it at:
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
dashtro:
|
|
129
|
+
image: ghcr.io/1atharvad/dashtro:latest
|
|
130
|
+
environment:
|
|
131
|
+
JWT_SECRET_KEY: ...
|
|
132
|
+
CORS_ORIGINS: ...
|
|
133
|
+
CMS_PUBLIC_URL: ...
|
|
134
|
+
DB_TYPE: sqlite
|
|
135
|
+
# ...see .env.example for the full list
|
|
136
|
+
volumes:
|
|
137
|
+
- uploads_data:/app/uploads
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Put it behind whatever reverse proxy/tunnel the consuming project already
|
|
141
|
+
uses to route a subdomain (e.g. `admin.example.com`) to it.
|
|
142
|
+
|
|
143
|
+
## Environment variables
|
|
144
|
+
|
|
145
|
+
See [`.env.example`](.env.example) for the full list (`DB_TYPE`, `JWT_SECRET_KEY`,
|
|
146
|
+
`CORS_ORIGINS`, `CMS_PUBLIC_URL`, etc.).
|
|
147
|
+
|
|
148
|
+
## Backup / restore CLI
|
|
149
|
+
|
|
150
|
+
`cms_backend/scripts/cms_schema.py` (installed as the `dashtro` console script)
|
|
151
|
+
exports/imports schemas, documents, and media to/from a `backup/` directory.
|
|
152
|
+
|
|
153
|
+
### Local (direct database access)
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Export
|
|
157
|
+
dashtro export schema --project-id <id>
|
|
158
|
+
dashtro export documents --project-id <id> --workspace <name>
|
|
159
|
+
dashtro export media
|
|
160
|
+
|
|
161
|
+
# Import
|
|
162
|
+
dashtro import schema --project-id <id>
|
|
163
|
+
dashtro import documents --project-id <id> --workspace <name>
|
|
164
|
+
dashtro import media
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Run against a running container:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
docker exec <container> dashtro export schema --project-id <id> --backup-dir /app/backup
|
|
171
|
+
docker exec <container> dashtro import schema --project-id <id> --backup-dir /app/backup
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Remote (HTTP API with authentication)
|
|
175
|
+
|
|
176
|
+
Use `--base-url` to export/import from/to a remote Dashtro instance. Requires an API key generated in the CMS settings.
|
|
177
|
+
|
|
178
|
+
**Via command line:**
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Export from remote
|
|
182
|
+
dashtro export schema --project-id <id> --base-url https://your-cms.com --api-key <api-key>
|
|
183
|
+
dashtro export documents --project-id <id> --workspace <name> --base-url https://your-cms.com --api-key <api-key>
|
|
184
|
+
dashtro export media --base-url https://your-cms.com --api-key <api-key>
|
|
185
|
+
|
|
186
|
+
# Import to remote
|
|
187
|
+
dashtro import schema --project-id <id> --base-url https://your-cms.com --api-key <api-key>
|
|
188
|
+
dashtro import documents --project-id <id> --workspace <name> --base-url https://your-cms.com --api-key <api-key>
|
|
189
|
+
dashtro import media --base-url https://your-cms.com --api-key <api-key>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Via environment variable (recommended):**
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
export CMS_API_KEY=<api-key>
|
|
196
|
+
dashtro export schema --project-id <id> --base-url https://your-cms.com
|
|
197
|
+
dashtro import documents --project-id <id> --workspace <name> --base-url https://your-cms.com
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Full backup/restore workflow
|
|
201
|
+
|
|
202
|
+
Export must run in order: schema → documents → media. Restore uses the same order:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Backup from source instance
|
|
206
|
+
dashtro export schema --project-id abc123 --base-url https://source.com --api-key <key>
|
|
207
|
+
dashtro export documents --project-id abc123 --workspace production --base-url https://source.com --api-key <key>
|
|
208
|
+
dashtro export media --base-url https://source.com --api-key <key>
|
|
209
|
+
|
|
210
|
+
# Restore to destination instance
|
|
211
|
+
dashtro import schema --project-id abc123 --base-url https://dest.com --api-key <key>
|
|
212
|
+
dashtro import documents --project-id abc123 --workspace production --base-url https://dest.com --api-key <key>
|
|
213
|
+
dashtro import media --base-url https://dest.com --api-key <key>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Options:**
|
|
217
|
+
|
|
218
|
+
| Flag | Meaning |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `--backup-dir` | Backup directory location (default: `./backup/`) |
|
|
221
|
+
| `--base-url` | Remote API URL (if omitted, uses direct database access) |
|
|
222
|
+
| `--api-key` | API key for authentication (or set `CMS_API_KEY` env var) |
|
|
223
|
+
| `--merge` | Merge documents instead of replacing (local mode only) |
|
|
224
|
+
|
|
225
|
+
## CI/CD
|
|
226
|
+
|
|
227
|
+
[`.github/workflows/build-image.yml`](.github/workflows/build-image.yml) runs
|
|
228
|
+
on every push to `main` (or manually via `workflow_dispatch`):
|
|
229
|
+
|
|
230
|
+
1. **`lint`** — frontend `npm run lint` + backend `isort`/`black`/`ruff --check`. Must pass before anything builds.
|
|
231
|
+
2. **`build-and-push`** — builds `Dockerfile.dashtro`, pushes to `ghcr.io/1atharvad/dashtro` tagged `latest` and the commit SHA.
|
|
232
|
+
|
|
233
|
+
That's it — this repo doesn't deploy anywhere itself. Whatever consumes the
|
|
234
|
+
image (e.g. the portfolio project) is responsible for pulling and running it.
|
|
235
|
+
|
|
236
|
+
## Tests
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
cd cms_backend && pytest # backend, SQLite by default
|
|
240
|
+
TEST_DB_TYPE=postgres pytest # backend, against a reachable Postgres
|
|
241
|
+
|
|
242
|
+
cd cms-frontend && npm test # frontend (vitest)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
[MIT](LICENSE)
|
dashtro-0.1.0/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Dashtro
|
|
2
|
+
|
|
3
|
+
[](https://github.com/1atharvad/dashtro/actions/workflows/build-image.yml)
|
|
4
|
+
[](https://github.com/1atharvad/dashtro/pkgs/container/dashtro)
|
|
5
|
+
[](https://www.npmjs.com/package/@dashtro/client)
|
|
6
|
+
[](https://www.npmjs.com/package/@dashtro/mcp)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
A self-hosted CMS with a project → workspace → collection → document model, a
|
|
10
|
+
FastAPI backend, and a React/TypeScript frontend. Ships as a single Docker
|
|
11
|
+
image, published to `ghcr.io/1atharvad/dashtro`. This repo only builds and
|
|
12
|
+
publishes that image — running it in production (nginx, tunnel/domain
|
|
13
|
+
routing, etc.) is owned by the consuming project (e.g. the portfolio site
|
|
14
|
+
that embeds Dashtro as its admin/CMS backend).
|
|
15
|
+
|
|
16
|
+
## Contents
|
|
17
|
+
|
|
18
|
+
- [Structure](#structure)
|
|
19
|
+
- [Packages](#packages)
|
|
20
|
+
- [Architecture](#architecture)
|
|
21
|
+
- [Data backends](#data-backends)
|
|
22
|
+
- [Running locally (dev)](#running-locally-dev)
|
|
23
|
+
- [Running the image elsewhere](#running-the-image-elsewhere)
|
|
24
|
+
- [Environment variables](#environment-variables)
|
|
25
|
+
- [Backup / restore CLI](#backup--restore-cli)
|
|
26
|
+
- [CI/CD](#cicd)
|
|
27
|
+
- [Tests](#tests)
|
|
28
|
+
|
|
29
|
+
## Structure
|
|
30
|
+
|
|
31
|
+
| Path | What it is |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| [`cms_backend/`](cms_backend/) | FastAPI backend — API, auth, schema engine, data clients. See its [README](cms_backend/README.md). |
|
|
34
|
+
| [`cms-frontend/`](cms-frontend/) | React + TypeScript + Vite frontend. See its [README](cms-frontend/README.md). |
|
|
35
|
+
| [`cms_mcp/`](cms_mcp/) | MCP server exposing Dashtro operations to MCP-compatible clients (Python, console script `dashtro-mcp`, direct database access). |
|
|
36
|
+
| [`sdk/js/`](sdk/js/) | `@dashtro/client` — JS/TS client SDK for `/api/sdk/*`. |
|
|
37
|
+
| [`sdk/python/`](sdk/python/) | `dashtro-client` — Python client SDK for `/api/sdk/*`, released in lockstep with `sdk/js/`. |
|
|
38
|
+
| [`sdk/mcp/`](sdk/mcp/) | `@dashtro/mcp` — npx-runnable MCP server (Node/TS port of `cms_mcp/`) for consuming projects; talks to `/api/cms/*` over HTTP, no Python required. |
|
|
39
|
+
| [`nginx/`](nginx/) | Reverse proxy config for local dev only. |
|
|
40
|
+
|
|
41
|
+
## Packages
|
|
42
|
+
|
|
43
|
+
Everything this repo publishes, and what it's for:
|
|
44
|
+
|
|
45
|
+
| Package | Registry | Install | What it's for |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `ghcr.io/1atharvad/dashtro` | [GHCR](https://github.com/1atharvad/dashtro/pkgs/container/dashtro) | `docker pull ghcr.io/1atharvad/dashtro` | The CMS itself — backend + built frontend in one image. See [Running the image elsewhere](#running-the-image-elsewhere). |
|
|
48
|
+
| [`@dashtro/client`](sdk/js/) | [npm](https://www.npmjs.com/package/@dashtro/client) | `npm install @dashtro/client` | JS/TS client SDK for `/api/sdk/*` — read/write a project's documents and RTDB from an external app. |
|
|
49
|
+
| [`dashtro-client`](sdk/python/) | [PyPI](https://pypi.org/project/dashtro-client/) | `pip install dashtro-client` | Python equivalent of `@dashtro/client`, released in lockstep with it. |
|
|
50
|
+
| [`@dashtro/mcp`](sdk/mcp/) | [npm](https://www.npmjs.com/package/@dashtro/mcp) | `npx @dashtro/mcp init` | npx-runnable MCP server — lets Claude/other MCP clients read and write your CMS content. Node port of `cms_mcp/`, no Python needed. |
|
|
51
|
+
| `dashtro` | not published | n/a — run from a repo checkout | Root package: the `dashtro` backup/restore CLI (`cms_backend/scripts/cms_schema.py`, see [Backup / restore CLI](#backup--restore-cli)). Also bundles `cms_mcp`/`dashtro-mcp`, but use `@dashtro/mcp` instead for MCP access — this package isn't on PyPI. |
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
```mermaid
|
|
56
|
+
flowchart LR
|
|
57
|
+
Browser["Browser SPA\n(cms-frontend)"] -->|"/api/*"| Backend["FastAPI backend\n(cms_backend)"]
|
|
58
|
+
Backend --> DB[("sqlite / postgres")]
|
|
59
|
+
ExternalApp["External app"] -->|"/api/sdk/*"| Backend
|
|
60
|
+
ExternalApp -.->|uses| ClientSDK["@dashtro/client\ndashtro-client"]
|
|
61
|
+
MCPClient["MCP client\n(Claude, etc.)"] -->|stdio, npx| MCPNode["@dashtro/mcp\n(Node)"]
|
|
62
|
+
MCPNode -->|"/api/cms/*"| Backend
|
|
63
|
+
MCPClient -.->|stdio, local install| MCPPy["dashtro-mcp\n(cms_mcp, Python)"]
|
|
64
|
+
MCPPy -->|"/api/cms/*"| Backend
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Everything ultimately talks to the same FastAPI backend — the frontend over
|
|
68
|
+
its own API, external apps over the API-key-scoped `/api/sdk/*` surface (via
|
|
69
|
+
either client SDK), and MCP clients over `/api/cms/*` through either MCP
|
|
70
|
+
server (`@dashtro/mcp` for a zero-install `npx` setup, or `cms_mcp`/`dashtro-mcp`
|
|
71
|
+
if you're already in a Python environment with direct database access).
|
|
72
|
+
|
|
73
|
+
## Data backends
|
|
74
|
+
|
|
75
|
+
`DB_TYPE` selects the storage backend, defaulting to `sqlite`:
|
|
76
|
+
|
|
77
|
+
- `sqlite` — single-file DB, zero external dependencies. Default.
|
|
78
|
+
- `postgres` — set `DB_HOST`/`DB_PORT`/`DB_NAME`/`DB_USER`/`DB_PASSWORD`. An optional
|
|
79
|
+
bundled `postgres` compose service is available (see below) if you don't want to
|
|
80
|
+
point at an external instance.
|
|
81
|
+
|
|
82
|
+
Both backends implement the same interface (`get_data_client()` / `get_auth_client()` /
|
|
83
|
+
`get_audit_client()` in `cms_backend/api/utils/__init__.py`), so routers don't change
|
|
84
|
+
depending on which one is active.
|
|
85
|
+
|
|
86
|
+
## Running locally (dev)
|
|
87
|
+
|
|
88
|
+
Hot-reloading, separate frontend/backend containers, Cloudflare tunnel support:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cp .env.example .env # fill in real values
|
|
92
|
+
npm run dev # docker compose -f docker-compose.dev.yml up --build
|
|
93
|
+
npm run dev:down
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Optional local Postgres instead of an external one:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
docker compose -f docker-compose.dev.yml --profile postgres up
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Running the image elsewhere
|
|
103
|
+
|
|
104
|
+
The published image serves both the API and the built SPA on port 7312, and
|
|
105
|
+
reads its config from env vars (see below) — no other dependency beyond
|
|
106
|
+
whichever `DB_TYPE` backend you point it at:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
dashtro:
|
|
110
|
+
image: ghcr.io/1atharvad/dashtro:latest
|
|
111
|
+
environment:
|
|
112
|
+
JWT_SECRET_KEY: ...
|
|
113
|
+
CORS_ORIGINS: ...
|
|
114
|
+
CMS_PUBLIC_URL: ...
|
|
115
|
+
DB_TYPE: sqlite
|
|
116
|
+
# ...see .env.example for the full list
|
|
117
|
+
volumes:
|
|
118
|
+
- uploads_data:/app/uploads
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Put it behind whatever reverse proxy/tunnel the consuming project already
|
|
122
|
+
uses to route a subdomain (e.g. `admin.example.com`) to it.
|
|
123
|
+
|
|
124
|
+
## Environment variables
|
|
125
|
+
|
|
126
|
+
See [`.env.example`](.env.example) for the full list (`DB_TYPE`, `JWT_SECRET_KEY`,
|
|
127
|
+
`CORS_ORIGINS`, `CMS_PUBLIC_URL`, etc.).
|
|
128
|
+
|
|
129
|
+
## Backup / restore CLI
|
|
130
|
+
|
|
131
|
+
`cms_backend/scripts/cms_schema.py` (installed as the `dashtro` console script)
|
|
132
|
+
exports/imports schemas, documents, and media to/from a `backup/` directory.
|
|
133
|
+
|
|
134
|
+
### Local (direct database access)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Export
|
|
138
|
+
dashtro export schema --project-id <id>
|
|
139
|
+
dashtro export documents --project-id <id> --workspace <name>
|
|
140
|
+
dashtro export media
|
|
141
|
+
|
|
142
|
+
# Import
|
|
143
|
+
dashtro import schema --project-id <id>
|
|
144
|
+
dashtro import documents --project-id <id> --workspace <name>
|
|
145
|
+
dashtro import media
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Run against a running container:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
docker exec <container> dashtro export schema --project-id <id> --backup-dir /app/backup
|
|
152
|
+
docker exec <container> dashtro import schema --project-id <id> --backup-dir /app/backup
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Remote (HTTP API with authentication)
|
|
156
|
+
|
|
157
|
+
Use `--base-url` to export/import from/to a remote Dashtro instance. Requires an API key generated in the CMS settings.
|
|
158
|
+
|
|
159
|
+
**Via command line:**
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Export from remote
|
|
163
|
+
dashtro export schema --project-id <id> --base-url https://your-cms.com --api-key <api-key>
|
|
164
|
+
dashtro export documents --project-id <id> --workspace <name> --base-url https://your-cms.com --api-key <api-key>
|
|
165
|
+
dashtro export media --base-url https://your-cms.com --api-key <api-key>
|
|
166
|
+
|
|
167
|
+
# Import to remote
|
|
168
|
+
dashtro import schema --project-id <id> --base-url https://your-cms.com --api-key <api-key>
|
|
169
|
+
dashtro import documents --project-id <id> --workspace <name> --base-url https://your-cms.com --api-key <api-key>
|
|
170
|
+
dashtro import media --base-url https://your-cms.com --api-key <api-key>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Via environment variable (recommended):**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
export CMS_API_KEY=<api-key>
|
|
177
|
+
dashtro export schema --project-id <id> --base-url https://your-cms.com
|
|
178
|
+
dashtro import documents --project-id <id> --workspace <name> --base-url https://your-cms.com
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Full backup/restore workflow
|
|
182
|
+
|
|
183
|
+
Export must run in order: schema → documents → media. Restore uses the same order:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Backup from source instance
|
|
187
|
+
dashtro export schema --project-id abc123 --base-url https://source.com --api-key <key>
|
|
188
|
+
dashtro export documents --project-id abc123 --workspace production --base-url https://source.com --api-key <key>
|
|
189
|
+
dashtro export media --base-url https://source.com --api-key <key>
|
|
190
|
+
|
|
191
|
+
# Restore to destination instance
|
|
192
|
+
dashtro import schema --project-id abc123 --base-url https://dest.com --api-key <key>
|
|
193
|
+
dashtro import documents --project-id abc123 --workspace production --base-url https://dest.com --api-key <key>
|
|
194
|
+
dashtro import media --base-url https://dest.com --api-key <key>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Options:**
|
|
198
|
+
|
|
199
|
+
| Flag | Meaning |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `--backup-dir` | Backup directory location (default: `./backup/`) |
|
|
202
|
+
| `--base-url` | Remote API URL (if omitted, uses direct database access) |
|
|
203
|
+
| `--api-key` | API key for authentication (or set `CMS_API_KEY` env var) |
|
|
204
|
+
| `--merge` | Merge documents instead of replacing (local mode only) |
|
|
205
|
+
|
|
206
|
+
## CI/CD
|
|
207
|
+
|
|
208
|
+
[`.github/workflows/build-image.yml`](.github/workflows/build-image.yml) runs
|
|
209
|
+
on every push to `main` (or manually via `workflow_dispatch`):
|
|
210
|
+
|
|
211
|
+
1. **`lint`** — frontend `npm run lint` + backend `isort`/`black`/`ruff --check`. Must pass before anything builds.
|
|
212
|
+
2. **`build-and-push`** — builds `Dockerfile.dashtro`, pushes to `ghcr.io/1atharvad/dashtro` tagged `latest` and the commit SHA.
|
|
213
|
+
|
|
214
|
+
That's it — this repo doesn't deploy anywhere itself. Whatever consumes the
|
|
215
|
+
image (e.g. the portfolio project) is responsible for pulling and running it.
|
|
216
|
+
|
|
217
|
+
## Tests
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
cd cms_backend && pytest # backend, SQLite by default
|
|
221
|
+
TEST_DB_TYPE=postgres pytest # backend, against a reachable Postgres
|
|
222
|
+
|
|
223
|
+
cd cms-frontend && npm test # frontend (vitest)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
[MIT](LICENSE)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from decouple import config
|
|
2
|
+
|
|
3
|
+
from .audit_client import SqliteAuditClient
|
|
4
|
+
from .postgres_audit_client import PostgresAuditClient
|
|
5
|
+
from .postgres_client import PostgresAuth, PostgresData
|
|
6
|
+
from .sqlite_client import SqliteAuth, SqliteData
|
|
7
|
+
|
|
8
|
+
# mongodb is not yet migrated to the FastAPI data API, so it stays unsupported.
|
|
9
|
+
_DB_TYPE = config("DB_TYPE", default="sqlite")
|
|
10
|
+
_SUPPORTED_DB_TYPES = ("sqlite", "postgres")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_data_client():
|
|
14
|
+
if _DB_TYPE == "postgres":
|
|
15
|
+
return PostgresData()
|
|
16
|
+
if _DB_TYPE == "sqlite":
|
|
17
|
+
return SqliteData()
|
|
18
|
+
raise NotImplementedError(
|
|
19
|
+
f"DB_TYPE={_DB_TYPE!r} is not supported yet; use one of {_SUPPORTED_DB_TYPES}"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_auth_client():
|
|
24
|
+
if _DB_TYPE == "postgres":
|
|
25
|
+
return PostgresAuth()
|
|
26
|
+
if _DB_TYPE == "sqlite":
|
|
27
|
+
return SqliteAuth()
|
|
28
|
+
raise NotImplementedError(
|
|
29
|
+
f"DB_TYPE={_DB_TYPE!r} is not supported yet; use one of {_SUPPORTED_DB_TYPES}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_audit_client() -> SqliteAuditClient | PostgresAuditClient:
|
|
34
|
+
if _DB_TYPE == "postgres":
|
|
35
|
+
return PostgresAuditClient()
|
|
36
|
+
return SqliteAuditClient()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"SqliteData",
|
|
41
|
+
"SqliteAuth",
|
|
42
|
+
"SqliteAuditClient",
|
|
43
|
+
"PostgresData",
|
|
44
|
+
"PostgresAuth",
|
|
45
|
+
"PostgresAuditClient",
|
|
46
|
+
"get_data_client",
|
|
47
|
+
"get_auth_client",
|
|
48
|
+
"get_audit_client",
|
|
49
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
def get_actor(request) -> dict:
|
|
2
|
+
"""Return the actor verified by CMSAuthMiddleware for this request."""
|
|
3
|
+
return request.state.actor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_client_ip(request) -> str:
|
|
7
|
+
"""Extract the real client IP from a FastAPI Request."""
|
|
8
|
+
forwarded_for = request.headers.get("x-forwarded-for")
|
|
9
|
+
if forwarded_for:
|
|
10
|
+
return forwarded_for.split(",")[0].strip()
|
|
11
|
+
return request.client.host if request.client else ""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from fastapi import Header, HTTPException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def require_api_key(operation: str):
|
|
5
|
+
"""Dependency factory: verifies X-API-Key and that it carries `operation` ('read'/'write')."""
|
|
6
|
+
|
|
7
|
+
def _dependency(x_api_key: str = Header(default=None)) -> dict:
|
|
8
|
+
if not x_api_key:
|
|
9
|
+
raise HTTPException(status_code=401, detail="X-API-Key header missing")
|
|
10
|
+
from api.utils import get_auth_client
|
|
11
|
+
|
|
12
|
+
db_auth = get_auth_client()
|
|
13
|
+
key_info = db_auth.verify_api_key(x_api_key)
|
|
14
|
+
if not key_info:
|
|
15
|
+
raise HTTPException(status_code=401, detail="Invalid or revoked API key")
|
|
16
|
+
if operation not in (key_info.get("scopes") or []):
|
|
17
|
+
raise HTTPException(
|
|
18
|
+
status_code=403, detail=f"API key does not have '{operation}' access"
|
|
19
|
+
)
|
|
20
|
+
return key_info
|
|
21
|
+
|
|
22
|
+
return _dependency
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_key_scope(key_info: dict, project_id: str, collection_name: str):
|
|
26
|
+
"""Raises 403 if the key is scoped to a different project or collection set."""
|
|
27
|
+
if key_info.get("project_id") and key_info["project_id"] != project_id:
|
|
28
|
+
raise HTTPException(status_code=403, detail="API key is not scoped to this project")
|
|
29
|
+
collections = key_info.get("collections") or []
|
|
30
|
+
if collections and collection_name not in collections:
|
|
31
|
+
raise HTTPException(status_code=403, detail="API key is not scoped to this collection")
|