Refactor server structure and enhance middleware functionality

- Consolidated middleware setup into a dedicated setup file for better organization.
- Introduced API proxy middleware to handle requests to the backend API.
- Registered well-known, merge, and SSR routes in separate files for improved modularity.
- Removed unused HTML and SSR layout files to streamline the codebase.
- Implemented a utility for HTML escaping to prevent XSS vulnerabilities.
- Updated the main server entry point to utilize the new middleware and route structure.
This commit is contained in:
2026-02-26 18:38:37 +07:00
parent 00bbe0f503
commit ff1d4902bc
11 changed files with 488 additions and 465 deletions

426
AGENTS.md
View File

@@ -1,82 +1,101 @@
# Holistream - AI Agent Guide
# AGENTS.md
This document provides comprehensive guidance for AI coding agents working on the Holistream project.
This file provides guidance for AI coding agents working with the Holistream codebase.
## Project Overview
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities with a focus on performance and user experience.
**Holistream** is a Vue 3 streaming application with Server-Side Rendering (SSR) deployed on Cloudflare Workers. It provides video upload, management, and streaming capabilities for content creators.
- **Name**: holistream
- **Type**: ES Module JavaScript/TypeScript project
- **Package Manager**: Bun (uses `bun.lock`)
### Key Characteristics
- **Type**: Full-stack web application with SSR
- **Primary Language**: TypeScript
- **Package Manager**: Bun (evident from `bun.lock`)
- **Deployment Target**: Cloudflare Workers
## Technology Stack
| Category | Technology |
|----------|------------|
| **Framework** | Vue 3.5+ with Composition API |
| **Rendering** | SSR (Server-Side Rendering) with Vue's `createSSRApp` |
| **Router** | Vue Router 5 with SSR-aware history |
| **Server** | Hono framework on Cloudflare Workers |
| **Build Tool** | Vite 7 with custom SSR plugin |
| **Styling** | UnoCSS (Tailwind-like utility-first CSS) |
| **UI Components** | PrimeVue 4 with Aura theme |
| **State Management** | Pinia 3 + Pinia Colada for server state |
| **HTTP Client** | Auto-generated from OpenAPI/Swagger spec |
| **Head Management** | @unhead/vue for SEO |
| **Icons** | Custom Vue icon components |
| **Validation** | Zod v4 |
| **Utils** | VueUse, clsx, tailwind-merge |
| Category | Technology | Version |
|----------|------------|---------|
| Framework | Vue | 3.5.27 |
| Router | Vue Router | 5.0.2 |
| Server Framework | Hono | 4.11.7 |
| Build Tool | Vite | 7.3.1 |
| CSS Framework | UnoCSS | 66.6.0 |
| UI Components | PrimeVue | 4.5.4 |
| State Management | Pinia | 3.0.4 |
| Server State | Pinia Colada | 0.21.2 |
| Meta/SEO | @unhead/vue | 2.1.2 |
| Utilities | VueUse | 14.2.0 |
| Validation | Zod | 4.3.6 |
| Deployment | Wrangler | 4.62.0 |
## Project Structure
```
.
├── src/
│ ├── api/ # API client and HTTP adapters
│ │ ├── client.ts # Auto-generated API client from Swagger
│ │ ├── httpClientAdapter.client.ts # Browser fetch adapter
│ │ ── httpClientAdapter.server.ts # SSR fetch adapter with cookie forwarding
│ │ └── rpc/ # RPC utilities
│ │ ├── client.ts # Auto-generated API client from OpenAPI spec
│ │ ├── httpClientAdapter.client.ts # Client-side fetch adapter
│ │ ── httpClientAdapter.server.ts # Server-side fetch adapter
│ ├── client.ts # Client entry point (hydration)
│ ├── components/ # Vue components (auto-imported)
│ │ ├── icons/ # Custom SVG icon components
│ ├── components/ # Vue components
│ │ ├── dashboard/ # Dashboard-specific components
│ │ ── ui/ # UI primitive components
│ │ ── icons/ # Custom icon components
│ │ ├── ui/ # UI primitive components
│ │ ├── ClientOnly.tsx # SSR-safe client-only wrapper
│ │ ├── DashboardLayout.vue # Main dashboard layout
│ │ ├── GlobalUploadIndicator.vue
│ │ ├── NotificationDrawer.vue
│ │ └── RootLayout.vue # Root application layout
│ ├── composables/ # Vue composables
│ │ └── useUploadQueue.ts # File upload queue management
│ │ └── useUploadQueue.ts # Upload queue management
│ ├── index.tsx # Server entry point (Hono app)
│ ├── lib/ # Utility libraries
│ │ ├── utils.ts # Helper functions (cn, formatBytes, etc.)
│ │ ├── liteMqtt.ts # MQTT client for real-time notifications
│ │ ├── swr/ # SWR cache implementation
│ │ ── directives/ # Vue custom directives
│ │ ├── constants.ts # Application constants
│ │ ├── directives/ # Custom Vue directives
│ │ ├── hoc/ # Higher-order components
│ │ ── interface.ts # TypeScript interfaces
│ │ ├── liteMqtt.ts # MQTT client (browser)
│ │ ├── manifest.ts # Vite manifest utilities
│ │ ├── PiniaSharedState.ts # Pinia state hydration plugin
│ │ ├── primePassthrough.ts # PrimeVue theme configuration
│ │ ├── replateStreamText.ts
│ │ └── utils.ts # Utility functions (cn, formatters, etc.)
│ ├── main.ts # App factory function
│ ├── routes/ # Route components
│ ├── auth/ # Login, signup, forgot password
│ │ ├── home/ # Landing, terms, privacy
│ │ ├── notification/ # Notification center
│ ├── mocks/ # Mock data for development
│ ├── routes/ # Route components (page components)
│ │ ├── auth/ # Authentication pages
│ │ ├── home/ # Public pages (landing, terms, privacy)
│ │ ├── notification/ # Notification page
│ │ ├── overview/ # Dashboard overview
│ │ ├── plans/ # Payments & plans
│ │ ├── profile/ # User profile
│ │ ├── upload/ # Video upload interface
│ │ ├── video/ # Video list and detail
│ │ ── index.ts # Router configuration
├── server/ # Server-specific modules
│ │ ├── upload/ # Video upload
│ │ ├── video/ # Video management
│ │ ── index.ts # Router configuration
│ └── NotFound.vue # 404 page
│ ├── server/ # Server-side utilities
│ │ └── modules/
│ │ └── merge.ts # Video chunk merge logic
│ ├── stores/ # Pinia stores
│ │ └── auth.ts # Authentication state
│ │ └── auth.ts # Authentication store
│ ├── type.d.ts # TypeScript declarations
│ └── worker/ # Worker utilities
├── public/ # Static assets (favicons, etc.)
├── docs.json # OpenAPI/Swagger specification
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── vite.config.ts # Vite + Cloudflare plugin config
├── wrangler.jsonc # Cloudflare Workers configuration
│ ├── html.ts
│ └── ssrLayout.ts
├── bootstrap_btn.ts # Bootstrap button preset for UnoCSS
├── ssrPlugin.ts # Custom Vite SSR plugin
├── uno.config.ts # UnoCSS configuration
├── ssrPlugin.ts # Custom SSR build plugin
├── bootstrap_btn.ts # Custom UnoCSS preset for Bootstrap buttons
├── auto-imports.d.ts # Generated auto-import declarations
── components.d.ts # Generated component declarations
├── vite.config.ts # Vite configuration
├── wrangler.jsonc # Cloudflare Workers configuration
├── tsconfig.json # TypeScript configuration
── package.json # Package dependencies
├── bun.lock # Bun lock file
├── docs.json # OpenAPI/Swagger spec for API
├── auto-imports.d.ts # Auto-generated type declarations
└── components.d.ts # Auto-generated component declarations
```
## Build and Development Commands
@@ -85,10 +104,10 @@ This document provides comprehensive guidance for AI coding agents working on th
# Install dependencies
bun install
# Development server with hot reload
# Start development server with hot reload
bun dev
# Production build (client + worker)
# Build for production (client + worker)
bun run build
# Preview production build locally
@@ -104,45 +123,49 @@ bun run cf-typegen
bun run tail
```
> **Note**: While npm commands work (`npm run dev`, etc.), the project uses Bun as its primary package manager.
## Architecture Details
### SSR Architecture
The application uses a custom SSR setup (`ssrPlugin.ts`):
The application uses a custom SSR setup defined in `ssrPlugin.ts`:
1. **Build Order**: Client bundle is built FIRST, then Worker bundle
2. **Manifest Injection**: Vite manifest is injected into server build for asset rendering
3. **Environment-based Module Resolution**: Different implementations for `@httpClientAdapter` and `@liteMqtt` based on SSR context
1. **Build Order**: Client bundle is built FIRST, then the Worker bundle
2. **Manifest Injection**: Vite manifest is injected into the server build for asset rendering
3. **Environment-based Resolution**: `httpClientAdapter` and `liteMqtt` resolve to different implementations based on SSR context
**Entry Points**:
**Entry Points:**
- **Server**: `src/index.tsx` - Hono app that renders Vue SSR stream
- **Client**: `src/client.ts` - Hydrates the SSR-rendered app
- **Factory**: `src/main.ts` - Creates app instance (used by both)
- **Client**: `src/client.ts` - Hydrates the SSR-rendered application
- **App Factory**: `src/main.ts` - Creates the Vue app instance (used by both)
### Module Aliases
| Alias | Resolution |
|-------|------------|
| `@/` | `./src/` |
| `@httpClientAdapter` | `src/api/httpClientAdapter.server.ts` (SSR) or `.client.ts` (browser) |
| `@liteMqtt` | `src/lib/liteMqtt.server.ts` (SSR) or `.ts` (browser) |
### State Management Pattern
### State Management with SSR
Uses **Pinia Colada** for server state with SSR hydration:
- Queries are fetched server-side and serialized to `window.__APP_DATA__`
- Client hydrates the query cache on startup via `hydrateQueryCache()`
- Pinia state is similarly serialized and restored via `PiniaSharedState` plugin
- Server-side queries are fetched and serialized to `window.__APP_DATA__`
- Client hydrates the query cache via `hydrateQueryCache()`
- Pinia state is serialized and restored via `PiniaSharedState` plugin
### Module Aliases
Configured in `tsconfig.json` and `vite.config.ts`:
| Alias | Resolution |
|-------|------------|
| `@/` | `src/` |
| `@httpClientAdapter` | `src/api/httpClientAdapter.server.ts` (SSR) or `.client.ts` (browser) |
| `@liteMqtt` | `src/lib/liteMqtt.server.ts` (SSR) or `.ts` (browser) |
### API Client Architecture
The API client (`src/api/client.ts`) is auto-generated from OpenAPI/Swagger spec (`docs.json`):
The API client (`src/api/client.ts`) is **auto-generated** from the OpenAPI spec (`docs.json`):
- **Base URL**: `r` (proxied to `https://api.pipic.fun`)
- **Server Adapter** (`httpClientAdapter.server.ts`): Forwards cookies, merges headers, calls backend API
- **Client Adapter** (`httpClientAdapter.client.ts`): Standard fetch with credentials
- **Proxy Route**: `/r/*` paths proxy to `https://api.pipic.fun`
- Uses `customFetch` adapter that differs between client/server
- **Server adapter** (`httpClientAdapter.server.ts`): Forwards cookies, merges headers, proxies to `api.pipic.fun`
- **Client adapter** (`httpClientAdapter.client.ts`): Standard fetch with credentials
- API proxy route: `/r/*` paths proxy to `https://api.pipic.fun`
### Routing Structure
@@ -150,22 +173,21 @@ Routes are defined in `src/routes/index.ts` with three main layout groups:
1. **Public** (`/`): Landing page, terms, privacy
2. **Auth** (`/login`, `/sign-up`, `/forgot`): Authentication pages (redirects if logged in)
3. **Dashboard**: Protected routes requiring auth
3. **Dashboard**: Protected routes requiring authentication
- `/overview` - Main dashboard
- `/upload` - Video upload
- `/upload` - Video upload with queue management
- `/video` - Video list
- `/video/:id` - Video detail/edit
- `/payments-and-plans` - Billing
- `/notification` - Notifications
- `/profile` - User settings
- `/payments-and-plans` - Billing management
- `/notification`, `/profile` - User settings
Route meta supports `@unhead/vue` for SEO:
```typescript
meta: {
head: {
title: "Page Title",
meta: [{ name: "description", content: "..." }]
}
```ts
meta: {
head: {
title: "Page Title",
meta: [{ name: "description", content: "..." }]
}
}
```
@@ -173,137 +195,166 @@ meta: {
Configuration in `uno.config.ts`:
- **Presets**: Wind4 (Tailwind), Typography, Attributify, Bootstrap buttons
- **Custom Colors**:
- `primary`: #14a74b (green)
- `accent`: #14a74b
- `secondary`: #fd7906 (orange)
- `success`, `info`, `warning`, `danger`
- **Presets**: Wind4 (Tailwind-like), Typography, Attributify, Bootstrap buttons
- **Custom Colors**:
- `primary` (#14a74b)
- `secondary` (#fd7906)
- `accent`, `success`, `info`, `warning`, `danger`
- **Shortcuts**: `press-animated` for button press effects
- **Transformers**: `transformerCompileClass` (prefix: `_`), `transformerVariantGroup`
- **Transformers**:
- `transformerCompileClass` (prefix: `_` for compiled classes)
- `transformerVariantGroup`
Use `cn()` from `src/lib/utils.ts` for conditional class merging (clsx + tailwind-merge):
```typescript
import { cn } from '@/lib/utils';
const className = cn('base-class', condition && 'conditional-class');
```
Use `cn()` from `src/lib/utils.ts` for conditional class merging (combines `clsx` + `tailwind-merge`).
### Component Auto-Import
Components in `src/components/` are auto-imported via `unplugin-vue-components`:
Components are auto-imported via `unplugin-vue-components`:
- PrimeVue components resolved via `PrimeVueResolver`
- Custom icons in `src/components/icons/` are auto-imported
- Vue/Pinia/Vue Router APIs auto-imported via `unplugin-auto-import`
- Type declarations auto-generated to `components.d.ts` and `auto-imports.d.ts`
No need to manually import `ref`, `computed`, `onMounted`, etc.
## Development Guidelines
### Code Style
- **TypeScript**: Strict mode enabled
- **JSX/TSX**: Supported for components (import source: `vue`)
- **CSS**: Use UnoCSS utility classes; custom CSS in component `<style>` blocks when needed
### File Organization
- Page components go in `src/routes/` following the route structure
- Reusable components go in `src/components/`
- Composables go in `src/composables/`
- Stores go in `src/stores/`
- Server utilities go in `src/server/`
### HTTP Requests
**Always use the generated API client** instead of raw fetch:
```ts
import { client } from '@/api/client';
// Example
const response = await client.auth.loginCreate({ email, password });
```
The client handles:
- Base URL resolution
- Cookie forwarding (server-side)
- Type safety
### Authentication Flow
- `useAuthStore` manages auth state with cookie-based sessions
- `init()` called on every request to fetch current user via `/me` endpoint
- `init()` is called on every request to fetch current user via `/me` endpoint
- `beforeEach` router guard redirects unauthenticated users from protected routes
- MQTT client connects on user login for real-time notifications
### File Upload Architecture
Upload queue (`src/composables/useUploadQueue.ts`):
- Supports both local files and remote URLs
- Presigned POST URLs fetched from API
- Parallel chunk upload for large files
- Parallel chunk upload (90MB chunks, max 3 parallel)
- Progress tracking with speed calculation
## Code Style Guidelines
### Type Safety
### File Naming Conventions
- TypeScript strict mode enabled
- `CloudflareBindings` interface for environment variables (generated via `cf-typegen`)
- API types auto-generated from backend OpenAPI spec (`docs.json`)
- **Components**: PascalCase (e.g., `VideoCard.vue`, `DashboardNav.vue`)
- **Composables**: camelCase with `use` prefix (e.g., `useUploadQueue.ts`)
- **Utilities**: camelCase (e.g., `utils.ts`, `liteMqtt.ts`)
- **Routes**: PascalCase for page components (e.g., `Overview.vue`)
- **Icons**: PascalCase with `Icon` suffix (e.g., `Bell.vue`, `VideoIcon.vue`)
## Environment Configuration
### Component Patterns
### Cloudflare Worker Bindings
Use Composition API with `<script setup>`:
Configured in `wrangler.jsonc`:
```vue
<script setup lang="ts">
// Auto-imported: ref, computed, onMounted, etc.
const props = defineProps<{
video: ModelVideo;
}>();
const emit = defineEmits<{
delete: [id: string];
}>();
// Use composables
const auth = useAuthStore();
const route = useRoute();
</script>
```json
{
"name": "holistream",
"compatibility_date": "2025-08-03",
"compatibility_flags": ["nodejs_compat"],
"observability": { ... }
}
```
### TypeScript
- Strict mode enabled
- Use `type` for type aliases
- Interfaces for object shapes
- Always type function parameters and returns
### CSS Class Ordering
Use UnoCSS utility classes in this order:
1. Layout (display, position)
2. Spacing (margin, padding)
3. Sizing (width, height)
4. Typography (font, text)
5. Visuals (bg, border, shadow)
6. Interactivity (hover, focus)
Example:
```html
<div class="flex items-center gap-2 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-600">
```
## Testing Strategy
**Note**: There are currently no automated test suites (like Vitest) or linting tools (like ESLint/Prettier) configured in this project.
Testing is currently manual:
1. Run `bun dev` for local development testing
2. Use `bun preview` to test production build locally
3. Use `bun run tail` to monitor Cloudflare Worker logs in production
## Deployment Process
### Cloudflare Workers
- **Config**: `wrangler.jsonc`
- **Entry**: `src/index.tsx`
- **Compatibility Date**: 2025-08-03
- **Compatibility Flags**: `nodejs_compat`
### Environment Variables
Cloudflare Worker bindings (configured in `wrangler.jsonc`):
- No explicit secrets in code - use Wrangler secrets management
- Run `bun run cf-typegen` to update TypeScript types after changing bindings
- Access environment variables via Hono context: `c.env.VAR_NAME`
### Deployment Steps
### Local Environment
1. Ensure code compiles: `bun run build`
2. Deploy: `bun run deploy`
3. Monitor logs: `bun run tail`
Create `.dev.vars` for local development secrets (do not commit):
```
SECRET_KEY=...
```
## Testing and Quality
**Current Status**: There are currently no automated test suites (like Vitest) or linting tools (like ESLint/Prettier) configured.
When adding tests or linting:
- Add appropriate dev dependencies
- Update this section with commands and conventions
- Consider the SSR environment when writing tests
## Security Considerations
- **Cookie-based Auth**: Session cookies are forwarded between client and API
- **CORS**: Configured in Hono middleware
- **CSRF**: Protected by same-origin cookie policy
- **Secrets**: Never commit secrets to code; use Wrangler secrets management
1. **Cookie Security**: Cookies are httpOnly, secure, and sameSite
2. **CORS**: Configured via Hono's CORS middleware
3. **API Proxy**: Backend API is never exposed directly to the browser; all requests go through `/r/*` proxy
4. **Input Validation**: Use Zod for runtime validation
5. **XSS Protection**: HTML escaping is applied to SSR data via `htmlEscape()` function
## Important File Locations
## Common Patterns
### Creating a New Page
1. Create component in `src/routes/<section>/PageName.vue`
2. Add route to `src/routes/index.ts` with appropriate meta
3. Use `head` in route meta for SEO if needed
### Using the Upload Queue
```ts
import { useUploadQueue } from '@/composables/useUploadQueue';
const { items, addFiles, addRemoteUrls, startQueue } = useUploadQueue();
```
### Accessing Hono Context in Components
```ts
import { inject } from 'vue';
const honoContext = inject('honoContext');
```
### Conditional Classes
```ts
import { cn } from '@/lib/utils';
const className = cn(
'base-class',
isActive && 'active-class',
variant === 'primary' ? 'text-primary' : 'text-secondary'
);
```
## External Dependencies
- **Backend API**: `https://api.pipic.fun`
- **MQTT Broker**: `wss://mqtt-dashboard.com:8884/mqtt`
- **Fonts**: Google Fonts (Google Sans loaded from fonts.googleapis.com)
## Important Files Reference
| Purpose | Path |
|---------|------|
@@ -318,11 +369,6 @@ Cloudflare Worker bindings (configured in `wrangler.jsonc`):
| Wrangler config | `wrangler.jsonc` |
| Vite config | `vite.config.ts` |
## Development Notes
---
- Always use `customFetch` from `@httpClientAdapter` for API calls, never raw fetch
- The `honoContext` is provided to Vue app for accessing request context in components
- MQTT client in `src/lib/liteMqtt.ts` handles real-time notifications
- Icons are custom Vue components in `src/components/icons/`
- Upload indicator is a global component showing queue status
- For SEO, use route meta with `@unhead/vue` head configuration
*This document was generated for AI coding agents. For human contributors, see README.md.*