mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-21 13:20:33 +08:00
* feat: public share landing page /s/:token + Workers SSR OG meta - Add SPA route `/s/:token` (TanStack Router, outside _authenticated) - Implement share components: ShareLanding, FilePreview, FolderBrowser, PasswordPrompt, SaveToDriveDialog, ShareError - File preview: image/video/audio/PDF via object URL fetch; fallback for other types with download CTA - Folder browser: breadcrumb navigation + children table with download - Password gate: POST /api/share/:token/verify with error feedback - Save to drive: workspace + folder picker, quota/password/gone error handling - Workers SSR: inject OG meta tags for /s/:token requests (title, description, image, twitter:card); fetch share metadata via /api/share/:token - Add /s/* to wrangler.toml run_worker_first for SSR routing - Add zValidator to /:token/children endpoint for typed RPC query params - Export ShareApiRoute type from server/app.ts; add RPC clients in rpc.ts - Add share.* i18n keys (en + zh) - 9 new unit tests covering error code derivation, escaping, i18n coverage Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c * test: add coverage for share public API wrappers and path traversal guard - api.test.ts: add unit tests for getShareLanding, verifySharePassword, getShareChildren, saveShareToDrive (success + all error paths) - share-public.integration.test.ts: add path traversal guard test (.. in path param returns 400 Invalid path) Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c * test: cover explicit page/pageSize params in children endpoint Add integration test for GET /api/share/:token/children with explicit page and pageSize query params to satisfy codecov/patch branch coverage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add error path coverage for children endpoint Cover invalid token (404), trashed matter (410), and non-numeric page/pageSize (NaN fallback) in GET /:token/children to satisfy codecov/patch threshold requirements. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add ASSETS binding to wrangler.toml for Workers SSR Without binding = "ASSETS", env.ASSETS is undefined at runtime and the /s/:token SSR handler throws error code 1101. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve CF SSR OG meta by calling service layer directly instead of self-subrequest Cloudflare Workers cannot fetch() their own origin when the path is listed in run_worker_first — the request loops back and returns a 500 error code 1101. Replace the HTTP subrequest in fetchShareMeta with a direct call to resolveShareByToken(platform.db, token) from the service layer. Add CF integration tests asserting that a valid landing share produces real og:title metadata and an unknown token falls back gracefully. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
import { Hono } from 'hono'
|
|
import { cors } from 'hono/cors'
|
|
import type { Auth } from './auth'
|
|
import { authMiddleware } from './middleware/auth'
|
|
import { accessLog } from './middleware/logger'
|
|
import type { Env } from './middleware/platform'
|
|
import { platformMiddleware } from './middleware/platform'
|
|
import type { Platform } from './platform/interface'
|
|
import authProviders from './routes/auth-providers'
|
|
import emailConfig from './routes/email-config'
|
|
import { adminInviteCodes, publicInviteCodes } from './routes/invite-codes'
|
|
import { notifications } from './routes/notifications'
|
|
import objects from './routes/objects'
|
|
import profile from './routes/profile'
|
|
import { adminQuotas, userQuotas } from './routes/quotas'
|
|
import shareApi from './routes/share-api'
|
|
import shareDirect from './routes/share-direct'
|
|
import shares from './routes/shares'
|
|
import storages from './routes/storages'
|
|
import system from './routes/system'
|
|
import { publicTeams, teams } from './routes/teams'
|
|
import trash from './routes/trash'
|
|
import users from './routes/users'
|
|
|
|
export function createApp(platform: Platform, auth: Auth) {
|
|
const app = new Hono<Env>()
|
|
|
|
app.use('/*', platformMiddleware(platform, auth))
|
|
app.use('/api/*', accessLog)
|
|
|
|
app.use(
|
|
'/api/*',
|
|
cors({
|
|
origin: (origin) => origin || '*',
|
|
allowHeaders: ['Content-Type', 'Authorization'],
|
|
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
credentials: true,
|
|
}),
|
|
)
|
|
|
|
app.on(['POST', 'GET'], '/api/auth/*', async (c) => {
|
|
const a = c.get('auth')
|
|
return a.handler(c.req.raw)
|
|
})
|
|
|
|
// Public share routes — no auth required; mount before authMiddleware.
|
|
// /api/share/* is covered by run_worker_first=["/api/*"] in wrangler.toml.
|
|
// /dl/* is listed separately in run_worker_first.
|
|
// /s/:token is intentionally left for the T8 SPA landing page.
|
|
app.route('/api/share', shareApi)
|
|
app.route('/dl', shareDirect)
|
|
|
|
// Public routes — no auth required; mount before authMiddleware
|
|
app.route('/api/profiles', profile)
|
|
app.route('/api/teams', publicTeams)
|
|
|
|
app.use('/api/*', authMiddleware)
|
|
|
|
// Mount routes separately to avoid deep type chain accumulation.
|
|
// Each .route() call is independent — TypeScript doesn't stack types.
|
|
app.route('/api/objects', objects)
|
|
app.route('/api/shares', shares)
|
|
app.route('/api/recycle-bin', trash)
|
|
app.route('/api/teams', teams)
|
|
app.route('/api/admin/storages', storages)
|
|
app.route('/api/admin/users', users)
|
|
app.route('/api/admin/email-config', emailConfig)
|
|
app.route('/api/admin/invite-codes', adminInviteCodes)
|
|
app.route('/api/invite-codes', publicInviteCodes)
|
|
app.route('/api/admin/quotas', adminQuotas)
|
|
app.route('/api/quotas', userQuotas)
|
|
app.route('/api/system', system)
|
|
app.route('/api/auth-providers', authProviders)
|
|
app.route('/api/notifications', notifications)
|
|
|
|
app.get('/api/health', (c) => c.json({ status: 'ok' }))
|
|
|
|
return app
|
|
}
|
|
|
|
export type AppType = ReturnType<typeof createApp>
|
|
|
|
// Sub-router types for RPC clients — avoids combined AppType OOM
|
|
export type ObjectsRoute = typeof objects
|
|
export type ShareApiRoute = typeof shareApi
|
|
export type SharesRoute = typeof shares
|
|
export type TrashRoute = typeof trash
|
|
export type StoragesRoute = typeof storages
|
|
export type UsersRoute = typeof users
|
|
export type AdminQuotasRoute = typeof adminQuotas
|
|
export type UserQuotasRoute = typeof userQuotas
|
|
export type SystemRoute = typeof system
|
|
export type EmailConfigRoute = typeof emailConfig
|
|
export type AdminInviteCodesRoute = typeof adminInviteCodes
|
|
export type PublicInviteCodesRoute = typeof publicInviteCodes
|
|
export type AuthProvidersRoute = typeof authProviders
|
|
export type ProfileRoute = typeof profile
|
|
export type TeamsRoute = typeof teams
|
|
export type PublicTeamsRoute = typeof publicTeams
|
|
export type NotificationsRoute = typeof notifications
|