Skip to content

Commit 7fde44b

Browse files
committed
Filter chat history by authenticated user
- Add /api/user endpoint to get current authenticated user from EasyAuth headers - Update conversation endpoints to use auth headers instead of query params - Modify Cosmos DB query to filter by user_id (empty for dev mode) - Update frontend to fetch user info and remove userId prop from ChatHistory - Fix Dockerfile to use port 8000 and single worker - Fix f-string syntax error in app.py
1 parent b0a202e commit 7fde44b

8 files changed

Lines changed: 128 additions & 54 deletions

File tree

content-gen/aci-deployment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ properties:
3636
value: conversations
3737
- name: AZURE_BLOB_ACCOUNT_NAME
3838
value: storagecontentgenjh
39+
- name: PORT
40+
value: '8000'
41+
- name: WORKERS
42+
value: '1'
3943
- name: RUNNING_IN_PRODUCTION
4044
value: 'true'
4145
- name: AUTH_ENABLED

content-gen/src/Dockerfile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ COPY . .
1818

1919
# Set environment variables
2020
ENV PYTHONPATH=/app
21-
ENV PORT=5000
21+
ENV PORT=8000
22+
ENV PYTHONUNBUFFERED=1
2223

2324
# Expose port
24-
EXPOSE 5000
25+
EXPOSE 8000
2526

2627
# Health check
2728
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
28-
CMD curl -f http://localhost:5000/health || exit 1
29+
CMD curl -f http://localhost:${PORT}/health || exit 1
2930

3031
# Run the application
31-
CMD ["hypercorn", "app:app", "--config", "hypercorn.conf.py"]
32+
CMD ["hypercorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "1"]

content-gen/src/app.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@
3232
app = cors(app, allow_origin="*")
3333

3434

35+
# ==================== Authentication Helper ====================
36+
37+
def get_authenticated_user():
38+
"""
39+
Get the authenticated user from EasyAuth headers.
40+
41+
In production (with App Service Auth), the X-Ms-Client-Principal-Id header
42+
contains the user's ID. In development mode, returns empty/None values.
43+
"""
44+
user_principal_id = request.headers.get("X-Ms-Client-Principal-Id", "")
45+
user_name = request.headers.get("X-Ms-Client-Principal-Name", "")
46+
auth_provider = request.headers.get("X-Ms-Client-Principal-Idp", "")
47+
48+
return {
49+
"user_principal_id": user_principal_id or "",
50+
"user_name": user_name or "",
51+
"auth_provider": auth_provider or "",
52+
"is_authenticated": bool(user_principal_id)
53+
}
54+
55+
3556
# ==================== Health Check ====================
3657

3758
@app.route("/health", methods=["GET"])
@@ -44,6 +65,19 @@ async def health_check():
4465
})
4566

4667

68+
# ==================== User Info Endpoint ====================
69+
70+
@app.route("/api/user", methods=["GET"])
71+
async def get_current_user():
72+
"""
73+
Get the current authenticated user info.
74+
75+
Returns user details from EasyAuth headers, or empty values if not authenticated.
76+
"""
77+
user = get_authenticated_user()
78+
return jsonify(user)
79+
80+
4781
# ==================== Chat Endpoints ====================
4882

4983
@app.route("/api/chat", methods=["POST"])
@@ -341,7 +375,7 @@ async def generate():
341375
user_id=user_id,
342376
message={
343377
"role": "assistant",
344-
"content": f"Content generated successfully! {f'Headline: "{headline}"' if headline else ''}",
378+
"content": f"Content generated successfully!{' Headline: ' + headline if headline else ''}",
345379
"agent": "ContentAgent",
346380
"timestamp": datetime.now(timezone.utc).isoformat()
347381
}
@@ -544,15 +578,17 @@ async def list_conversations():
544578
"""
545579
List conversations for a user.
546580
581+
Uses authenticated user from EasyAuth headers. In development mode
582+
(when not authenticated), returns conversations where user_id is empty/null.
583+
547584
Query params:
548-
user_id: User identifier (required)
549585
limit: Max number of results (default 20)
550586
"""
551-
user_id = request.args.get("user_id")
552-
limit = int(request.args.get("limit", 20))
587+
# Get authenticated user from headers
588+
auth_user = get_authenticated_user()
589+
user_id = auth_user["user_principal_id"] # Empty string if not authenticated
553590

