7.1 KiB
Nextcloud Talk Integration — Design Document
Overview
Add a read-only Nextcloud Talk widget to the Feuerwehr Dashboard showing the user's 2-3 most recent chats, unread message counts, and clickable links to open conversations in Nextcloud Talk. Users connect their Nextcloud account once via Login Flow v2, which generates an app password stored in the database.
Architecture
Auth Flow (Login Flow v2 — App Passwords)
Each user connects their Nextcloud account through the Nextcloud Login Flow v2. This generates an app-specific password that the backend stores and uses for all subsequent Nextcloud API calls via HTTP Basic Auth. No Authentik session cookies are involved.
Data flow:
Frontend → Dashboard Backend (JWT auth) → Nextcloud OCS API (Basic Auth with stored app password)
- User clicks "Mit Nextcloud verbinden" in the widget
- Frontend calls
POST /api/nextcloud/connectto initiate Login Flow v2 - Backend requests a login token from Nextcloud and returns the
loginURL +pollendpoint/token - Frontend opens the Nextcloud login URL in a new tab; user authorizes the app
- Frontend polls
POST /api/nextcloud/polluntil Nextcloud returns credentials - Backend stores the app password (encrypted) and Nextcloud username in the user's DB record
- Subsequent
GET /api/nextcloud/talk/roomscalls use the stored credentials with Basic Auth - User can disconnect via
DELETE /api/nextcloud/connect, which revokes the app password
Components to Build
- Backend: Four endpoints under
/api/nextcloud - Frontend: One widget component (
NextcloudTalkWidget) with connect/disconnect flow - Database: Migration adding
nextcloud_usernameandnextcloud_app_passwordcolumns to users table - Config: One env var:
NEXTCLOUD_URL
Backend: Nextcloud Endpoints
GET /api/nextcloud/talk/rooms
Fetches the user's recent conversations using their stored Nextcloud credentials.
- Reads the user's
nextcloud_usernameandnextcloud_app_passwordfrom the database - Calls Nextcloud OCS API with Basic Auth and
OCS-APIRequest: trueheader - Filters out
type: 4(changelog) conversations - Sorts by
lastActivitydescending, limits to top 3 - Calculates
totalUnreadsum across all conversations - Pre-builds deep link URLs
Response shape:
{
success: true,
data: {
totalUnread: 12,
conversations: [
{
token: "abc123",
displayName: "Einsatzgruppe 1",
unreadMessages: 5,
lastActivity: 1709142000,
lastMessage: {
text: "Einsatz morgen um 8:00",
author: "Max Mustermann",
timestamp: 1709142000
},
type: 2,
url: "https://cloud.feuerwehr-rems.at/call/abc123"
}
]
}
}
POST /api/nextcloud/connect
Initiates Login Flow v2 by requesting a login token from Nextcloud. Returns the login URL (for the user to open in a new tab) and the poll endpoint/token (for the frontend to poll).
POST /api/nextcloud/poll
Polls the Nextcloud Login Flow v2 endpoint. When the user has authorized the app, Nextcloud returns the app password and username. The backend stores these credentials in the user's DB record.
DELETE /api/nextcloud/connect
Disconnects the Nextcloud account: revokes the app password via the Nextcloud provisioning API and removes the stored credentials from the database.
Error handling:
- No stored credentials →
401withnextcloud_not_connectederror - Invalid/revoked app password →
401 - Nextcloud unreachable →
502
Frontend: NextcloudTalkWidget
State: Not Connected
┌─────────────────────────────────┐
│ Nextcloud Talk │
├─────────────────────────────────┤
│ │
│ Mit Nextcloud verbinden │ ← Button triggers Login Flow v2
│ │
└─────────────────────────────────┘
Clicking the button calls POST /api/nextcloud/connect, opens the returned login URL in a new tab, and starts polling POST /api/nextcloud/poll until credentials are obtained.
State: Connected
┌─────────────────────────────────┐
│ Nextcloud Talk (12) │ ← Header + total unread badge
├─────────────────────────────────┤
│ Einsatzgruppe 1 (5) │ ← Chat name + unread count
│ Max: Einsatz morgen um 8:00 │ ← Last message preview
│─────────────────────────────────│
│ Ausbildung (4) │
│ Anna: Termin verschoben │
│─────────────────────────────────│
│ Kommando (3) │
│ Peter: Protokoll angehängt │
├─────────────────────────────────┤
│ Trennen ⋮ │ ← Disconnect option
└─────────────────────────────────┘
Each row clickable → opens /call/{token} in new tab
Behavior:
- React Query with 30s refetch interval
- SkeletonCard during loading
- Error state: "Nextcloud nicht erreichbar"
- Clickable rows with hover effect, open deep link in new tab
- MUI Badge/Chip for unread counts, hidden when zero
- Relative timestamps via date-fns (e.g., "vor 5 Min.")
- MUI Grid item:
xs={12} md={6} lg={4}
Configuration
New env var:
NEXTCLOUD_URL=https://cloud.feuerwehr-rems.at
Added to .env.example, docker-compose.yml, and backend/src/config/environment.ts.
Files to Create/Modify
New Files
backend/src/routes/nextcloud.routes.ts— Route definitions (4 endpoints)backend/src/controllers/nextcloud.controller.ts— Request handlersbackend/src/services/nextcloud.service.ts— Login Flow v2 + OCS API logicbackend/src/database/migrations/013_add_nextcloud_credentials.sql— Addnextcloud_usernameandnextcloud_app_passwordcolumnsfrontend/src/components/dashboard/NextcloudTalkWidget.tsx— Widget with connect/disconnect flowfrontend/src/services/nextcloud.ts— Frontend API service (connect, poll, disconnect, getRooms)frontend/src/types/nextcloud.types.ts— TypeScript interfaces
Modified Files
backend/src/app.ts— Register nextcloud routesbackend/src/config/environment.ts— Add NEXTCLOUD_URLbackend/src/models/user.model.ts— Addnextcloud_usernameandnextcloud_app_passwordfields to User interfacebackend/src/services/user.service.ts— Add methods to store/retrieve/clear Nextcloud credentialsfrontend/src/pages/Dashboard.tsx— Add widget to grid.env.example— Add NEXTCLOUD_URLdocker-compose.yml— Pass NEXTCLOUD_URL to backend