Rename project from KeepItTalking to DS Chat

Renames the app's display name everywhere (page titles, PWA manifest,
TopBar, email subject lines, HMAC signature header) and its internal
technical slug from chatapp to ds-chat/ds_chat: the Python package name
and console script, the systemd unit and its user/group/paths, the deploy
scripts, the Docker container names, and the Postgres database name.

The live dev Postgres role stays "chatapp" -- renaming a role requires
disconnecting the session using it, which needed a temporary superuser
role Claude's auto-mode classifier correctly declined to create
unsupervised. Functionally invisible (it's just a login credential), but
worth knowing about if this ever needs fully cleaning up by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 21:11:41 -06:00
co-authored by Claude Sonnet 5
parent 62e4760c8a
commit 14dc340174
29 changed files with 145 additions and 145 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
DATABASE_URL=postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp
DATABASE_URL=postgresql+asyncpg://ds_chat:ds_chat@localhost:5432/ds_chat
SESSION_SECRET=change-me-to-a-long-random-string
SESSION_HTTPS_ONLY=false
REDIS_URL=redis://localhost:6379/0
+8 -8
View File
@@ -1,4 +1,4 @@
# KeepItTalking backend (Phase 1 + 2 + 4 + 5 + 6 + 7 + 8, image uploads, file attachments, admin-configurable upload size limits, emoji & reactions, user profiles, site invites & email, password reset)
# DS Chat backend (Phase 1 + 2 + 4 + 5 + 6 + 7 + 8, image uploads, file attachments, admin-configurable upload size limits, emoji & reactions, user profiles, site invites & email, password reset)
FastAPI + SQLAlchemy 2.0 (async) + PostgreSQL + Redis. Implements auth, room
CRUD (open and private), room roles (owner/admin/member) and direct
@@ -24,15 +24,15 @@ Accounts are created by an operator on the app server — see step 4 below.
Any local Postgres 14+ works. The quickest option is a container:
```bash
docker run -d --name chatapp-postgres \
-e POSTGRES_USER=chatapp -e POSTGRES_PASSWORD=chatapp -e POSTGRES_DB=chatapp \
docker run -d --name ds-chat-postgres \
-e POSTGRES_USER=ds_chat -e POSTGRES_PASSWORD=ds_chat -e POSTGRES_DB=ds_chat \
-p 5432:5432 postgres:16-alpine
```
Then create the test database (used by the test suite, kept separate from dev data):
```bash
docker exec chatapp-postgres psql -U chatapp -d chatapp -c "CREATE DATABASE chatapp_test;"
docker exec ds-chat-postgres psql -U ds_chat -d ds_chat -c "CREATE DATABASE ds_chat_test;"
```
(Docker here is purely a local-dev convenience for standing up Postgres quickly —
@@ -44,7 +44,7 @@ Used for cross-instance WebSocket fan-out and presence (see the section
below). Required — there's no in-memory fallback.
```bash
docker run -d --name chatapp-redis -p 6379:6379 redis:7-alpine
docker run -d --name ds-chat-redis -p 6379:6379 redis:7-alpine
```
### 3. Python environment
@@ -99,13 +99,13 @@ connected to the other, purely via Redis.
### 8. Run tests
Tests run against a real Postgres database (`chatapp_test` by default — native
Tests run against a real Postgres database (`ds_chat_test` by default — native
`ENUM`/`UUID` types aren't faithfully reproduced by SQLite) and a real Redis
(db 15 by default, kept separate from dev use of db 0), with each test
wrapped in a transaction that's rolled back afterward:
```bash
DATABASE_URL=postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp_test .venv/bin/pytest
DATABASE_URL=postgresql+asyncpg://ds_chat:ds_chat@localhost:5432/ds_chat_test .venv/bin/pytest
```
## Layout
@@ -247,7 +247,7 @@ call sites rather than duplicated).
**Outgoing webhooks / event subscriptions** (`POST
/api/rooms/{id}/event-subscriptions`, room-admin managed; room-scoped or
global via `room_id=null`): fires an HMAC-SHA256-signed POST
(`X-KeepItTalking-Signature: sha256=...`) on `message.created`/
(`X-DS-Chat-Signature: sha256=...`) on `message.created`/
`message.updated`, delivered via a backgrounded `asyncio.create_task`
(`app/services/webhook_delivery.py`) — safe to background here, unlike the
Phase 4 push lesson, since there's no DB session involved, just the
+1 -1
View File
@@ -86,7 +86,7 @@ path_separator = os
# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp
sqlalchemy.url = postgresql+asyncpg://chatapp:chatapp@localhost:5432/ds_chat
[post_write_hooks]
+3 -3
View File
@@ -18,8 +18,8 @@ from app.ws.connection_manager import ConnectionManager
from app.ws.presence import Presence
# backend/app/main.py -> backend/ -> repo root -- matches both the local
# monorepo layout and the production layout (/srv/chatapp/backend,
# /srv/chatapp/frontend/dist), which is the same relative shape.
# monorepo layout and the production layout (/srv/ds-chat/backend,
# /srv/ds-chat/frontend/dist), which is the same relative shape.
FRONTEND_DIST = pathlib.Path(__file__).resolve().parent.parent.parent / "frontend" / "dist"
@@ -55,7 +55,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
def create_app() -> FastAPI:
app = FastAPI(title="KeepItTalking", lifespan=lifespan)
app = FastAPI(title="DS Chat", lifespan=lifespan)
app.add_middleware(
SessionMiddleware,
+2 -2
View File
@@ -58,6 +58,6 @@ async def send_test_email(db: AsyncSession, to_address: str) -> None:
await _deliver(
cfg,
to_address,
"KeepItTalking test email",
"This is a test email from KeepItTalking to confirm your SMTP settings are working.",
"DS Chat test email",
"This is a test email from DS Chat to confirm your SMTP settings are working.",
)
+1 -1
View File
@@ -44,7 +44,7 @@ async def request_password_reset(db: AsyncSession, email: str, base_url: str) ->
await send_email(
db,
email,
"Reset your KeepItTalking password",
"Reset your DS Chat password",
f"Someone requested a password reset for this account.\n\n"
f"Reset it here:\n{reset_link}\n\n"
f"This link expires in 15 minutes. If you didn't request this, "
+1 -1
View File
@@ -140,7 +140,7 @@ async def add_member(
db,
target.email,
f"You've been added to #{room.name}",
f"You've been added to the #{room.name} room on KeepItTalking.\n\n"
f"You've been added to the #{room.name} room on DS Chat.\n\n"
f"Open the app: {base_url.rstrip('/')}",
)
+2 -2
View File
@@ -43,8 +43,8 @@ async def create_site_invite(
await send_email(
db,
email,
"You're invited to join KeepItTalking",
f"You've been invited to join KeepItTalking by {actor.username}.\n\n"
"You're invited to join DS Chat",
f"You've been invited to join DS Chat by {actor.username}.\n\n"
f"Set up your account here:\n{signup_link}\n\n"
f"This link expires in 7 days.",
)
+1 -1
View File
@@ -32,7 +32,7 @@ async def deliver_event(subscription: EventSubscription, event_type: str, payloa
content=body,
headers={
"Content-Type": "application/json",
"X-KeepItTalking-Signature": f"sha256={signature}",
"X-DS-Chat-Signature": f"sha256={signature}",
},
)
except httpx.HTTPError:
+1 -1
View File
@@ -6,7 +6,7 @@ from PIL import Image, UnidentifiedImageError
# backend/app/storage.py -> backend/ -> repo root -- same
# resolve-relative-to-file convention FRONTEND_DIST uses in app/main.py, so
# this lands in the right place in both local dev and the /srv/chatapp
# this lands in the right place in both local dev and the /srv/ds-chat
# production layout with zero new config.
UPLOADS_DIR = pathlib.Path(__file__).resolve().parent.parent.parent / "uploads"
+3 -3
View File
@@ -1,7 +1,7 @@
[project]
name = "chatapp"
name = "ds-chat"
version = "0.1.0"
description = "KeepItTalking chat service backend"
description = "DS Chat backend service"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115",
@@ -25,7 +25,7 @@ dependencies = [
]
[project.scripts]
chatapp-create-user = "app.cli:main"
ds-chat-create-user = "app.cli:main"
[project.optional-dependencies]
dev = [
+1 -1
View File
@@ -3,7 +3,7 @@ import os
from pathlib import Path
os.environ.setdefault(
"DATABASE_URL", "postgresql+asyncpg://chatapp:chatapp@localhost:5432/chatapp_test"
"DATABASE_URL", "postgresql+asyncpg://chatapp:chatapp@localhost:5432/ds_chat_test"
)
os.environ.setdefault("SESSION_SECRET", "test-secret")
os.environ.setdefault("SESSION_HTTPS_ONLY", "false")
+1 -1
View File
@@ -31,7 +31,7 @@ async def test_subscribe_creates_row(client, db_session):
resp = await client.post("/api/push/subscribe", json=payload)
assert resp.status_code == 204
# The chatapp_test database is shared across the whole suite and the
# The ds_chat_test database is shared across the whole suite and the
# ws_client-based tests below intentionally don't roll back (see
# conftest.ws_client), so a unique endpoint keeps this test independent
# of leftover rows from those instead of asserting on the total count.
+1 -1
View File
@@ -158,7 +158,7 @@ async def test_outgoing_webhook_delivers_signed_payload(client, db_session, monk
assert len(posts) == 1
body = posts[0]["content"]
expected_signature = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
assert posts[0]["headers"]["X-KeepItTalking-Signature"] == f"sha256={expected_signature}"
assert posts[0]["headers"]["X-DS-Chat-Signature"] == f"sha256={expected_signature}"
payload = json.loads(body)
assert payload["event"] == "message.created"
assert payload["data"]["content"] == "ping"