554-
if not user_id:
555-
return jsonify({"error": "user_id is required"}), 400
591+
limit = int(request.args.get("limit", 20))
556592

557593
cosmos_service = await get_cosmos_service()
558594
conversations = await cosmos_service.get_user_conversations(user_id, limit)
@@ -568,13 +604,10 @@ async def get_conversation(conversation_id: str):
568604
"""
569605
Get a specific conversation.
570606
571-
Query params:
572-
user_id: User identifier (required)
607+
Uses authenticated user from EasyAuth headers.
573608
"""
574-
user_id = request.args.get("user_id")
575-
576-
if not user_id:
577-
return jsonify({"error": "user_id is required"}), 400
609+
auth_user = get_authenticated_user()
610+
user_id = auth_user["user_principal_id"]
578611

579612
cosmos_service = await get_cosmos_service()
580613
conversation = await cosmos_service.get_conversation(conversation_id, user_id)
@@ -590,13 +623,10 @@ async def delete_conversation(conversation_id: str):
590623
"""
591624
Delete a specific conversation.
592625
593-
Query params:
594-
user_id: User identifier (required)
626+
Uses authenticated user from EasyAuth headers.
595627
"""
596-
user_id = request.args.get("user_id")
597-
598-
if not user_id:
599-
return jsonify({"error": "user_id is required"}), 400
628+
auth_user = get_authenticated_user()
629+
user_id = auth_user["user_principal_id"]
600630

601631
try:
602632
cosmos_service = await get_cosmos_service()

