Skip to content

Commit fd49c99

Browse files
committed
Merged branch feature/postgres into develop
1 parent d3d4f54 commit fd49c99

345 files changed

Lines changed: 17121 additions & 15558 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ scaffold with
6262
mise run add-migration "The name of the migration"
6363
```
6464

65-
This will generate a new file in src/core/migrations/versions, link it in the sequence of migrations,
66-
and you can then edit it there, based on the patterns established.
65+
This will generate a new file in src/core/migrations/sqlite/versions
66+
and in sqr/core/migrations/postgres/versions,
67+
link it in the sequence of migrations, and you can then edit it
68+
there, based on the patterns established.
6769

6870
### Lint, fix & check commands
6971

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# ADR 0008: WebAPI backend blend controlled via `Config.global`
2+
3+
## Status
4+
5+
Accepted (2026-05-03)
6+
7+
## Context
8+
9+
The WebAPI process wires several replaceable infrastructure pieces: where
10+
domain data lives, how telemetry is exported, which implementation backs search
11+
queries, and whether CRM side effects run against a real provider. Operators
12+
need a single, explicit place to choose that **blend** per deployment, without
13+
scattering ad hoc rules across `env`, hosting flags, and implicit defaults.
14+
15+
## Decision
16+
17+
1. **Control surface**
18+
Four environment variables live in `src/Config.global` (loaded with other
19+
universe-wide settings when building global configuration). Each variable
20+
selects one implementation for that concern. Together they define the
21+
**backend blend** for the WebAPI (and any other backend components that share
22+
the same top-level wiring and configuration load in that process).
23+
24+
2. **Global scope**
25+
The chosen values apply to **all** backend composition for that run: one
26+
blend per process, not per workspace, user, or request.
27+
28+
3. **Variables and allowed values**
29+
30+
| Variable | Allowed values | Meaning |
31+
<!-- markdownlint-disable MD013 -->
32+
| -------- | -------------- | ------- |
33+
| `WEBAPI_STORAGE_ENGINE` | `sqlite`, `postgres` | Primary domain persistence engine |
34+
| `WEBAPI_TELEMETRY` | `sentry`, `local` | Telemetry sink (e.g. Sentry vs local/no-op style) |
35+
| `WEBAPI_SEARCH` | `sql`, `algolia` | Search query / document storage engine for search |
36+
| `WEBAPI_CRM` | `wix`, `noop` | CRM integration vs no-op |
37+
<!-- markdownlint-enable MD013 -->
38+
39+
Values are lowercase string tokens. Invalid or missing values should be
40+
rejected at startup with a clear error once wiring reads these variables.
41+
42+
4. **Credentials and URLs**
43+
Provider-specific secrets and connection strings remain in service-level
44+
config (for example `src/webapi/Config.project` and related env). The
45+
`WEBAPI_*` flags only choose **which** implementation to construct; they do
46+
not replace DSNs, API keys, or database URLs.
47+
48+
## Conventions by universe
49+
50+
- **Thrive** (`UNIVERSE=thrive`): use the hosted-style blend—`postgres`,
51+
`sentry`, `algolia`, and `wix` for the four `WEBAPI_*` variables respectively.
52+
- **Other universes** (not thrive): use the local-first blend—`sqlite`, `local`,
53+
`sql`, and `noop`.
54+
- **Dev** (`UNIVERSE=dev`): no fixed convention; teams may set any valid
55+
combination while developing or testing.
56+
57+
## Consequences
58+
59+
- Operators set the four `WEBAPI_*` entries in `Config.global` (or equivalent
60+
injected env) for each deployment’s intended blend.
61+
- Composition code in the WebAPI entrypoint should branch on these settings (or
62+
on typed values derived from them) so behavior stays aligned with the declared
63+
configuration instead of re-deriving the same choices from unrelated signals.

infra/self-hosted/compose.yaml

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
name: jupiter
22

33
services:
4+
webapi-postgres:
5+
profiles:
6+
- storage-engine-postgres
7+
image: postgres:${POSTGRES_VERSION:-18}
8+
restart: always
9+
environment:
10+
POSTGRES_USER: ${JUPITER_DEV_POSTGRES_USER:-jupiter}
11+
POSTGRES_PASSWORD: ${JUPITER_DEV_POSTGRES_PASSWORD:-secret}
12+
POSTGRES_DB: ${JUPITER_DEV_POSTGRES_DB:-jupiter}
13+
volumes:
14+
# Postgres 18+ images expect one mount at /var/lib/postgresql (not .../data); see docker-library/postgres#1259.
15+
- postgres_data:/var/lib/postgresql
16+
healthcheck:
17+
# Must match POSTGRES_USER / POSTGRES_DB (from JUPITER_DEV_POSTGRES_*), not hardcoded jupiter.
18+
test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
19+
interval: 5s
20+
timeout: 5s
21+
retries: 10
22+
ports:
23+
# Host port for psql / GUI tools (Compose v2.20+): see WEBAPI_POSTGRES_PORT from srv.sh / mise run run:srv.
24+
# Use a non-zero value for a stable mapping; :0 assigns an ephemeral host port.
25+
- "${WEBAPI_POSTGRES_PORT:-0}:5432"
426
webapi:
527
environment:
6-
- SQLITE_DB_URL=sqlite+aiosqlite:////data/jupiter.sqlite
28+
- SQLITE_DB_URL=${SQLITE_DB_URL:-sqlite+aiosqlite:////data/jupiter.sqlite}
29+
- POSTGRES_DB_URL=${POSTGRES_DB_URL:-}
30+
- ALEMBIC_INI_PATH=${ALEMBIC_INI_PATH:-../core/migrations/alembic.sqlite.ini}
31+
- ALEMBIC_MIGRATIONS_PATH=${ALEMBIC_MIGRATIONS_PATH:-../core/migrations/sqlite}
732
- UNIVERSE=${UNIVERSE:?Check https://docs.get-thriving.com/how-tos/self-hosting}
833
- ENV=${ENV:-production}
934
- INSTANCE=${INSTANCE:?Check https://docs.get-thriving.com/how-tos/self-hosting}
1035
- PORT=2000
1136
- AUTH_TOKEN_SECRET=${AUTH_TOKEN_SECRET:?Check https://docs.get-thriving.com/how-tos/self-hosting}
37+
- WEBAPI_STORAGE_ENGINE=${WEBAPI_STORAGE_ENGINE:-sqlite}
38+
- WEBAPI_TELEMETRY=${WEBAPI_TELEMETRY:-local}
39+
- WEBAPI_SEARCH=${WEBAPI_SEARCH:-sql}
40+
- WEBAPI_CRM=${WEBAPI_CRM:-noop}
41+
- POSTGRES_VERSION=${POSTGRES_VERSION:-18}
42+
depends_on:
43+
webapi-postgres:
44+
condition: service_healthy
45+
required: false
1246
restart: always
1347
image: ${DOCKER_IMAGE_WEBAPI:-getthriving/webapi:${VERSION:-latest}}
1448
ports:
1549
- "${WEBAPI_PORT:-0}:2000"
1650
volumes:
17-
- db:/data
51+
- sqlite_data:/data
1852
healthcheck:
1953
test: ["CMD", "curl", "-f", "http://localhost:2000/healthz"]
2054
interval: 30s
@@ -95,6 +129,8 @@ services:
95129
- PUBLIC_NAME=${PUBLIC_NAME}
96130
restart: always
97131
image: ${DOCKER_IMAGE_DOCS:-getthriving/docs:${VERSION:-latest}}
132+
ports:
133+
- "${DOCS_PORT:-0}:8000"
98134
healthcheck:
99135
test: ["CMD", "curl", "-f", "http://localhost:8000/healthz"]
100136
interval: 30s
@@ -125,6 +161,8 @@ services:
125161
timeout: 10s
126162
retries: 5
127163
volumes:
128-
db:
129-
name: db-${INSTANCE:-dev}
164+
sqlite_data:
165+
name: sqlite_data-${INSTANCE:-dev}
166+
postgres_data:
167+
name: postgres_data-${INSTANCE:-dev}
130168

itests/api/big_plans.test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,10 @@ def test_api_big_plan_create_inbox_task_visible_in_inbox(
451451
},
452452
timeout=10,
453453
)
454+
455+
print(create_response.status_code)
456+
print(create_response.content)
457+
454458
assert create_response.status_code == 200
455459
created_ref_id = create_response.json()["new_inbox_task"]["ref_id"]
456460

itests/api/search.test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ def _search(
108108

109109

110110
def _ref_ids_from_search(data: Any) -> list[str]: # type: ignore
111-
return [m["summary"]["ref_id"] for m in data.get("matches", [])]
111+
# JSON numbers decode as int; API clients use str ref_ids — normalize for ``in`` checks.
112+
return [str(m["summary"]["ref_id"]) for m in data.get("matches", [])]
112113

113114

114115
def _todo_ref_ids_from_search(data: Any) -> list[str]: # type: ignore

itests/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"content": {"suites": {"0": {"status": {"total_pass": 0, "total_skip": 0, "total_xpass": 0, "total_xfail": 0, "total_rerun": 0, "total_fail": 0, "total_error": 3}, "tests": {"0": {"status": "ERROR", "message": "conftest.py:85: in api_url\n raise Exception(\"API_URL is not set\")\nE Exception: API_URL is not set\n", "test_name": "test_webui_time_plan_link_untracked_inbox_tasks[chromium]", "rerun": "0"}, "1": {"status": "ERROR", "message": "conftest.py:85: in api_url\n raise Exception(\"API_URL is not set\")\nE Exception: API_URL is not set\n", "test_name": "test_webui_time_plan_link_untracked_big_plans[chromium]", "rerun": "0"}, "2": {"status": "ERROR", "message": "conftest.py:85: in api_url\n raise Exception(\"API_URL is not set\")\nE Exception: API_URL is not set\n", "test_name": "test_webui_time_plan_show_activity_doneness[chromium]", "rerun": "0"}}, "suite_name": "itests/webui/entities/time_plans.test.py"}}}, "date": "May 09, 2026", "start_time": 1778353928.159845, "total_suite": 1, "status": "FAIL", "status_list": {"pass": "0", "fail": "0", "skip": "0", "error": "3", "xpass": "0", "xfail": "0", "rerun": "0"}, "total_tests": "3"}

0 commit comments

Comments
 (0)