mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat: enhance static asset handling and improve request processing
- Refactored StaticAssetService to remove unnecessary request options, simplifying the handleRequest method. - Introduced resolveRequestHost method to streamline host resolution logic. - Updated StaticBaseController and StaticShareController to align with the new request handling approach. - Added I18nProvider to tenant missing and restricted entry points for improved localization support. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -217,3 +217,7 @@ This project contains multiple web applications with distinct design systems. Fo
|
||||
|
||||
- **`apps/web`**: Contains the "Glassmorphic Depth Design System" for the main user-facing photo gallery. See `apps/web/AGENTS.md` for details.
|
||||
- **`be/apps/dashboard`**: Contains guidelines for the functional, data-driven UI of the administration panel. See `be/apps/dashboard/AGENTS.md` for details.
|
||||
|
||||
## IMPORTANT
|
||||
|
||||
Avoid feature gates/flags and any backwards compability changes - since our app is still unreleased" is really helpful.
|
||||
@@ -5,7 +5,7 @@ import { extname, isAbsolute, join, normalize, relative, resolve } from 'node:pa
|
||||
import { Readable } from 'node:stream'
|
||||
|
||||
import type { PrettyLogger } from '@afilmory/framework'
|
||||
import { createLogger } from '@afilmory/framework'
|
||||
import { createLogger, HttpContext } from '@afilmory/framework'
|
||||
import { DOMParser } from 'linkedom'
|
||||
import { lookup as lookupMimeType } from 'mime-types'
|
||||
|
||||
@@ -34,10 +34,6 @@ export interface StaticAssetServiceOptions {
|
||||
staticAssetHostResolver?: (requestHost?: string | null) => Promise<string | null>
|
||||
}
|
||||
|
||||
export interface StaticAssetRequestOptions {
|
||||
requestHost?: string | null
|
||||
}
|
||||
|
||||
export interface ResolvedStaticAsset {
|
||||
absolutePath: string
|
||||
relativePath: string
|
||||
@@ -61,11 +57,7 @@ export abstract class StaticAssetService {
|
||||
this.staticAssetHostResolver = options.staticAssetHostResolver
|
||||
}
|
||||
|
||||
async handleRequest(
|
||||
fullPath: string,
|
||||
headOnly: boolean,
|
||||
options?: StaticAssetRequestOptions,
|
||||
): Promise<Response | null> {
|
||||
async handleRequest(fullPath: string, headOnly: boolean): Promise<Response | null> {
|
||||
const staticRoot = await this.resolveStaticRoot()
|
||||
if (!staticRoot) {
|
||||
return null
|
||||
@@ -78,7 +70,7 @@ export abstract class StaticAssetService {
|
||||
return null
|
||||
}
|
||||
|
||||
return await this.createResponse(target, headOnly, options)
|
||||
return await this.createResponse(target, headOnly)
|
||||
}
|
||||
|
||||
protected get routeSegment(): string {
|
||||
@@ -364,13 +356,9 @@ export abstract class StaticAssetService {
|
||||
return relativePath !== '' && !relativePath.startsWith('..') && !isAbsolute(relativePath)
|
||||
}
|
||||
|
||||
private async createResponse(
|
||||
file: ResolvedStaticAsset,
|
||||
headOnly: boolean,
|
||||
options?: StaticAssetRequestOptions,
|
||||
): Promise<Response> {
|
||||
private async createResponse(file: ResolvedStaticAsset, headOnly: boolean): Promise<Response> {
|
||||
if (this.isHtml(file.relativePath)) {
|
||||
return await this.createHtmlResponse(file, headOnly, options)
|
||||
return await this.createHtmlResponse(file, headOnly)
|
||||
}
|
||||
|
||||
const mimeType = lookupMimeType(file.absolutePath) || 'application/octet-stream'
|
||||
@@ -390,13 +378,9 @@ export abstract class StaticAssetService {
|
||||
return new Response(body, { headers, status: 200 })
|
||||
}
|
||||
|
||||
private async createHtmlResponse(
|
||||
file: ResolvedStaticAsset,
|
||||
headOnly: boolean,
|
||||
options?: StaticAssetRequestOptions,
|
||||
): Promise<Response> {
|
||||
private async createHtmlResponse(file: ResolvedStaticAsset, headOnly: boolean): Promise<Response> {
|
||||
const html = await readFile(file.absolutePath, 'utf-8')
|
||||
const transformed = await this.transformIndexHtml(html, file, options)
|
||||
const transformed = await this.transformIndexHtml(html, file)
|
||||
const headers = new Headers()
|
||||
headers.set('content-type', 'text/html; charset=utf-8')
|
||||
headers.set('content-length', `${Buffer.byteLength(transformed, 'utf-8')}`)
|
||||
@@ -410,16 +394,12 @@ export abstract class StaticAssetService {
|
||||
return new Response(transformed, { headers, status: 200 })
|
||||
}
|
||||
|
||||
private async transformIndexHtml(
|
||||
html: string,
|
||||
file: ResolvedStaticAsset,
|
||||
options?: StaticAssetRequestOptions,
|
||||
): Promise<string> {
|
||||
private async transformIndexHtml(html: string, file: ResolvedStaticAsset): Promise<string> {
|
||||
try {
|
||||
const document = DOM_PARSER.parseFromString(html, 'text/html') as unknown as StaticAssetDocument
|
||||
await this.decorateDocument(document, file)
|
||||
if (this.shouldRewriteAssetReferences(file)) {
|
||||
const staticAssetHost = await this.getStaticAssetHost(options?.requestHost)
|
||||
const staticAssetHost = await this.getStaticAssetHost(this.resolveRequestHost())
|
||||
this.rewriteStaticAssetReferences(document, staticAssetHost)
|
||||
}
|
||||
return document.documentElement.outerHTML
|
||||
@@ -458,6 +438,29 @@ export abstract class StaticAssetService {
|
||||
return requestHost.trim().toLowerCase()
|
||||
}
|
||||
|
||||
private resolveRequestHost(): string | null {
|
||||
const context = HttpContext.getValue('hono')
|
||||
if (!context) {
|
||||
return null
|
||||
}
|
||||
const forwardedHost = context.req.header('x-forwarded-host')?.trim()
|
||||
if (forwardedHost) {
|
||||
return forwardedHost
|
||||
}
|
||||
|
||||
const host = context.req.header('host')?.trim()
|
||||
if (host) {
|
||||
return host
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(context.req.url)
|
||||
return url.hostname
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private shouldTreatAsImmutable(relativePath: string): boolean {
|
||||
if (this.isHtml(relativePath)) {
|
||||
return false
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Context } from 'hono'
|
||||
|
||||
import type { StaticAssetService } from './static-asset.service'
|
||||
import { StaticControllerUtils } from './static-controller.utils'
|
||||
import type { StaticDashboardService } from './static-dashboard.service'
|
||||
import { STATIC_DASHBOARD_BASENAME } from './static-dashboard.service'
|
||||
import type { StaticWebService } from './static-web.service'
|
||||
@@ -20,9 +19,7 @@ export abstract class StaticBaseController {
|
||||
protected async serve(context: Context, service: StaticAssetService, headOnly: boolean): Promise<Response> {
|
||||
const pathname = context.req.path
|
||||
const normalizedPath = this.normalizeRequestPath(pathname, service)
|
||||
const response = await service.handleRequest(normalizedPath, headOnly, {
|
||||
requestHost: StaticControllerUtils.resolveRequestHost(context),
|
||||
})
|
||||
const response = await service.handleRequest(normalizedPath, headOnly)
|
||||
|
||||
if (response) {
|
||||
return response
|
||||
|
||||
@@ -2,7 +2,6 @@ import { isTenantSlugReserved } from '@afilmory/utils'
|
||||
import { BizException, ErrorCode } from 'core/errors'
|
||||
import { ROOT_TENANT_SLUG } from 'core/modules/platform/tenant/tenant.constants'
|
||||
import { getTenantContext, isPlaceholderTenantContext } from 'core/modules/platform/tenant/tenant.context'
|
||||
import type { Context } from 'hono'
|
||||
|
||||
import type { StaticDashboardService } from './static-dashboard.service'
|
||||
import { STATIC_DASHBOARD_BASENAME } from './static-dashboard.service'
|
||||
@@ -11,25 +10,6 @@ const TENANT_MISSING_ENTRY_PATH = `${STATIC_DASHBOARD_BASENAME}/tenant-missing.h
|
||||
const TENANT_RESTRICTED_ENTRY_PATH = `${STATIC_DASHBOARD_BASENAME}/tenant-restricted.html`
|
||||
|
||||
export const StaticControllerUtils = {
|
||||
resolveRequestHost(context: Context): string | null {
|
||||
const forwardedHost = context.req.header('x-forwarded-host')?.trim()
|
||||
if (forwardedHost) {
|
||||
return forwardedHost
|
||||
}
|
||||
|
||||
const host = context.req.header('host')?.trim()
|
||||
if (host) {
|
||||
return host
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(context.req.url)
|
||||
return url.host
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
cloneResponseWithStatus(response: Response, status: number): Response {
|
||||
const headers = new Headers(response.headers)
|
||||
return new Response(response.body, {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { z } from 'zod'
|
||||
|
||||
import { StaticControllerUtils } from './static-controller.utils'
|
||||
import { StaticDashboardService } from './static-dashboard.service'
|
||||
import { STATIC_SHARE_ENTRY_PATH,StaticShareService } from './static-share.service'
|
||||
import { STATIC_SHARE_ENTRY_PATH, StaticShareService } from './static-share.service'
|
||||
|
||||
const shareQuerySchema = z.object({
|
||||
id: z.string().min(1, 'Photo ID(s) required'),
|
||||
@@ -28,9 +28,7 @@ export class StaticShareController {
|
||||
return await StaticControllerUtils.renderTenantMissingPage(this.staticDashboardService)
|
||||
}
|
||||
|
||||
const response = await this.staticShareService.handleRequest(STATIC_SHARE_ENTRY_PATH, false, {
|
||||
requestHost: StaticControllerUtils.resolveRequestHost(context),
|
||||
})
|
||||
const response = await this.staticShareService.handleRequest(STATIC_SHARE_ENTRY_PATH, false)
|
||||
|
||||
if (!response || response.status === 404) {
|
||||
throw new BizException(ErrorCode.COMMON_NOT_FOUND, {
|
||||
|
||||
@@ -2,6 +2,8 @@ import '../styles/index.css'
|
||||
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { I18nProvider } from '~/providers/i18n-provider'
|
||||
|
||||
import { TenantMissingStandalone } from '../modules/welcome/components/TenantMissingStandalone'
|
||||
|
||||
const root = document.querySelector('#root')
|
||||
@@ -10,4 +12,8 @@ if (!root) {
|
||||
throw new Error('Root element not found for tenant missing entry.')
|
||||
}
|
||||
|
||||
createRoot(root).render(<TenantMissingStandalone />)
|
||||
createRoot(root).render(
|
||||
<I18nProvider>
|
||||
<TenantMissingStandalone />
|
||||
</I18nProvider>,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ import '../styles/index.css'
|
||||
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { I18nProvider } from '~/providers/i18n-provider'
|
||||
|
||||
import { TenantRestrictedStandalone } from '../modules/welcome/components/TenantRestrictedStandalone'
|
||||
|
||||
const root = document.querySelector('#root')
|
||||
@@ -10,4 +12,8 @@ if (!root) {
|
||||
throw new Error('Root element not found for tenant restricted entry.')
|
||||
}
|
||||
|
||||
createRoot(root).render(<TenantRestrictedStandalone />)
|
||||
createRoot(root).render(
|
||||
<I18nProvider>
|
||||
<TenantRestrictedStandalone />
|
||||
</I18nProvider>,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user