chore: test deploy snapshot
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -66,6 +66,7 @@ import { getTokenSettingById, type TokenSettingRecord } from '../services/token-
|
||||
const CHAT_ATTACHMENT_FILE_SIZE_LIMIT = 300 * 1024 * 1024;
|
||||
const CHAT_ATTACHMENT_ROUTE_BODY_LIMIT = 450 * 1024 * 1024;
|
||||
const CHAT_PUBLIC_ROUTE_PREFIX = '/.codex_chat/';
|
||||
const CHAT_LEGACY_PUBLIC_ROUTE_PREFIX = '/public/.codex_chat/';
|
||||
const CHAT_API_RESOURCE_ROUTE_PREFIX = '/api/chat/resources';
|
||||
const CHAT_SHARE_ROUTE_PREFIX = '/api/chat/shares';
|
||||
const CHAT_SHARE_TOKEN_VERSION = 1;
|
||||
@@ -1235,7 +1236,11 @@ async function buildChatShareSnapshot(
|
||||
tokenPayload.kind === 'request-bundle' && normalizedSessionId.startsWith(MANAGED_CHAT_SHARE_SESSION_PREFIX);
|
||||
const useInitialManagedShareRoomView = isManagedShareRoomSession && detailLevel === 'initial';
|
||||
const detailPage = useInitialManagedShareRoomView
|
||||
? await listChatConversationDetailPage(normalizedSessionId, { limit: 12 })
|
||||
? await listChatConversationDetailPage(normalizedSessionId, {
|
||||
limit: 12,
|
||||
includeActivityLogs: false,
|
||||
includePagination: false,
|
||||
})
|
||||
: null;
|
||||
const [requests, messages] = detailPage
|
||||
? [detailPage.requests, detailPage.messages]
|
||||
@@ -1571,7 +1576,7 @@ async function ensureManagedShareAccessPin(
|
||||
});
|
||||
|
||||
if (pinStatus.status === 'ok' || pinStatus.status === 'not-configured') {
|
||||
return true;
|
||||
return pinStatus;
|
||||
}
|
||||
|
||||
if (pinStatus.status === 'required') {
|
||||
@@ -1579,7 +1584,7 @@ async function ensureManagedShareAccessPin(
|
||||
code: 'share_pin_required',
|
||||
message: '이 공유 채팅방은 4자리 비밀번호 입력이 필요합니다.',
|
||||
});
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pinStatus.status === 'invalid') {
|
||||
@@ -1587,17 +1592,17 @@ async function ensureManagedShareAccessPin(
|
||||
code: 'share_pin_invalid',
|
||||
message: '공유 채팅방 비밀번호가 올바르지 않습니다.',
|
||||
});
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pinStatus.status === 'missing') {
|
||||
reply.code(404).send({
|
||||
message: '공유 링크를 찾을 수 없습니다.',
|
||||
});
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasManagedSharePermission(
|
||||
@@ -1641,6 +1646,11 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
||||
});
|
||||
|
||||
app.get(`${CHAT_LEGACY_PUBLIC_ROUTE_PREFIX}*`, async (request, reply) => {
|
||||
const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
|
||||
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
||||
});
|
||||
|
||||
app.get(`${CHAT_API_RESOURCE_ROUTE_PREFIX}/*`, async (request, reply) => {
|
||||
const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
|
||||
return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
|
||||
@@ -1668,7 +1678,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
||||
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||
|
||||
if (!accessPinStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1898,7 +1910,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
||||
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||
|
||||
if (!accessPinStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2033,7 +2047,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
||||
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||
|
||||
if (!accessPinStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2467,7 +2483,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
|
||||
const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);
|
||||
|
||||
if (!accessPinStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2497,15 +2515,19 @@ export async function registerChatRoutes(app: FastifyInstance) {
|
||||
});
|
||||
}
|
||||
|
||||
const accessPinStatus = await validateSharedResourceAccessPinBySharePath(managedContext.sharePath, getRequestChatSharePin(request), {
|
||||
clientId: getRequestClientId(request),
|
||||
});
|
||||
|
||||
if (managedContext.managedResource) {
|
||||
await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
|
||||
void recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
|
||||
actorLabel: 'share-viewer',
|
||||
summary: '공유 채팅 링크를 열었습니다.',
|
||||
detail: managedContext.managedResource.token.resourceLabel,
|
||||
}).catch((error) => {
|
||||
request.log.warn(
|
||||
{
|
||||
err: error,
|
||||
managedResourceTokenId: managedContext.managedResource?.token.id,
|
||||
},
|
||||
'Failed to record shared chat view usage',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { extractChatMessageParts, parseChatMessageParts } from './chat-message-parts.js';
|
||||
|
||||
test('extractChatMessageParts normalizes absolute legacy dot-codex prompt preview urls to api chat resource urls', () => {
|
||||
const input = [
|
||||
'문서 미리보기',
|
||||
'[[prompt:{"title":"확인","options":[{"label":"legacy","value":"legacy","preview":{"type":"resource","url":"https://preview.sm-home.cloud/public/.codex_chat/chat-room/resource/source/chat-room-reference.md"}}]}]]',
|
||||
].join('\n');
|
||||
|
||||
const parsed = extractChatMessageParts(input);
|
||||
const prompt = parsed.parts.find((part): part is Extract<(typeof parsed.parts)[number], { type: 'prompt' }> => part.type === 'prompt');
|
||||
|
||||
assert.ok(prompt);
|
||||
assert.equal(
|
||||
prompt.options[0]?.preview?.url,
|
||||
'/api/chat/resources/chat-room/resource/source/chat-room-reference.md',
|
||||
);
|
||||
});
|
||||
|
||||
test('parseChatMessageParts normalizes absolute legacy link card urls to api chat resource urls', () => {
|
||||
const parsed = parseChatMessageParts([
|
||||
{
|
||||
type: 'link_card',
|
||||
title: 'legacy resource',
|
||||
url: 'https://preview.sm-home.cloud/.codex_chat/chat-room/resource/uploads/spec.png',
|
||||
actionLabel: '열기',
|
||||
},
|
||||
]);
|
||||
|
||||
assert.deepEqual(parsed, [
|
||||
{
|
||||
type: 'link_card',
|
||||
title: 'legacy resource',
|
||||
url: '/api/chat/resources/chat-room/resource/uploads/spec.png',
|
||||
actionLabel: '열기',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -237,6 +237,14 @@ function normalizeUrl(value: string) {
|
||||
if (pathname.startsWith(CHAT_API_RESOURCE_MARKER) || pathname.startsWith(RESOURCE_MANAGER_PREVIEW_MARKER)) {
|
||||
return normalizePreviewPathHash(pathname);
|
||||
}
|
||||
|
||||
if (pathname.startsWith(CHAT_PUBLIC_DOT_CODEX_MARKER)) {
|
||||
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_PUBLIC_DOT_CODEX_MARKER.length)}`;
|
||||
}
|
||||
|
||||
if (pathname.startsWith(CHAT_DOT_CODEX_MARKER)) {
|
||||
return `${CHAT_API_RESOURCE_MARKER}${pathname.slice(CHAT_DOT_CODEX_MARKER.length)}`;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to handle relative and embedded resource paths below.
|
||||
}
|
||||
|
||||
@@ -3570,12 +3570,16 @@ export async function listChatConversationDetailPage(
|
||||
options: {
|
||||
limit?: number;
|
||||
beforeMessageId?: number | null;
|
||||
includeActivityLogs?: boolean;
|
||||
includePagination?: boolean;
|
||||
} = {},
|
||||
): Promise<ChatConversationDetailPage> {
|
||||
const normalizedSessionId = sessionId.trim();
|
||||
await getChatConversation(normalizedSessionId, null);
|
||||
const conversation = await db(CHAT_CONVERSATION_TABLE).where({ session_id: normalizedSessionId }).first();
|
||||
const normalizedLimit = Math.max(1, Math.min(100, Math.round(options.limit ?? 10)));
|
||||
const includeActivityLogs = options.includeActivityLogs !== false;
|
||||
const includePagination = options.includePagination !== false;
|
||||
const normalizedBeforeMessageId =
|
||||
Number.isFinite(options.beforeMessageId) && (options.beforeMessageId ?? 0) > 0
|
||||
? Math.trunc(options.beforeMessageId as number)
|
||||
@@ -3629,21 +3633,26 @@ export async function listChatConversationDetailPage(
|
||||
.orderBy('message_id', 'asc')
|
||||
.orderBy('id', 'asc');
|
||||
const messages = messageRows.map((row: Parameters<typeof mapMessageRow>[0]) => mapMessageRow(row));
|
||||
const activityLogs = await listChatConversationActivityLogsByRequestIds(normalizedSessionId, requestIds);
|
||||
const oldestLoadedMessageId =
|
||||
requests.reduce<number | null>((oldestId, request) => {
|
||||
const candidateIds = [request.userMessageId, request.responseMessageId].filter(
|
||||
(value): value is number => typeof value === 'number' && Number.isInteger(value) && value > 0,
|
||||
);
|
||||
const activityLogs = includeActivityLogs
|
||||
? await listChatConversationActivityLogsByRequestIds(normalizedSessionId, requestIds)
|
||||
: [];
|
||||
const oldestLoadedMessageId = includePagination
|
||||
? (
|
||||
requests.reduce<number | null>((oldestId, request) => {
|
||||
const candidateIds = [request.userMessageId, request.responseMessageId].filter(
|
||||
(value): value is number => typeof value === 'number' && Number.isInteger(value) && value > 0,
|
||||
);
|
||||
|
||||
if (candidateIds.length === 0) {
|
||||
return oldestId;
|
||||
}
|
||||
if (candidateIds.length === 0) {
|
||||
return oldestId;
|
||||
}
|
||||
|
||||
const nextCandidateId = Math.min(...candidateIds);
|
||||
return oldestId == null ? nextCandidateId : Math.min(oldestId, nextCandidateId);
|
||||
}, null) ?? messages[0]?.id ?? null;
|
||||
const oldestRequest = requests[0] ?? null;
|
||||
const nextCandidateId = Math.min(...candidateIds);
|
||||
return oldestId == null ? nextCandidateId : Math.min(oldestId, nextCandidateId);
|
||||
}, null) ?? messages[0]?.id ?? null
|
||||
)
|
||||
: null;
|
||||
const oldestRequest = includePagination ? requests[0] ?? null : null;
|
||||
const hasOlderMessages = oldestRequest
|
||||
? Boolean(
|
||||
await db(CHAT_CONVERSATION_REQUEST_TABLE)
|
||||
|
||||
Reference in New Issue
Block a user