diff --git a/README.md b/README.md index d2d762d..cf9188a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,62 @@ # DS Chat -A web-based team chat service (Mattermost-style, no threaded conversations), -invite-only. See [ARCHITECTURE.md](ARCHITECTURE.md) for the full system design -and phased build plan. +A self-hosted, real-time team chat service in the spirit of Slack/Discord/ +Mattermost (channel-based, no threaded conversations) — built from scratch +as a full-stack solo project, running in production on my own infrastructure +rather than as a demo. Invite-only: there's no public sign-up, every account +comes from an admin invite or a room invite. See +[ARCHITECTURE.md](ARCHITECTURE.md) for the full system design and phased +build plan. -**Phase 1**: auth, open-room CRUD, and single-instance WebSocket chat, backend -+ a minimal frontend. **Phase 2**: private rooms, room roles (owner/admin/ -member), and room invites — backend only, see below. Later phases (push -notifications, Redis fan-out, the admin portal, the bot/extension system, and -production deployment) are tracked as issues in the repo's issue tracker, -prioritized. +## Features + +- **Auth & accounts** — session-based auth, invite-only signup (admin-issued + site invites or room invites, both delivered by email), password reset, + per-user light/dark/midnight/sunset presets plus a live theme builder for + fully custom, named, savable color themes. +- **Rooms** — open and private rooms, owner/admin/member roles, invites, + room browsing/search, file/image galleries per room. +- **Real-time chat** — WebSocket-based messaging with automatic reconnect + and backoff, Markdown rendering, @mentions with autocomplete and inline + highlighting, emoji reactions, message editing, image and file + attachments (drag-and-drop, paste, or picker) with inline previews for + images/PDFs/text/Markdown, unread indicators, and presence (online/away/ + offline, with a manual "appear offline" override). +- **Notifications** — Web Push for offline/backgrounded members, with + per-type opt-in/out (mentions vs. all messages), plus in-app unread + badges. +- **PWA** — installable, offline-capable (cached room/message data, a + dedicated offline banner), with automatic update detection that prompts + a reload as soon as a new deploy goes live. +- **Admin portal** — user management, site invites, SMTP configuration, + audit log, and management of the bot/webhook system below. +- **Bots & integrations** — scoped API tokens for bot accounts, incoming + webhooks (post into a room from an external system) and outgoing webhooks + (signed HMAC event delivery on message create/update), all with SSRF + protection on any admin-supplied external URL. +- **Scale-out** — Redis-backed pub/sub for WebSocket fan-out and presence, + so the app runs across multiple horizontally-scaled instances rather than + a single process. + +## Tech stack + +- **Backend**: Python, FastAPI, SQLAlchemy 2.0 (fully async), PostgreSQL, + Redis, Alembic migrations, argon2 password hashing, Web Push (VAPID). +- **Frontend**: React 19, TypeScript, Vite, a hand-rolled WebSocket client + with reconnect/backoff and visibility-aware presence, a PWA service + worker (Workbox) for offline caching and push. +- **Deployment**: two bare Debian 13 servers (app + DB/cache), no + containers — see [DEPLOYMENT.md](DEPLOYMENT.md). ## Structure -- [`backend/`](backend/) — FastAPI + SQLAlchemy 2.0 (async) + PostgreSQL. See - [`backend/README.md`](backend/README.md) for local setup, migrations, how to - create a user (site registration is invite-only — no public sign-up - endpoint), and the Phase 2 room-roles/invites API. -- [`frontend/`](frontend/) — React + Vite PWA (login, room list, chat view). - Still Phase-1-only: it doesn't yet call any of the Phase 2 endpoints. A UI - redesign is happening separately; frontend work resumes once that lands. +- [`backend/`](backend/) — FastAPI + SQLAlchemy 2.0 (async) + PostgreSQL + + Redis. See [`backend/README.md`](backend/README.md) for local setup, + migrations, how to create a user (site registration is invite-only — no + public sign-up endpoint), and the full API surface. +- [`frontend/`](frontend/) — React + Vite PWA covering the full feature set: + auth, room roles/invites, chat, offline caching, push notifications, and + the admin portal. See [`frontend/README.md`](frontend/README.md). ## Quickstart @@ -29,7 +66,10 @@ 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 -# 2. Backend +# 2. Redis (required — used for cross-instance WebSocket fan-out and presence) +docker run -d --name ds-chat-redis -p 6379:6379 redis:7-alpine + +# 3. Backend cd backend python3 -m venv .venv .venv/bin/pip install -e ".[dev]" @@ -38,7 +78,7 @@ cp .env.example .env # then set SESSION_SECRET .venv/bin/python -m app.cli create-user alice alice@example.com "some-password" .venv/bin/uvicorn app.main:app --reload & -# 3. Frontend (in another shell) +# 4. Frontend (in another shell) cd frontend npm install npm run dev diff --git a/frontend/README.md b/frontend/README.md index 390e1ee..c5caafc 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,8 +1,14 @@ -# DS Chat frontend (Phase 1) +# DS Chat frontend -React + Vite PWA. Login, room list, and chat views wired to the backend's -REST API and `/ws/chat` WebSocket endpoint. See [`../README.md`](../README.md) -and [`../backend/README.md`](../backend/README.md) for full local setup. +React 19 + TypeScript + Vite PWA. The full client for DS Chat: auth and +invite-based signup, room CRUD with roles/invites, real-time WebSocket chat +(Markdown, @mentions, reactions, image/file attachments with previews, +message editing), unread indicators and presence, per-user theming +(presets plus a custom theme builder), Web Push notifications, offline +caching and an auto-update banner via a custom service worker, and a +site-admin portal — wired to the backend's REST API and `/ws/chat` WebSocket +endpoint. See [`../README.md`](../README.md) and +[`../backend/README.md`](../backend/README.md) for full local setup. ## Dev @@ -21,16 +27,73 @@ login page to work. npm run build ``` -Generates the PWA manifest and service worker via `vite-plugin-pwa` into `dist/`. +`vite-plugin-pwa` runs in `injectManifest` mode: instead of generating a +service worker, it precaches the build output (`src/sw.ts`'s +`precacheAndRoute`) and injects that manifest into the hand-written worker at +`src/sw.ts`. `injectManifest` mode was needed over the default `generateSW` +because push/`notificationclick` listeners have to be hand-written into the +worker. ## Layout ``` src/ - main.tsx, App.tsx routes: /login, /rooms, /rooms/:roomId - api/ fetch wrappers (client, auth, rooms) - ws/useChatSocket.ts WebSocket hook (join/send/receive) - context/AuthContext.tsx current-user state, hydrated via GET /api/auth/me - components/ ProtectedRoute, RoomListItem, MessageList, MessageInput - pages/ LoginPage, RoomListPage, ChatRoomPage + main.tsx, App.tsx routes: /login, /signup, /forgot-password, /reset-password, + /rooms, /rooms/:roomId, /admin (AdminRoute-gated); mounts + UpdateBanner globally and ChatSocketProvider once authed + types.ts shared request/response/WS-envelope types, mirroring the + backend's Pydantic schemas + + api/ fetch wrappers, one file per backend resource: client + (base fetch/error handling), auth, signup, rooms, users, + bots, webhooks, push, admin, customThemes, uploads + ws/useChatSocket.ts the WebSocket hook: connect/reconnect with backoff, + join/leave rooms, send/edit/react, visibility-gated + presence, triggers an SW update check on reconnect + context/ + AuthContext.tsx current-user state, hydrated via GET /api/auth/me + ChatSocketContext.tsx shares one useChatSocket instance across the app + + lib/ + avatar.ts deterministic accent-color cycling for avatars + emoji.ts, emojiNames.ts, + emojiShortcodes.ts, recentEmoji.ts emoji picker data + recency tracking + fileSize.ts human-readable byte formatting + lastUser.ts cached "who was I last logged in as" for offline shell render + messageGrouping.ts groups consecutive messages by sender/time, presence lookup + push.ts PushManager subscribe/unsubscribe, VAPID key conversion + swUpdate.ts bridges the SW registration to useChatSocket's reconnect hook + theme.ts applies preset/custom themes as CSS custom properties + + hooks/ + useEscapeKey.ts Escape-to-close for modals/popovers + useOnlineStatus.ts navigator.onLine, for the OfflineBanner + useResizableWidth.ts drag-to-resize (sidebar/panel widths) + useWindowWidth.ts viewport width, for responsive sidebar/pane layout + + components/ + ProtectedRoute.tsx, AdminRoute.tsx auth/site-admin route guards + TopBar.tsx, Sidebar.tsx, RoomRow.tsx room list chrome + ChatPane.tsx, MessageList.tsx, Composer.tsx chat view: history+live merge, + message rendering, composer/attach/send + MessageContent.tsx, MentionAutocomplete.tsx Markdown rendering + @mention highlighting/autocomplete + ImageLightbox.tsx, FilePreviewModal.tsx attachment viewers (image/PDF/text/Markdown) + EmojiPicker.tsx reaction/composer emoji picker + RoomInfoPanel.tsx room details/members/roles panel + NewRoomModal.tsx, BrowseRoomsModal.tsx, UserPicker.tsx room creation/discovery, member picking + ProfileModal.tsx, ThemeBuilderModal.tsx, CustomThemePreview.tsx + profile settings + the custom theme editor + (opened in its own wide dialog) with a live, + hoverable mockup of the real UI + RoomAvatar.tsx, UserAvatar.tsx avatar rendering (incl. presence dot) + OfflineBanner.tsx, UpdateBanner.tsx connectivity state / new-version-available prompt + + pages/ + LoginPage.tsx, SignupPage.tsx, ForgotPasswordPage.tsx, ResetPasswordPage.tsx + ChatShellPage.tsx, AdminPage.tsx + + styles/tokens.css design tokens (DarkSingularity theme: colors, spacing, etc.) + sw.ts custom service worker (injectManifest): app-shell + precache + NetworkFirst runtime caching, push/notificationclick + handlers, SKIP_WAITING messaging for the update-prompt flow ```