content-gen/src/backend/services/cosmos_service.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ async def get_user_conversations(
367367
Get all conversations for a user with summary data.
368368
369369
Args:
370-
user_id: User ID
370+
user_id: User ID (empty string for development mode - returns conversations with empty/null user_id)
371371
limit: Maximum number of conversations
372372
373373
Returns:
@@ -376,16 +376,30 @@ async def get_user_conversations(
376376
await self.initialize()
377377

378378
# Get conversations with messages to extract title and last message
379-
query = """
380-
SELECT TOP @limit c.id, c.user_id, c.updated_at, c.messages, c.brief
381-
FROM c
382-
WHERE c.user_id = @user_id
383-
ORDER BY c.updated_at DESC
384-
"""
385-
params = [
386-
{"name": "@user_id", "value": user_id},
387-
{"name": "@limit", "value": limit}
388-
]
379+
# In development mode (empty user_id), get conversations where user_id is empty, null, or not set
380+
if user_id:
381+
# Production mode: get conversations for the authenticated user
382+
query = """
383+
SELECT TOP @limit c.id, c.user_id, c.updated_at, c.messages, c.brief
384+
FROM c
385+
WHERE c.user_id = @user_id
386+
ORDER BY c.updated_at DESC
387+
"""
388+
params = [
389+
{"name": "@user_id", "value": user_id},
390+
{"name": "@limit", "value": limit}
391+
]
392+
else:
393+
# Development mode: get conversations where user_id is empty, null, or not defined
394+
query = """
395+
SELECT TOP @limit c.id, c.user_id, c.updated_at, c.messages, c.brief
396+
FROM c
397+
WHERE (NOT IS_DEFINED(c.user_id) OR c.user_id = null OR c.user_id = "")
398+
ORDER BY c.updated_at DESC
399+
"""
400+
params = [
401+
{"name": "@limit", "value": limit}
402+
]
389403

390404
conversations = []
391405
async for item in self._conversations_container.query_items(

content-gen/src/frontend/src/App.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useCallback } from 'react';
1+
import { useState, useCallback, useEffect } from 'react';
22
import {
33
Title1,
44
Subtitle1,
@@ -14,9 +14,16 @@ import { ChatPanel } from './components/ChatPanel';
1414
import { ChatHistory } from './components/ChatHistory';
1515
import type { ChatMessage, CreativeBrief, Product, GeneratedContent } from './types';
1616

17+
interface UserInfo {
18+
user_principal_id: string;
19+
user_name: string;
20+
auth_provider: string;
21+
is_authenticated: boolean;
22+
}
23+
1724
function App() {
1825
const [conversationId, setConversationId] = useState<string>(() => uuidv4());
19-
const [userId] = useState<string>('demo-user');
26+
const [userId, setUserId] = useState<string>('');
2027
const [messages, setMessages] = useState<ChatMessage[]>([]);
2128
const [isLoading, setIsLoading] = useState(false);
2229

@@ -33,6 +40,25 @@ function App() {
3340
// Trigger for refreshing chat history
3441
const [historyRefreshTrigger, setHistoryRefreshTrigger] = useState(0);
3542

43+
// Fetch current user on mount
44+
useEffect(() => {
45+
const fetchUser = async () => {
46+
try {
47+
const response = await fetch('/api/user');
48+
if (response.ok) {
49+
const user: UserInfo = await response.json();
50+
// Use user_principal_id if authenticated, otherwise empty string for dev mode
51+
setUserId(user.user_principal_id || '');
52+
}
53+
} catch (err) {
54+
console.error('Error fetching user:', err);
55+
// Default to empty string for development mode
56+
setUserId('');
57+
}
58+
};
59+
fetchUser();
60+
}, []);
61+
3662
// Handle selecting a conversation from history
3763
const handleSelectConversation = useCallback(async (selectedConversationId: string) => {
3864
setIsLoading(true);
@@ -350,7 +376,6 @@ function App() {
350376
<div className="history-panel">
351377
<ChatHistory
352378
currentConversationId={conversationId}
353-
userId={userId}
354379
currentMessages={messages}
355380
onSelectConversation={handleSelectConversation}
356381
onNewConversation={handleNewConversation}

content-gen/src/frontend/src/components/ChatHistory.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ interface ConversationSummary {
3232

3333
interface ChatHistoryProps {
3434
currentConversationId: string;
35-
userId: string;
3635
currentMessages?: { role: string; content: string }[]; // Current session messages
3736
onSelectConversation: (conversationId: string) => void;
3837
onNewConversation: () => void;
@@ -41,7 +40,6 @@ interface ChatHistoryProps {
4140

4241
export function ChatHistory({
4342
currentConversationId,
44-
userId,
4543
currentMessages = [],
4644
onSelectConversation,
4745
onNewConversation,
@@ -57,7 +55,8 @@ export function ChatHistory({
5755
setIsLoading(true);
5856
setError(null);
5957
try {
60-
const response = await fetch(`/api/conversations?user_id=${encodeURIComponent(userId)}`);
58+
// Backend gets user from auth headers, no need to pass user_id
59+
const response = await fetch('/api/conversations');
6160
if (response.ok) {
6261
const data = await response.json();
6362
setConversations(data.conversations || []);
@@ -72,7 +71,7 @@ export function ChatHistory({
7271
} finally {
7372
setIsLoading(false);
7473
}
75-
}, [userId]);
74+
}, []);
7675

7776
useEffect(() => {
7877
loadConversations();
@@ -116,7 +115,8 @@ export function ChatHistory({
116115
const handleDeleteConversation = async (conversationId: string, e: React.MouseEvent) => {
117116
e.stopPropagation();
118117
try {
119-
const response = await fetch(`/api/conversations/${conversationId}?user_id=${encodeURIComponent(userId)}`, {
118+
// Backend gets user from auth headers
119+
const response = await fetch(`/api/conversations/${conversationId}`, {
120120
method: 'DELETE',
121121
});
122122
if (response.ok) {

content-gen/src/static/assets/index-uIwqAuu8.js renamed to content-gen/src/static/assets/index-Ds2DWner.js

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

content-gen/src/static/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Content Generation Accelerator</title>
8-
<script type="module" crossorigin src="/assets/index-uIwqAuu8.js"></script>
8+
<script type="module" crossorigin src="/assets/index-Ds2DWner.js"></script>
99
<link rel="stylesheet" crossorigin href="/assets/index-C6xB6aWi.css">
1010
</head>
1111
<body>

0 commit comments

Comments
 (0)