Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions apps/web/src/app/api/account/avatar/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,17 @@ import { NextRequest, NextResponse } from 'next/server';
import { authenticateRequestWithOptions, isAuthError } from '@/lib/auth';
import { db, users, eq } from '@pagespace/db';

import { PROCESSOR_URL } from '@/lib/processor-config';
import { createAvatarServiceToken } from '@/lib/auth/avatar-service';

const AUTH_OPTIONS = { allow: ['session'] as const, requireCSRF: true };
import { createUserServiceToken, type ServiceScope } from '@pagespace/lib';

// Maximum file size: 5MB
const MAX_FILE_SIZE = 5 * 1024 * 1024;

// Allowed image types
const ALLOWED_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];

// Processor service URL
const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';

const REQUIRED_AVATAR_SCOPES: ServiceScope[] = ['avatars:write'];

async function createAvatarServiceToken(
userId: string,
expirationTime: string
): Promise<{ token: string }> {
// createUserServiceToken validates that the user is accessing their own resources
const { token } = await createUserServiceToken(
userId,
REQUIRED_AVATAR_SCOPES,
expirationTime
);
return { token };
}

export async function POST(request: NextRequest) {
try {
// Verify authentication
Expand Down
21 changes: 2 additions & 19 deletions apps/web/src/app/api/account/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { users, db, eq } from '@pagespace/db';
import { createHash } from 'crypto';
import { loggers, accountRepository, activityLogRepository } from '@pagespace/lib/server';
import { authenticateRequestWithOptions, isAuthError } from '@/lib/auth';
import { createUserServiceToken, type ServiceScope } from '@pagespace/lib';
import { getActorInfo, logUserActivity } from '@pagespace/lib/monitoring/activity-logger';
import { PROCESSOR_URL } from '@/lib/processor-config';
import { createAvatarServiceToken } from '@/lib/auth/avatar-service';

const AUTH_OPTIONS_READ = { allow: ['session'] as const, requireCSRF: false };
const AUTH_OPTIONS_WRITE = { allow: ['session'] as const, requireCSRF: true };
Expand Down Expand Up @@ -114,9 +115,6 @@ export async function PATCH(req: Request) {
}
}

// Processor service URL
const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';

/**
* Create an anonymized identifier for GDPR-compliant audit trail preservation.
* Uses a deterministic hash so the same user ID always produces the same anonymized ID.
Expand All @@ -126,21 +124,6 @@ function createAnonymizedActorEmail(userId: string): string {
return `deleted_user_${hash}`;
}

const REQUIRED_AVATAR_SCOPES: ServiceScope[] = ['avatars:write'];

async function createAvatarServiceToken(
userId: string,
expirationTime: string
): Promise<{ token: string }> {
// createUserServiceToken validates that the user is accessing their own resources
const { token } = await createUserServiceToken(
userId,
REQUIRED_AVATAR_SCOPES,
expirationTime
);
return { token };
}

export async function DELETE(req: Request) {
const auth = await authenticateRequestWithOptions(req, AUTH_OPTIONS_WRITE);
if (isAuthError(auth)) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/channels/[pageId]/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { createUploadServiceToken, isPermissionDeniedError } from '@pagespace/li
import { sanitizeFilenameForHeader } from '@pagespace/lib/utils/file-security';
import { getActorInfo, logFileActivity } from '@pagespace/lib/monitoring/activity-logger';

const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
import { PROCESSOR_URL } from '@/lib/processor-config';

const AUTH_OPTIONS = { allow: ['session'] as const, requireCSRF: true };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import mammoth from 'mammoth';
import { createId } from '@paralleldrive/cuid2';
import { broadcastPageEvent, createPageEventPayload } from '@/lib/websocket';
import { getActorInfo, logFileActivity, logPageActivity } from '@pagespace/lib/monitoring/activity-logger';
import { PROCESSOR_URL } from '@/lib/processor-config';

const AUTH_OPTIONS = { allow: ['session'] as const, requireCSRF: true };

Expand Down Expand Up @@ -78,7 +79,6 @@ export async function POST(
}

// Fetch file from processor service using content hash
const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
const contentHash = filePage.filePath; // filePath stores the content hash

console.log('[Convert] Fetching file from processor:', {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/files/[id]/download/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface RouteParams {
}>;
}

const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
import { PROCESSOR_URL } from '@/lib/processor-config';

/**
* Fetch a file from the processor and return it as a download
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/files/[id]/view/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface RouteParams {
}>;
}

const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
import { PROCESSOR_URL } from '@/lib/processor-config';

/**
* Fetch a file from the processor service and return it with appropriate headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { db, pages, eq } from '@pagespace/db';
import { authenticateRequestWithOptions, isAuthError } from '@/lib/auth';
import { createPageServiceToken, canUserViewPage } from '@pagespace/lib';

const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
import { PROCESSOR_URL } from '@/lib/processor-config';
const AUTH_OPTIONS = { allow: ['session'] as const, requireCSRF: false };

export async function GET(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/pages/[pageId]/reprocess/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextResponse } from 'next/server';
import { db, pages, eq } from '@pagespace/db';
const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
import { PROCESSOR_URL } from '@/lib/processor-config';
import { authenticateRequestWithOptions, isAuthError } from '@/lib/auth';
import { createPageServiceToken } from '@pagespace/lib';
import { getActorInfo } from '@pagespace/lib/monitoring/activity-logger';
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ interface FileMetadata {
[key: string]: string | number | boolean | undefined;
}

// Processor service URL
const PROCESSOR_URL = process.env.PROCESSOR_URL || 'http://processor:3003';
import { PROCESSOR_URL } from '@/lib/processor-config';

const AUTH_OPTIONS = { allow: ['session', 'mcp'] as const, requireCSRF: true };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,46 +29,16 @@ import { SearchResultsRenderer, type SearchResult } from './SearchResultsRendere
import { AgentListRenderer, type AgentInfo } from './AgentListRenderer';
import { ActivityRenderer, type ActivityItem } from './ActivityRenderer';
import { WebSearchRenderer, type WebSearchResult } from './WebSearchRenderer';

interface ToolPart {
type: string;
toolName?: string;
toolCallId?: string;
state?: 'input-streaming' | 'input-available' | 'output-available' | 'output-error' | 'done' | 'streaming';
input?: unknown;
output?: unknown;
errorText?: string;
}

interface CompactToolCallRendererProps {
part: ToolPart;
}

// Helper function to count pages in tree structure (moved outside component)
const countPages = (items: TreeItem[]): number => {
return items.reduce((count, item) => {
return count + 1 + (item.children ? countPages(item.children) : 0);
}, 0);
};

// Helper for safe JSON parsing
const safeJsonParse = (value: unknown): Record<string, unknown> | null => {
if (typeof value === 'string') {
try {
return JSON.parse(value);
} catch {
return null;
}
}
if (typeof value === 'object' && value !== null) {
return value as Record<string, unknown>;
}
return null;
};

// Tool name mapping (moved outside component to avoid recreation)
const TOOL_NAME_MAP: Record<string, string> = {
'ask_agent': 'Ask Agent',
import {
type ToolPart,
safeJsonParse,
formatToolName,
countPages,
flattenActivityGroups,
} from './toolCallUtils';

// Compact-specific short name overrides for the condensed view
const COMPACT_TOOL_NAMES: Record<string, string> = {
'list_drives': 'List Drives',
'list_pages': 'List Pages',
'read_page': 'Read',
Expand All @@ -78,9 +48,13 @@ const TOOL_NAME_MAP: Record<string, string> = {
'trash': 'Trash',
'restore': 'Restore',
'move_page': 'Move',
'list_trash': 'List Trash'
'list_trash': 'List Trash',
};

interface CompactToolCallRendererProps {
part: ToolPart;
}

// Internal renderer component with hooks
const CompactToolCallRendererInternal: React.FC<{ part: ToolPart; toolName: string }> = memo(function CompactToolCallRendererInternal({ part, toolName }) {
const [isExpanded, setIsExpanded] = useState(false);
Expand Down Expand Up @@ -144,12 +118,9 @@ const CompactToolCallRendererInternal: React.FC<{ part: ToolPart; toolName: stri
}
}, [state, error]);

// Memoize formatted tool name
// Memoize formatted tool name (compact-specific short names take priority)
const formattedToolName = useMemo(() => {
return TOOL_NAME_MAP[toolName] || toolName
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
return COMPACT_TOOL_NAMES[toolName] || formatToolName(toolName);
}, [toolName]);

// Memoize descriptive title
Expand Down Expand Up @@ -575,43 +546,7 @@ const CompactToolCallRendererInternal: React.FC<{ part: ToolPart; toolName: stri
}>;
}>;

const opToAction = (op: string): 'created' | 'updated' | 'deleted' | 'restored' | 'moved' | 'renamed' => {
switch (op) {
case 'create': return 'created';
case 'update': return 'updated';
case 'delete': case 'trash': return 'deleted';
case 'restore': return 'restored';
case 'move': case 'reorder': return 'moved';
case 'rename': return 'renamed';
default: return 'updated';
}
};

const flatActivities: ActivityItem[] = [];
for (const group of driveGroups) {
for (const activity of group.activities) {
const actor = actors[activity.actor];
flatActivities.push({
id: activity.id,
action: opToAction(activity.op),
pageId: activity.pageId || undefined,
pageTitle: activity.title || undefined,
pageType: activity.res === 'page' ? undefined : activity.res,
driveId: group.drive.id,
driveName: group.drive.name,
actorName: actor?.name || actor?.email || undefined,
timestamp: activity.ts,
summary: activity.ai ? `AI-generated (${activity.ai})` : undefined,
});
}
}

flatActivities.sort((a, b) => {
const timeA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
const timeB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
return timeB - timeA;
});

const flatActivities = flattenActivityGroups(driveGroups, actors);
const meta = result.meta as { window?: string } | undefined;
const period = meta?.window ? `Last ${meta.window}` : undefined;

Expand Down
Loading