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.
Files changed (36) hide show
  1. dashtro-0.1.0/LICENSE +21 -0
  2. dashtro-0.1.0/PKG-INFO +247 -0
  3. dashtro-0.1.0/README.md +228 -0
  4. dashtro-0.1.0/cms_backend/__init__.py +0 -0
  5. dashtro-0.1.0/cms_backend/api/__init__.py +0 -0
  6. dashtro-0.1.0/cms_backend/api/utils/__init__.py +49 -0
  7. dashtro-0.1.0/cms_backend/api/utils/actor.py +11 -0
  8. dashtro-0.1.0/cms_backend/api/utils/api_key_auth.py +31 -0
  9. dashtro-0.1.0/cms_backend/api/utils/audit_client.py +237 -0
  10. dashtro-0.1.0/cms_backend/api/utils/mongodb_client.py +183 -0
  11. dashtro-0.1.0/cms_backend/api/utils/postgres_audit_client.py +207 -0
  12. dashtro-0.1.0/cms_backend/api/utils/postgres_client.py +1258 -0
  13. dashtro-0.1.0/cms_backend/api/utils/schema.py +69 -0
  14. dashtro-0.1.0/cms_backend/api/utils/sqlite_client.py +1236 -0
  15. dashtro-0.1.0/cms_backend/api/utils/workspace_diff.py +44 -0
  16. dashtro-0.1.0/cms_backend/config.py +13 -0
  17. dashtro-0.1.0/cms_backend/main.py +78 -0
  18. dashtro-0.1.0/cms_backend/models/__init__.py +0 -0
  19. dashtro-0.1.0/cms_backend/models/collection.py +48 -0
  20. dashtro-0.1.0/cms_backend/models/field_types.py +75 -0
  21. dashtro-0.1.0/cms_backend/models/project.py +23 -0
  22. dashtro-0.1.0/cms_backend/models/rich_text_component.py +22 -0
  23. dashtro-0.1.0/cms_backend/models/schema.py +213 -0
  24. dashtro-0.1.0/cms_backend/scripts/__init__.py +0 -0
  25. dashtro-0.1.0/cms_backend/scripts/cms_schema.py +1036 -0
  26. dashtro-0.1.0/cms_backend/scripts/migrate_image_keys.py +54 -0
  27. dashtro-0.1.0/cms_mcp/__init__.py +0 -0
  28. dashtro-0.1.0/cms_mcp/server.py +316 -0
  29. dashtro-0.1.0/dashtro.egg-info/PKG-INFO +247 -0
  30. dashtro-0.1.0/dashtro.egg-info/SOURCES.txt +34 -0
  31. dashtro-0.1.0/dashtro.egg-info/dependency_links.txt +1 -0
  32. dashtro-0.1.0/dashtro.egg-info/entry_points.txt +3 -0
  33. dashtro-0.1.0/dashtro.egg-info/requires.txt +6 -0
  34. dashtro-0.1.0/dashtro.egg-info/top_level.txt +2 -0
  35. dashtro-0.1.0/pyproject.toml +68 -0
  36. 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
+ [![CI](https://github.com/1atharvad/dashtro/actions/workflows/build-image.yml/badge.svg)](https://github.com/1atharvad/dashtro/actions/workflows/build-image.yml)
23
+ [![Docker image](https://img.shields.io/badge/ghcr.io-1atharvad%2Fdashtro-2496ED?logo=docker&logoColor=white)](https://github.com/1atharvad/dashtro/pkgs/container/dashtro)
24
+ [![npm @dashtro/client](https://img.shields.io/npm/v/%40dashtro%2Fclient?label=%40dashtro%2Fclient)](https://www.npmjs.com/package/@dashtro/client)
25
+ [![npm @dashtro/mcp](https://img.shields.io/npm/v/%40dashtro%2Fmcp?label=%40dashtro%2Fmcp)](https://www.npmjs.com/package/@dashtro/mcp)
26
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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)
@@ -0,0 +1,228 @@
1
+ # Dashtro
2
+
3
+ [![CI](https://github.com/1atharvad/dashtro/actions/workflows/build-image.yml/badge.svg)](https://github.com/1atharvad/dashtro/actions/workflows/build-image.yml)
4
+ [![Docker image](https://img.shields.io/badge/ghcr.io-1atharvad%2Fdashtro-2496ED?logo=docker&logoColor=white)](https://github.com/1atharvad/dashtro/pkgs/container/dashtro)
5
+ [![npm @dashtro/client](https://img.shields.io/npm/v/%40dashtro%2Fclient?label=%40dashtro%2Fclient)](https://www.npmjs.com/package/@dashtro/client)
6
+ [![npm @dashtro/mcp](https://img.shields.io/npm/v/%40dashtro%2Fmcp?label=%40dashtro%2Fmcp)](https://www.npmjs.com/package/@dashtro/mcp)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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")