Skip to content

Commit 33cbbfe

Browse files
committed
refactor(errors): simplify BizException and BizErrorResponse interfaces
- Removed generic type parameters from BizExceptionOptions and BizErrorResponse interfaces for clarity. - Updated BizException class to directly use the message property from options, enhancing readability. - Adjusted toResponse method in BizException to streamline response structure by removing unnecessary details handling. fix(controller): update error handling in StorageSettingController - Changed error code in ensureKeyAllowed method to use COMMON_BAD_REQUEST for better accuracy in error reporting. fix(dashboard): update HTML files to use link tag for favicon - Replaced meta tag with link tag for favicon in index.html, tenant-missing.html, and tenant-restricted.html for improved compatibility. Signed-off-by: Innei <tukon479@gmail.com>
1 parent 76d60fd commit 33cbbfe

File tree

9 files changed

+224
-123
lines changed

9 files changed

+224
-123
lines changed
Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
11
import type { ErrorCode, ErrorDescriptor } from './error-codes'
22
import { ERROR_CODE_DESCRIPTORS } from './error-codes'
33

4-
export interface BizExceptionOptions<TDetails = unknown> {
4+
export interface BizExceptionOptions {
55
message?: string
6-
details?: TDetails
76
cause?: unknown
87
}
98

10-
export interface BizErrorResponse<TDetails = unknown> {
9+
export interface BizErrorResponse {
10+
ok: boolean
1111
code: ErrorCode
1212
message: string
13-
details?: TDetails
1413
}
1514

16-
export class BizException<TDetails = unknown> extends Error {
15+
export class BizException extends Error {
1716
readonly code: ErrorCode
18-
readonly details?: TDetails
17+
1918
private readonly httpStatus: number
2019

21-
constructor(code: ErrorCode, options?: BizExceptionOptions<TDetails>) {
20+
readonly message: string
21+
constructor(code: ErrorCode, options?: BizExceptionOptions) {
2222
const descriptor: ErrorDescriptor = ERROR_CODE_DESCRIPTORS[code]
2323
super(options?.message ?? descriptor.message, options?.cause ? { cause: options.cause } : undefined)
2424
this.name = 'BizException'
2525
this.code = code
26-
this.details = options?.details
2726
this.httpStatus = descriptor.httpStatus
27+
this.message = options?.message ?? descriptor.message
2828
}
2929

3030
getHttpStatus(): number {
3131
return this.httpStatus
3232
}
3333

34-
toResponse(): BizErrorResponse<TDetails> {
35-
const response: BizErrorResponse<TDetails> = {
34+
toResponse(): BizErrorResponse {
35+
return {
36+
ok: false,
3637
code: this.code,
3738
message: this.message,
3839
}
39-
40-
if (this.details !== undefined) {
41-
response.details = this.details
42-
}
43-
44-
return response
4540
}
4641
}

be/apps/core/src/guards/roles.guard.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,9 @@ export class RolesGuard implements CanActivate {
3737
const userMask = roleBitWithInheritance(roleNameToBit(userRoleName))
3838
const hasRole = (requiredMask & userMask) !== 0
3939
if (!hasRole) {
40-
this.log.warn(
41-
`Denied access: user ${(authContext.user as { id?: string }).id ?? 'unknown'} role=${userRoleName ?? 'n/a'} lacks permission mask=${requiredMask} on ${method} ${path}`,
42-
)
43-
throw new BizException(ErrorCode.AUTH_FORBIDDEN)
40+
const message = `Insufficient permissions for user ${(authContext.user as { id?: string }).id ?? 'unknown'} role=${userRoleName ?? 'n/a'} lacks permission mask=${requiredMask} on ${method} ${path}`
41+
this.log.warn(message)
42+
throw new BizException(ErrorCode.AUTH_FORBIDDEN, { message })
4443
}
4544

4645
return true

be/apps/core/src/modules/configuration/storage-setting/storage-setting.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const STORAGE_SETTING_KEYS = ['builder.storage.providers', 'builder.storage.acti
1212
type StorageSettingKey = (typeof STORAGE_SETTING_KEYS)[number]
1313

1414
@Controller('storage/settings')
15-
@Roles('superadmin')
15+
@Roles('admin')
1616
export class StorageSettingController {
1717
constructor(private readonly storageSettingService: StorageSettingService) {}
1818

@@ -76,7 +76,7 @@ export class StorageSettingController {
7676

7777
private ensureKeyAllowed(key: string) {
7878
if (!key.startsWith('builder.storage.')) {
79-
throw new BizException(ErrorCode.AUTH_FORBIDDEN, { message: 'Only storage settings are available' })
79+
throw new BizException(ErrorCode.COMMON_BAD_REQUEST, { message: 'Only storage settings are available' })
8080
}
8181
}
8282
}

be/apps/core/src/modules/infrastructure/static-web/static-asset.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const DEFAULT_ASSET_LINK_RELS = new Set([
1515
'preload',
1616
'prefetch',
1717
'icon',
18+
1819
'shortcut icon',
1920
'apple-touch-icon',
2021
'manifest',
@@ -196,7 +197,7 @@ export abstract class StaticAssetService {
196197
private extractRelativePath(fullPath: string): string {
197198
const index = fullPath.indexOf(this.routeSegment)
198199
if (index === -1) {
199-
return this.stripLeadingSlashes(fullPath)
200+
return ''
200201
}
201202

202203
const sliceStart = index + this.routeSegment.length

be/apps/dashboard/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
<meta charset="UTF-8" />
55

66
<meta name="description" content="Afilmory Dashboard for managing your gallery" />
7-
<meta name="favicon" content="/favicon.ico" />
7+
8+
<link rel="shortcut icon" href="/favicon.ico" />
89
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
910
<title>Afilmory Dashboard</title>
1011
</head>

be/apps/dashboard/src/modules/photos/components/PhotoPage.tsx

Lines changed: 131 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ReactNode } from 'react'
12
import { useCallback, useEffect, useMemo, useState } from 'react'
23
import { useSearchParams } from 'react-router'
34
import { toast } from 'sonner'
@@ -24,14 +25,13 @@ import type {
2425
PhotoSyncResolution,
2526
PhotoSyncResult,
2627
} from '../types'
27-
import { PhotoLibraryActionBar } from './library/PhotoLibraryActionBar'
2828
import { PhotoLibraryGrid } from './library/PhotoLibraryGrid'
29-
import { PhotoSyncActions } from './sync/PhotoSyncActions'
29+
import { PhotoPageActions } from './PhotoPageActions'
3030
import { PhotoSyncConflictsPanel } from './sync/PhotoSyncConflictsPanel'
3131
import { PhotoSyncProgressPanel } from './sync/PhotoSyncProgressPanel'
3232
import { PhotoSyncResultPanel } from './sync/PhotoSyncResultPanel'
3333

34-
type PhotoPageTab = 'sync' | 'library' | 'storage'
34+
export type PhotoPageTab = 'sync' | 'library' | 'storage'
3535

3636
const BATCH_RESOLVING_ID = '__batch__'
3737

@@ -207,31 +207,59 @@ export function PhotoPage() {
207207
)
208208
}, [])
209209

210-
const handleDeleteAssets = async (ids: string[]) => {
211-
if (ids.length === 0) return
212-
try {
213-
await deleteMutation.mutateAsync(ids)
214-
toast.success(`已删除 ${ids.length} 个资源`)
215-
setSelectedIds((prev) => prev.filter((item) => !ids.includes(item)))
216-
void listQuery.refetch()
217-
} catch (error) {
218-
const message = error instanceof Error ? error.message : '删除失败,请稍后重试。'
219-
toast.error('删除失败', { description: message })
220-
}
221-
}
210+
const handleDeleteAssets = useCallback(
211+
async (ids: string[]) => {
212+
if (ids.length === 0) return
213+
try {
214+
await deleteMutation.mutateAsync(ids)
215+
toast.success(`已删除 ${ids.length} 个资源`)
216+
setSelectedIds((prev) => prev.filter((item) => !ids.includes(item)))
217+
void listQuery.refetch()
218+
} catch (error) {
219+
const message = error instanceof Error ? error.message : '删除失败,请稍后重试。'
220+
toast.error('删除失败', { description: message })
221+
}
222+
},
223+
[deleteMutation, listQuery, setSelectedIds],
224+
)
222225

223-
const handleUploadAssets = async (files: FileList) => {
224-
const fileArray = Array.from(files)
225-
if (fileArray.length === 0) return
226-
try {
227-
await uploadMutation.mutateAsync(fileArray)
228-
toast.success(`成功上传 ${fileArray.length} 张图片`)
226+
const handleUploadAssets = useCallback(
227+
async (files: FileList) => {
228+
const fileArray = Array.from(files)
229+
if (fileArray.length === 0) return
230+
try {
231+
await uploadMutation.mutateAsync(fileArray)
232+
toast.success(`成功上传 ${fileArray.length} 张图片`)
233+
void listQuery.refetch()
234+
} catch (error) {
235+
const message = error instanceof Error ? error.message : '上传失败,请稍后重试。'
236+
toast.error('上传失败', { description: message })
237+
}
238+
},
239+
[listQuery, uploadMutation],
240+
)
241+
242+
const handleSyncCompleted = useCallback(
243+
(data: PhotoSyncResult, context: { dryRun: boolean }) => {
244+
setResult(data)
245+
setLastWasDryRun(context.dryRun)
246+
setSyncProgress(null)
247+
void summaryQuery.refetch()
229248
void listQuery.refetch()
230-
} catch (error) {
231-
const message = error instanceof Error ? error.message : '上传失败,请稍后重试。'
232-
toast.error('上传失败', { description: message })
233-
}
234-
}
249+
},
250+
[listQuery, summaryQuery],
251+
)
252+
253+
const handleDeleteSelected = useCallback(() => {
254+
void handleDeleteAssets(selectedIds)
255+
}, [handleDeleteAssets, selectedIds])
256+
257+
const handleDeleteSingle = useCallback(
258+
(asset: PhotoAssetListItem) => {
259+
void handleDeleteAssets([asset.id])
260+
},
261+
[handleDeleteAssets],
262+
)
235263

236264
const handleResolveConflict = useCallback(
237265
async (conflict: PhotoSyncConflict, strategy: PhotoSyncResolution) => {
@@ -325,10 +353,6 @@ export function PhotoPage() {
325353
}
326354
}
327355

328-
const handleDeleteSingle = (asset: PhotoAssetListItem) => {
329-
void handleDeleteAssets([asset.id])
330-
}
331-
332356
const handleTabChange = (tab: PhotoPageTab) => {
333357
setActiveTab(tab)
334358
const next = new URLSearchParams(searchParams.toString())
@@ -347,36 +371,84 @@ export function PhotoPage() {
347371
const showConflictsPanel =
348372
conflictsQuery.isLoading || conflictsQuery.isFetching || (conflictsQuery.data?.length ?? 0) > 0
349373

374+
let tabContent: ReactNode | null = null
375+
376+
switch (activeTab) {
377+
case 'storage': {
378+
tabContent = <StorageProvidersManager />
379+
break
380+
}
381+
case 'sync': {
382+
let progressPanel: ReactNode | null = null
383+
if (syncProgress) {
384+
progressPanel = <PhotoSyncProgressPanel progress={syncProgress} />
385+
}
386+
387+
let conflictsPanel: ReactNode | null = null
388+
if (showConflictsPanel) {
389+
conflictsPanel = (
390+
<PhotoSyncConflictsPanel
391+
conflicts={conflictsQuery.data}
392+
isLoading={conflictsQuery.isLoading || conflictsQuery.isFetching}
393+
resolvingId={resolvingConflictId}
394+
isBatchResolving={resolvingConflictId === BATCH_RESOLVING_ID}
395+
onResolve={handleResolveConflict}
396+
onResolveBatch={handleResolveConflictsBatch}
397+
onRequestStorageUrl={getPhotoStorageUrl}
398+
/>
399+
)
400+
}
401+
402+
tabContent = (
403+
<>
404+
{progressPanel}
405+
<div className="space-y-6">
406+
{conflictsPanel}
407+
<PhotoSyncResultPanel
408+
result={result}
409+
lastWasDryRun={lastWasDryRun}
410+
baselineSummary={summaryQuery.data}
411+
isSummaryLoading={summaryQuery.isLoading}
412+
onRequestStorageUrl={getPhotoStorageUrl}
413+
/>
414+
</div>
415+
</>
416+
)
417+
break
418+
}
419+
case 'library': {
420+
tabContent = (
421+
<PhotoLibraryGrid
422+
assets={listQuery.data}
423+
isLoading={isListLoading}
424+
selectedIds={selectedSet}
425+
onToggleSelect={handleToggleSelect}
426+
onOpenAsset={handleOpenAsset}
427+
onDeleteAsset={handleDeleteSingle}
428+
isDeleting={deleteMutation.isPending}
429+
/>
430+
)
431+
break
432+
}
433+
default: {
434+
tabContent = null
435+
}
436+
}
437+
350438
return (
351439
<MainPageLayout title="照片库" description="在此同步和管理服务器中的照片资产。">
352-
{activeTab !== 'storage' ? (
353-
<MainPageLayout.Actions>
354-
{activeTab === 'sync' ? (
355-
<PhotoSyncActions
356-
onCompleted={(data, context) => {
357-
setResult(data)
358-
setLastWasDryRun(context.dryRun)
359-
setSyncProgress(null)
360-
void summaryQuery.refetch()
361-
void listQuery.refetch()
362-
}}
363-
onProgress={handleProgressEvent}
364-
onError={handleSyncError}
365-
/>
366-
) : (
367-
<PhotoLibraryActionBar
368-
selectionCount={selectedIds.length}
369-
isUploading={uploadMutation.isPending}
370-
isDeleting={deleteMutation.isPending}
371-
onUpload={handleUploadAssets}
372-
onDeleteSelected={() => {
373-
void handleDeleteAssets(selectedIds)
374-
}}
375-
onClearSelection={handleClearSelection}
376-
/>
377-
)}
378-
</MainPageLayout.Actions>
379-
) : null}
440+
<PhotoPageActions
441+
activeTab={activeTab}
442+
selectionCount={selectedIds.length}
443+
isUploading={uploadMutation.isPending}
444+
isDeleting={deleteMutation.isPending}
445+
onUpload={handleUploadAssets}
446+
onDeleteSelected={handleDeleteSelected}
447+
onClearSelection={handleClearSelection}
448+
onSyncCompleted={handleSyncCompleted}
449+
onSyncProgress={handleProgressEvent}
450+
onSyncError={handleSyncError}
451+
/>
380452

381453
<div className="space-y-6">
382454
<PageTabs
@@ -389,46 +461,7 @@ export function PhotoPage() {
389461
]}
390462
/>
391463

392-
{activeTab === 'storage' ? (
393-
<StorageProvidersManager />
394-
) : (
395-
<>
396-
{activeTab === 'sync' && syncProgress ? <PhotoSyncProgressPanel progress={syncProgress} /> : null}
397-
398-
{activeTab === 'sync' ? (
399-
<div className="space-y-6">
400-
{showConflictsPanel ? (
401-
<PhotoSyncConflictsPanel
402-
conflicts={conflictsQuery.data}
403-
isLoading={conflictsQuery.isLoading || conflictsQuery.isFetching}
404-
resolvingId={resolvingConflictId}
405-
isBatchResolving={resolvingConflictId === BATCH_RESOLVING_ID}
406-
onResolve={handleResolveConflict}
407-
onResolveBatch={handleResolveConflictsBatch}
408-
onRequestStorageUrl={getPhotoStorageUrl}
409-
/>
410-
) : null}
411-
<PhotoSyncResultPanel
412-
result={result}
413-
lastWasDryRun={lastWasDryRun}
414-
baselineSummary={summaryQuery.data}
415-
isSummaryLoading={summaryQuery.isLoading}
416-
onRequestStorageUrl={getPhotoStorageUrl}
417-
/>
418-
</div>
419-
) : (
420-
<PhotoLibraryGrid
421-
assets={listQuery.data}
422-
isLoading={isListLoading}
423-
selectedIds={selectedSet}
424-
onToggleSelect={handleToggleSelect}
425-
onOpenAsset={handleOpenAsset}
426-
onDeleteAsset={handleDeleteSingle}
427-
isDeleting={deleteMutation.isPending}
428-
/>
429-
)}
430-
</>
431-
)}
464+
{tabContent}
432465
</div>
433466
</MainPageLayout>
434467
)

0 commit comments

Comments
 (0)