@promptbook/cli 0.112.0-101 → 0.112.0-103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/agents-server/package.json +1 -1
- package/apps/agents-server/scripts/prerender-homepage.js +76 -1
- package/apps/agents-server/src/app/actions.ts +0 -6
- package/apps/agents-server/src/app/admin/about/page.tsx +1 -1
- package/apps/agents-server/src/app/admin/image-generator-test/ImageAttachmentsEditor.tsx +11 -6
- package/apps/agents-server/src/app/admin/login-methods/shibboleth/page.tsx +365 -0
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +13 -15
- package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +3 -3
- package/apps/agents-server/src/app/admin/servers/useCreateServerWizard.ts +13 -14
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +12 -3
- package/apps/agents-server/src/app/admin/usage/UsageClientTimelineChart.tsx +1 -1
- package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +21 -14
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatPageLayout.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +11 -7
- package/apps/agents-server/src/app/api/admin/cli-access/route.ts +27 -123
- package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +33 -125
- package/apps/agents-server/src/app/api/auth/login/route.ts +0 -10
- package/apps/agents-server/src/app/api/auth/shibboleth/acs/route.ts +77 -57
- package/apps/agents-server/src/app/api/auth/shibboleth/login/route.ts +57 -33
- package/apps/agents-server/src/app/api/auth/shibboleth/metadata/route.ts +4 -29
- package/apps/agents-server/src/app/api/auth/shibboleth/status/route.ts +17 -0
- package/apps/agents-server/src/app/api/upload/route.ts +148 -209
- package/apps/agents-server/src/app/api/users/[username]/route.ts +1 -1
- package/apps/agents-server/src/app/api/users/route.ts +5 -5
- package/apps/agents-server/src/app/dashboard/page.tsx +1 -1
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +1 -1
- package/apps/agents-server/src/app/docs/page.tsx +1 -1
- package/apps/agents-server/src/app/globals.css +100 -0
- package/apps/agents-server/src/app/layout.tsx +7 -0
- package/apps/agents-server/src/app/recycle-bin/page.tsx +1 -1
- package/apps/agents-server/src/app/system/settings/KeybindingsSettingsClient.tsx +13 -7
- package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +29 -1
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +3 -3
- package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +8 -2
- package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +4 -4
- package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +9 -9
- package/apps/agents-server/src/components/Footer/Footer.tsx +7 -7
- package/apps/agents-server/src/components/Header/Header.tsx +24 -4
- package/apps/agents-server/src/components/Header/HeaderTypes.ts +6 -0
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +51 -1
- package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
- package/apps/agents-server/src/components/Homepage/Section.tsx +3 -1
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +12 -1
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +100 -149
- package/apps/agents-server/src/components/Skeleton/ConsolePageLoadingSkeleton.tsx +1 -1
- package/apps/agents-server/src/components/Skeleton/DocumentationRouteLoadingSkeleton.tsx +1 -1
- package/apps/agents-server/src/components/Skeleton/HomepageLoadingSkeleton.tsx +1 -1
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +20 -4
- package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +3 -0
- package/apps/agents-server/src/constants/shibbolethAuth.ts +139 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +54 -80
- package/apps/agents-server/src/database/migrate.ts +30 -1
- package/apps/agents-server/src/database/migrations/2026-06-0100-shibboleth-auth.sql +136 -0
- package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +88 -36
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +4 -2
- package/apps/agents-server/src/languages/translations/czech.yaml +4 -2
- package/apps/agents-server/src/languages/translations/english.yaml +5 -3
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +54 -11
- package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +18 -2
- package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +6 -5
- package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +5 -0
- package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +11 -0
- package/apps/agents-server/src/utils/createAdminTerminalRouteHandlers.ts +264 -0
- package/apps/agents-server/src/utils/shareTargetPayloads.ts +19 -66
- package/apps/agents-server/src/utils/shibbolethAuthentication.ts +729 -621
- package/apps/agents-server/src/utils/upload/createBookEditorUploadHandler.ts +19 -28
- package/apps/agents-server/src/utils/upload/uploadFileToServer.ts +113 -0
- package/esm/index.es.js +194 -35
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/waitForPause.d.ts +12 -0
- package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -1
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +1 -0
- package/esm/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +1 -1
- package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/book-components/Chat/MarkdownContent/MarkdownContent.tsx +63 -4
- package/src/other/templates/getTemplatesPipelineCollection.ts +730 -739
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/index.umd.js +194 -35
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/waitForPause.d.ts +12 -0
- package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -1
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +1 -0
- package/umd/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +1 -1
- package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/umd/src/version.d.ts +1 -1
- package/apps/agents-server/src/app/api/auth/methods/route.ts +0 -44
- package/apps/agents-server/src/constants/authenticationMethods.ts +0 -74
- package/apps/agents-server/src/constants/shibbolethAuthentication.ts +0 -107
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
import { $getTableName } from '@/src/database/$getTableName';
|
|
2
|
-
import { $provideSupabase } from '@/src/database/$provideSupabase';
|
|
3
1
|
import { serializeError } from '@promptbook-local/utils';
|
|
4
|
-
import type { PostgrestSingleResponse, SupabaseClient } from '@supabase/supabase-js';
|
|
5
|
-
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
|
|
6
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { spaceTrim } from 'spacetrim';
|
|
7
4
|
import { assertsError } from '../../../../../../src/errors/assertsError';
|
|
8
|
-
import {
|
|
5
|
+
import { LimitReachedError } from '../../../../../../src/errors/LimitReachedError';
|
|
6
|
+
import { UnexpectedError } from '../../../../../../src/errors/UnexpectedError';
|
|
7
|
+
import { $getTableName } from '../../../database/$getTableName';
|
|
8
|
+
import { $provideSupabase } from '../../../database/$provideSupabase';
|
|
9
9
|
import type { AgentsServerDatabase } from '../../../database/schema';
|
|
10
|
+
import { $provideCdnForServer } from '../../../tools/$provideCdnForServer';
|
|
11
|
+
import { getSafeCdnPath } from '../../../utils/cdn/utils/getSafeCdnPath';
|
|
10
12
|
import { FILE_SECURITY_CHECKERS } from '../../../file-security-checkers';
|
|
13
|
+
import { getUserIdFromRequest } from '../../../utils/getUserIdFromRequest';
|
|
11
14
|
import { getMaxFileUploadSizeBytes } from '../../../utils/serverLimits';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Additional metadata accepted from the client-side upload helper.
|
|
15
|
-
*
|
|
16
|
-
* @private
|
|
17
|
-
*/
|
|
18
|
-
type UploadClientPayload = {
|
|
19
|
-
purpose?: unknown;
|
|
20
|
-
contentType?: unknown;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generic object used for safe JSON parsing in upload payloads.
|
|
25
|
-
*
|
|
26
|
-
* @private
|
|
27
|
-
*/
|
|
28
|
-
type JsonRecord = Record<string, unknown>;
|
|
15
|
+
import { validateMimeType } from '../../../utils/validators/validateMimeType';
|
|
29
16
|
|
|
30
17
|
/**
|
|
31
18
|
* Default purpose used for uploads when the client does not provide one.
|
|
@@ -42,40 +29,30 @@ const DEFAULT_UPLOAD_PURPOSE = 'GENERIC_UPLOAD';
|
|
|
42
29
|
const DEFAULT_UPLOAD_CONTENT_TYPE = 'application/octet-stream';
|
|
43
30
|
|
|
44
31
|
/**
|
|
45
|
-
*
|
|
32
|
+
* Regular expression for path segments that are safe to keep as public object keys.
|
|
46
33
|
*
|
|
47
34
|
* @private
|
|
48
35
|
*/
|
|
49
|
-
const
|
|
36
|
+
const SAFE_UPLOAD_PATH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~!$&'()*+,;=:@/-]*$/;
|
|
50
37
|
|
|
51
38
|
/**
|
|
52
|
-
*
|
|
39
|
+
* Parsed upload request.
|
|
53
40
|
*
|
|
54
41
|
* @private
|
|
55
42
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const parsed = JSON.parse(rawJson);
|
|
63
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return parsed as JsonRecord;
|
|
68
|
-
} catch {
|
|
69
|
-
return {};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
43
|
+
type ParsedUploadRequest = {
|
|
44
|
+
file: File;
|
|
45
|
+
pathname: string;
|
|
46
|
+
purpose: string;
|
|
47
|
+
contentType: string;
|
|
48
|
+
};
|
|
72
49
|
|
|
73
50
|
/**
|
|
74
51
|
* Normalizes upload purpose to a non-empty string.
|
|
75
52
|
*
|
|
76
53
|
* @private
|
|
77
54
|
*/
|
|
78
|
-
function normalizeUploadPurpose(value:
|
|
55
|
+
function normalizeUploadPurpose(value: FormDataEntryValue | null): string {
|
|
79
56
|
if (typeof value !== 'string') {
|
|
80
57
|
return DEFAULT_UPLOAD_PURPOSE;
|
|
81
58
|
}
|
|
@@ -89,197 +66,166 @@ function normalizeUploadPurpose(value: unknown): string {
|
|
|
89
66
|
*
|
|
90
67
|
* @private
|
|
91
68
|
*/
|
|
92
|
-
function normalizeUploadContentType(value:
|
|
93
|
-
|
|
69
|
+
function normalizeUploadContentType(value: FormDataEntryValue | null, fallbackContentType: string): string {
|
|
70
|
+
const candidate = typeof value === 'string' && value.trim() ? value.trim() : fallbackContentType;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
return validateMimeType(candidate || DEFAULT_UPLOAD_CONTENT_TYPE);
|
|
74
|
+
} catch {
|
|
94
75
|
return DEFAULT_UPLOAD_CONTENT_TYPE;
|
|
95
76
|
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolves and validates the storage key requested by the browser.
|
|
81
|
+
*
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
function resolveUploadPathname(value: FormDataEntryValue | null): string {
|
|
85
|
+
if (typeof value !== 'string') {
|
|
86
|
+
throw new UnexpectedError('Upload request is missing `pathname`.');
|
|
87
|
+
}
|
|
96
88
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
const pathname = value.trim().replace(/\\/g, '/').replace(/^\/+/, '');
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
pathname === '' ||
|
|
93
|
+
pathname.includes('/../') ||
|
|
94
|
+
pathname.startsWith('../') ||
|
|
95
|
+
pathname.endsWith('/..') ||
|
|
96
|
+
!SAFE_UPLOAD_PATH_PATTERN.test(pathname)
|
|
97
|
+
) {
|
|
98
|
+
throw new UnexpectedError(
|
|
99
|
+
spaceTrim(`
|
|
100
|
+
Upload request contains an invalid \`pathname\`.
|
|
101
|
+
|
|
102
|
+
The upload key must be a relative CDN path without parent-directory segments.
|
|
103
|
+
`),
|
|
104
|
+
);
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
return
|
|
107
|
+
return getSafeCdnPath({
|
|
108
|
+
pathname,
|
|
109
|
+
pathPrefix: process.env.NEXT_PUBLIC_CDN_PATH_PREFIX,
|
|
110
|
+
});
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
/**
|
|
106
|
-
*
|
|
114
|
+
* Parses the multipart request accepted by `/api/upload`.
|
|
107
115
|
*
|
|
108
116
|
* @private
|
|
109
117
|
*/
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
async function parseUploadRequest(request: NextRequest): Promise<ParsedUploadRequest> {
|
|
119
|
+
const formData = await request.formData();
|
|
120
|
+
const file = formData.get('file');
|
|
121
|
+
|
|
122
|
+
if (!(file instanceof File)) {
|
|
123
|
+
throw new UnexpectedError('Upload request is missing `file`.');
|
|
124
|
+
}
|
|
115
125
|
|
|
116
126
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
file,
|
|
128
|
+
pathname: resolveUploadPathname(formData.get('pathname')),
|
|
129
|
+
purpose: normalizeUploadPurpose(formData.get('purpose')),
|
|
130
|
+
contentType: normalizeUploadContentType(formData.get('contentType'), file.type),
|
|
119
131
|
};
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
/**
|
|
123
|
-
*
|
|
135
|
+
* Runs all configured file-security checkers against the uploaded public URL.
|
|
136
|
+
*
|
|
137
|
+
* @private
|
|
124
138
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const maxFileSize = await getMaxFileUploadSizeBytes();
|
|
143
|
-
|
|
144
|
-
// Generate the proper path with prefix
|
|
145
|
-
// Note: With client uploads, we use the original filename provided by the client
|
|
146
|
-
// The file will be stored at: {pathPrefix}/user/files/{filename}
|
|
147
|
-
const pathPrefix = process.env.NEXT_PUBLIC_CDN_PATH_PREFIX || '';
|
|
148
|
-
|
|
149
|
-
// Create a DB record at the start of the upload to track it
|
|
150
|
-
const uploadPurpose = purpose;
|
|
151
|
-
const {
|
|
152
|
-
data: insertedFile,
|
|
153
|
-
error: insertError,
|
|
154
|
-
}: PostgrestSingleResponse<Pick<AgentsServerDatabase['public']['Tables']['File']['Row'], 'id'>> =
|
|
155
|
-
await supabase
|
|
156
|
-
.from(await $getTableName('File'))
|
|
157
|
-
.insert({
|
|
158
|
-
userId: userId || null,
|
|
159
|
-
fileName: pathname,
|
|
160
|
-
fileSize: 0, // <- Will be updated when upload completes
|
|
161
|
-
fileType: contentType,
|
|
162
|
-
storageUrl: null, // <- To be updated on completion
|
|
163
|
-
shortUrl: null, // <- To be updated on completion
|
|
164
|
-
purpose: uploadPurpose,
|
|
165
|
-
status: 'UPLOADING',
|
|
166
|
-
})
|
|
167
|
-
.select('id')
|
|
168
|
-
.single();
|
|
139
|
+
async function checkUploadedFileSecurity(storageUrl: string): Promise<Record<string, unknown>> {
|
|
140
|
+
const securityResults: Record<string, unknown> = {};
|
|
141
|
+
|
|
142
|
+
for (const checkerId of Object.keys(FILE_SECURITY_CHECKERS)) {
|
|
143
|
+
try {
|
|
144
|
+
const checker = FILE_SECURITY_CHECKERS[checkerId]!;
|
|
145
|
+
securityResults[checkerId] = await checker.checkFile(storageUrl);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
securityResults[checkerId] = {
|
|
148
|
+
isSafe: false,
|
|
149
|
+
status: 'ERROR',
|
|
150
|
+
confidence: 0,
|
|
151
|
+
message: error instanceof Error ? error.message : String(error),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
169
155
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
156
|
+
return securityResults;
|
|
157
|
+
}
|
|
173
158
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Stores security results for the file row created by `TrackedFilesStorage`.
|
|
161
|
+
*
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
async function updateUploadedFileSecurityResult(
|
|
165
|
+
storageUrl: string,
|
|
166
|
+
securityResult: Record<string, unknown>,
|
|
167
|
+
): Promise<void> {
|
|
168
|
+
if (Object.keys(securityResult).length === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
179
171
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
uploadPath: pathname,
|
|
188
|
-
pathPrefix,
|
|
189
|
-
}),
|
|
190
|
-
};
|
|
191
|
-
},
|
|
192
|
-
onUploadCompleted: async ({ blob, tokenPayload }) => {
|
|
193
|
-
// !!!!
|
|
194
|
-
// ⚠️ IMPORTANT: This callback is a WEBHOOK called by Vercel's servers AFTER the upload completes
|
|
195
|
-
// - It runs in a DIFFERENT request context (not the original user request)
|
|
196
|
-
// - It WON'T work in local development (Vercel can't reach localhost)
|
|
197
|
-
// - All data must come from tokenPayload (userId, fileId, etc.)
|
|
198
|
-
// - Need to create a fresh supabase client here
|
|
199
|
-
console.info('🔼 Upload completed (webhook callback):', { blob, tokenPayload });
|
|
172
|
+
const supabase = $provideSupabase();
|
|
173
|
+
const securityResultForDatabase =
|
|
174
|
+
securityResult as AgentsServerDatabase['public']['Tables']['File']['Update']['securityResult'];
|
|
175
|
+
const { error } = await supabase
|
|
176
|
+
.from(await $getTableName('File'))
|
|
177
|
+
.update({ securityResult: securityResultForDatabase })
|
|
178
|
+
.eq('storageUrl', storageUrl);
|
|
200
179
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const tokenPurpose = normalizeUploadPurpose(payload.purpose);
|
|
206
|
-
const uploadPath = typeof payload.uploadPath === 'string' ? payload.uploadPath : null;
|
|
180
|
+
if (error) {
|
|
181
|
+
console.error('Failed to update uploaded file security result:', error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
207
184
|
|
|
208
|
-
|
|
209
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Handles file upload requests.
|
|
187
|
+
*/
|
|
188
|
+
export async function POST(request: NextRequest) {
|
|
189
|
+
try {
|
|
190
|
+
const { file, pathname, purpose, contentType } = await parseUploadRequest(request);
|
|
191
|
+
const fileBuffer = Buffer.from(await file.arrayBuffer());
|
|
192
|
+
const maxFileSize = await getMaxFileUploadSizeBytes();
|
|
193
|
+
|
|
194
|
+
if (fileBuffer.byteLength > maxFileSize) {
|
|
195
|
+
throw new LimitReachedError(
|
|
196
|
+
spaceTrim(`
|
|
197
|
+
Uploaded file \`${file.name}\` exceeds the configured upload limit.
|
|
198
|
+
|
|
199
|
+
Maximum supported size: **${maxFileSize} bytes**
|
|
200
|
+
`),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
210
203
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
securityResults as AgentsServerDatabase['public']['Tables']['File']['Update']['securityResult'];
|
|
215
|
-
for (const checkerId in FILE_SECURITY_CHECKERS) {
|
|
216
|
-
try {
|
|
217
|
-
const checker = FILE_SECURITY_CHECKERS[checkerId]!;
|
|
218
|
-
console.info(`🛡️ Checking file security with ${checker.title} (${blob.url})...`);
|
|
219
|
-
const result = await checker.checkFile(blob.url);
|
|
220
|
-
securityResults[checkerId] = result;
|
|
221
|
-
console.info(`🛡️ Security check result from ${checker.title}:`, result.status);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error(`🛡️ Security check failed for ${checkerId}:`, error);
|
|
224
|
-
securityResults[checkerId] = {
|
|
225
|
-
isSafe: false,
|
|
226
|
-
status: 'ERROR',
|
|
227
|
-
confidence: 0,
|
|
228
|
-
message: error instanceof Error ? error.message : String(error),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
}
|
|
204
|
+
const cdn = $provideCdnForServer();
|
|
205
|
+
const storageUrl = cdn.getItemUrl(pathname).href;
|
|
206
|
+
const userId = await getUserIdFromRequest(request);
|
|
232
207
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
fileType: blob.contentType,
|
|
241
|
-
storageUrl: blob.url,
|
|
242
|
-
// <- TODO: !!!! Split between storageUrl and shortUrl
|
|
243
|
-
purpose: tokenPurpose,
|
|
244
|
-
status: 'COMPLETED',
|
|
245
|
-
securityResult: securityResultForDatabase,
|
|
246
|
-
})
|
|
247
|
-
.eq('id', fileId);
|
|
208
|
+
await cdn.setItem(pathname, {
|
|
209
|
+
type: contentType,
|
|
210
|
+
data: fileBuffer,
|
|
211
|
+
purpose,
|
|
212
|
+
userId: userId || undefined,
|
|
213
|
+
fileSize: fileBuffer.byteLength,
|
|
214
|
+
});
|
|
248
215
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
console.info('🔼 File record updated successfully:', { fileId, shortUrl: blob.url });
|
|
253
|
-
}
|
|
254
|
-
} else if (uploadPath) {
|
|
255
|
-
// Fallback: Update by uploadPath if fileId is not available
|
|
256
|
-
const { error: updateError } = await supabase
|
|
257
|
-
.from(await $getTableName('File'))
|
|
258
|
-
.update({
|
|
259
|
-
fileSize: 0, // <- !!!!
|
|
260
|
-
fileType: blob.contentType,
|
|
261
|
-
storageUrl: blob.url,
|
|
262
|
-
status: 'COMPLETED',
|
|
263
|
-
securityResult: securityResultForDatabase,
|
|
264
|
-
})
|
|
265
|
-
.eq('fileName', uploadPath)
|
|
266
|
-
.eq('status', 'UPLOADING');
|
|
216
|
+
const securityResult = await checkUploadedFileSecurity(storageUrl);
|
|
217
|
+
await updateUploadedFileSecurityResult(storageUrl, securityResult);
|
|
267
218
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
console.error('🔼 Error in onUploadCompleted:', error);
|
|
274
|
-
}
|
|
275
|
-
},
|
|
219
|
+
return NextResponse.json({
|
|
220
|
+
url: storageUrl,
|
|
221
|
+
pathname,
|
|
222
|
+
contentType,
|
|
223
|
+
size: fileBuffer.byteLength,
|
|
276
224
|
});
|
|
277
|
-
|
|
278
|
-
return NextResponse.json(jsonResponse);
|
|
279
225
|
} catch (error) {
|
|
280
226
|
assertsError(error);
|
|
281
227
|
|
|
282
|
-
console.error('
|
|
228
|
+
console.error('Upload failed:', error);
|
|
283
229
|
|
|
284
230
|
return new Response(
|
|
285
231
|
JSON.stringify(
|
|
@@ -296,10 +242,3 @@ export async function POST(request: NextRequest) {
|
|
|
296
242
|
);
|
|
297
243
|
}
|
|
298
244
|
}
|
|
299
|
-
|
|
300
|
-
// TODO: !!!! Change uploaded URLs from `storageUrl` to `shortUrl`
|
|
301
|
-
// TODO: !!!! Record both `storageUrl` (actual storage location) and `shortUrl` in `File` table
|
|
302
|
-
// TODO: !!!! Record `purpose` in `File` table
|
|
303
|
-
// TODO: !!!! Record `userId` in `File` table
|
|
304
|
-
// TODO: !!!! Record all things into `File` table
|
|
305
|
-
// TODO: !!!! File type (mime type) of `.book` files should be `application/book` <- [🧠] !!!! Best mime type?!
|
|
@@ -34,7 +34,7 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ us
|
|
|
34
34
|
.from(await $getTableName('User'))
|
|
35
35
|
.update(updates)
|
|
36
36
|
.eq('username', usernameParam)
|
|
37
|
-
.select('
|
|
37
|
+
.select('*')
|
|
38
38
|
.single();
|
|
39
39
|
|
|
40
40
|
if (error) {
|
|
@@ -16,7 +16,7 @@ export async function GET() {
|
|
|
16
16
|
const supabase = $provideSupabaseForServer();
|
|
17
17
|
const { data: users, error } = await supabase
|
|
18
18
|
.from(await $getTableName('User'))
|
|
19
|
-
.select('
|
|
19
|
+
.select('*')
|
|
20
20
|
.order('username');
|
|
21
21
|
|
|
22
22
|
if (error) {
|
|
@@ -58,18 +58,18 @@ export async function POST(request: Request) {
|
|
|
58
58
|
createdAt: new Date().toISOString(),
|
|
59
59
|
updatedAt: new Date().toISOString(),
|
|
60
60
|
})
|
|
61
|
-
.select('
|
|
61
|
+
.select('*')
|
|
62
62
|
.single();
|
|
63
63
|
|
|
64
64
|
if (error) {
|
|
65
|
-
if (error.code === '23505') {
|
|
66
|
-
|
|
65
|
+
if (error.code === '23505') {
|
|
66
|
+
// unique_violation
|
|
67
|
+
return NextResponse.json({ error: 'Username already exists' }, { status: 409 });
|
|
67
68
|
}
|
|
68
69
|
throw error;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
return NextResponse.json(newUser);
|
|
72
|
-
|
|
73
73
|
} catch (error) {
|
|
74
74
|
console.error('Create user error:', error);
|
|
75
75
|
const passwordValidationMessage = getPasswordValidationMessage(error);
|
|
@@ -56,7 +56,7 @@ export default async function DashboardPage() {
|
|
|
56
56
|
const host = (await headers()).get('host') || 'unknown';
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
|
-
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
|
59
|
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900">
|
|
60
60
|
<div className="container mx-auto px-4 py-16">
|
|
61
61
|
<HomepagePrimarySections
|
|
62
62
|
agents={agents}
|
|
@@ -32,7 +32,7 @@ export default async function DocPage(props: DocPageProps) {
|
|
|
32
32
|
const { primary, aliases } = group;
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
|
35
|
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900">
|
|
36
36
|
<div className="container mx-auto px-4 py-16">
|
|
37
37
|
<DocsToolbar />
|
|
38
38
|
<PrintHeader title={primary.type} />
|
|
@@ -19,7 +19,7 @@ export default function DocsPage() {
|
|
|
19
19
|
const groupedCommitments = getVisibleCommitmentDefinitions();
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
|
22
|
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 dark:from-slate-950 dark:via-slate-950 dark:to-slate-900">
|
|
23
23
|
<div className="container mx-auto px-4 py-16">
|
|
24
24
|
<DocsToolbar />
|
|
25
25
|
<PrintHeader title="Full Documentation" />
|
|
@@ -640,6 +640,26 @@ html.dark .agent-chat-route-surface .agent-chat-sidebar-toggle.agent-chat-solid-
|
|
|
640
640
|
box-shadow: 0 20px 38px rgba(2, 6, 23, 0.6), inset 1px 0 0 rgba(191, 219, 254, 0.18);
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
+
html.dark .agent-chat-sidebar-item-active {
|
|
644
|
+
border-color: rgba(125, 211, 252, 0.52) !important;
|
|
645
|
+
background: linear-gradient(145deg, rgba(8, 47, 73, 0.88), rgba(15, 23, 42, 0.96)) !important;
|
|
646
|
+
color: #e0f2fe !important;
|
|
647
|
+
box-shadow: 0 14px 30px rgba(2, 6, 23, 0.4), inset 0 0 0 1px rgba(125, 211, 252, 0.16) !important;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
html.dark .agent-chat-sidebar-item-active .agent-chat-sidebar-item-preview-card {
|
|
651
|
+
border-color: rgba(125, 211, 252, 0.34) !important;
|
|
652
|
+
background: rgba(15, 23, 42, 0.82) !important;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
html.dark .agent-chat-sidebar-item-active .agent-chat-sidebar-item-title {
|
|
656
|
+
color: #f8fafc !important;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
html.dark .agent-chat-sidebar-item-active .agent-chat-sidebar-item-preview {
|
|
660
|
+
color: #bae6fd !important;
|
|
661
|
+
}
|
|
662
|
+
|
|
643
663
|
.agent-chat-route-surface .chat-scrollToBottom.chat-scrollToBottom + [role='status'] {
|
|
644
664
|
top: auto;
|
|
645
665
|
right: auto;
|
|
@@ -807,6 +827,36 @@ html.dark .bg-slate-100\/80 {
|
|
|
807
827
|
background-color: #111827 !important;
|
|
808
828
|
}
|
|
809
829
|
|
|
830
|
+
html.dark .bg-gray-200,
|
|
831
|
+
html.dark .bg-slate-200 {
|
|
832
|
+
background-color: rgba(30, 41, 59, 0.92) !important;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
html.dark .bg-gray-300,
|
|
836
|
+
html.dark .bg-slate-300 {
|
|
837
|
+
background-color: rgba(51, 65, 85, 0.94) !important;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
html.dark .bg-blue-50,
|
|
841
|
+
html.dark .bg-blue-100 {
|
|
842
|
+
background-color: rgba(30, 64, 175, 0.24) !important;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
html.dark .bg-amber-50,
|
|
846
|
+
html.dark .bg-amber-100 {
|
|
847
|
+
background-color: rgba(120, 53, 15, 0.28) !important;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
html.dark .bg-red-50,
|
|
851
|
+
html.dark .bg-red-100 {
|
|
852
|
+
background-color: rgba(127, 29, 29, 0.3) !important;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
html.dark .bg-emerald-50,
|
|
856
|
+
html.dark .bg-emerald-100 {
|
|
857
|
+
background-color: rgba(6, 78, 59, 0.28) !important;
|
|
858
|
+
}
|
|
859
|
+
|
|
810
860
|
html.dark .border-gray-100,
|
|
811
861
|
html.dark .border-gray-200,
|
|
812
862
|
html.dark .border-slate-100,
|
|
@@ -844,9 +894,34 @@ html.dark .text-slate-400 {
|
|
|
844
894
|
color: #64748b !important;
|
|
845
895
|
}
|
|
846
896
|
|
|
897
|
+
html.dark .text-blue-600,
|
|
898
|
+
html.dark .text-blue-700,
|
|
899
|
+
html.dark .hover\:text-blue-700:hover {
|
|
900
|
+
color: #93c5fd !important;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
html.dark .text-amber-700,
|
|
904
|
+
html.dark .text-amber-800,
|
|
905
|
+
html.dark .text-amber-900 {
|
|
906
|
+
color: #fcd34d !important;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
html.dark .text-red-600,
|
|
910
|
+
html.dark .text-red-700,
|
|
911
|
+
html.dark .text-red-800,
|
|
912
|
+
html.dark .text-red-900 {
|
|
913
|
+
color: #fca5a5 !important;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
html.dark .text-emerald-600,
|
|
917
|
+
html.dark .text-emerald-700 {
|
|
918
|
+
color: #86efac !important;
|
|
919
|
+
}
|
|
920
|
+
|
|
847
921
|
html.dark .hover\:bg-white:hover,
|
|
848
922
|
html.dark .hover\:bg-gray-50:hover,
|
|
849
923
|
html.dark .hover\:bg-gray-100:hover,
|
|
924
|
+
html.dark .hover\:bg-gray-300:hover,
|
|
850
925
|
html.dark .hover\:bg-slate-50:hover,
|
|
851
926
|
html.dark .hover\:bg-slate-100:hover {
|
|
852
927
|
background-color: #0f172a !important;
|
|
@@ -864,6 +939,31 @@ html.dark .hover\:border-slate-400:hover {
|
|
|
864
939
|
border-color: rgba(125, 211, 252, 0.45) !important;
|
|
865
940
|
}
|
|
866
941
|
|
|
942
|
+
html.dark input:not([type='checkbox']):not([type='radio']):not([type='range']),
|
|
943
|
+
html.dark textarea,
|
|
944
|
+
html.dark select {
|
|
945
|
+
background-color: #0f172a;
|
|
946
|
+
color: #e2e8f0;
|
|
947
|
+
border-color: rgba(71, 85, 105, 0.92);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
html.dark input:not([type='checkbox']):not([type='radio']):not([type='range'])::placeholder,
|
|
951
|
+
html.dark textarea::placeholder {
|
|
952
|
+
color: #94a3b8;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
html.dark input:not([type='checkbox']):not([type='radio']):not([type='range']):disabled,
|
|
956
|
+
html.dark textarea:disabled,
|
|
957
|
+
html.dark select:disabled {
|
|
958
|
+
background-color: #111827;
|
|
959
|
+
color: #64748b;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
html.dark option {
|
|
963
|
+
background-color: #0f172a;
|
|
964
|
+
color: #e2e8f0;
|
|
965
|
+
}
|
|
966
|
+
|
|
867
967
|
html.dark .prose {
|
|
868
968
|
color: #cbd5e1;
|
|
869
969
|
}
|
|
@@ -46,6 +46,10 @@ import {
|
|
|
46
46
|
CONTROL_PANEL_OPTION_AVAILABILITY_METADATA_KEYS,
|
|
47
47
|
getControlPanelOptionAvailability,
|
|
48
48
|
} from '../utils/getControlPanelOptionAvailability';
|
|
49
|
+
import {
|
|
50
|
+
SHIBBOLETH_AUTHENTICATION_METADATA_KEYS,
|
|
51
|
+
resolveShibbolethAuthenticationMenuStatus,
|
|
52
|
+
} from '../constants/shibbolethAuth';
|
|
49
53
|
import '@prisma/studio-core/ui/index.css';
|
|
50
54
|
import './globals.css';
|
|
51
55
|
|
|
@@ -257,6 +261,7 @@ export default async function RootLayout({
|
|
|
257
261
|
DEFAULT_THEME_METADATA_KEY,
|
|
258
262
|
SERVER_LANGUAGE_METADATA_KEY,
|
|
259
263
|
IS_SERVER_LANGUAGE_ENFORCED_METADATA_KEY,
|
|
264
|
+
...SHIBBOLETH_AUTHENTICATION_METADATA_KEYS,
|
|
260
265
|
...CONTROL_PANEL_OPTION_AVAILABILITY_METADATA_KEYS,
|
|
261
266
|
]);
|
|
262
267
|
const currentUserPromise = getCurrentUser();
|
|
@@ -380,6 +385,7 @@ export default async function RootLayout({
|
|
|
380
385
|
metadata: layoutMetadata,
|
|
381
386
|
isPushNotificationsConfigured: Boolean(webPushPublicKey),
|
|
382
387
|
});
|
|
388
|
+
const shibbolethAuthenticationStatus = resolveShibbolethAuthenticationMenuStatus(layoutMetadata);
|
|
383
389
|
const themeModeBootstrapScript = createThemeModeBootstrapScript(defaultThemeMode);
|
|
384
390
|
|
|
385
391
|
return (
|
|
@@ -414,6 +420,7 @@ export default async function RootLayout({
|
|
|
414
420
|
defaultIsNotificationsOn={defaultIsNotificationsOn}
|
|
415
421
|
isExperimental={isExperimental}
|
|
416
422
|
feedbackMode={feedbackMode}
|
|
423
|
+
shibbolethAuthenticationStatus={shibbolethAuthenticationStatus}
|
|
417
424
|
isExperimentalPwaAppEnabled={isExperimentalPwaAppEnabled}
|
|
418
425
|
controlPanelOptionAvailability={controlPanelOptionAvailability}
|
|
419
426
|
defaultServerLanguage={serverLanguage}
|