Merge branch 'main' into feat/auth

This commit is contained in:
junchi.zhang
2026-04-29 01:20:30 +08:00
4 changed files with 50 additions and 50 deletions
+5 -5
View File
@@ -15,7 +15,7 @@ export interface Env {
DB: D1Database;
API_KEY: string; // 项目 API Key — 用于所有 auth 接口认证
PROJECT_NAME: string; // 项目名 — 所有 auth 接口必须同时传递
BASE_URL?: string; // 可选,默认 https://pinme.dev
BASE_URL?: string; // 可选,默认 https://pinme.cloud
}
```
@@ -103,7 +103,7 @@ async function createAuthUser(
env: Env,
payload: { email: string; password: string; display_name?: string }
): Promise<{ user?: UserInfo; error?: string }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(
`${baseUrl}/api/v1/auth/create_user?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,
{
@@ -162,7 +162,7 @@ async function verifyAuthToken(
env: Env,
idToken: string
): Promise<{ uid?: string; email?: string; error?: string; emailNotVerified?: boolean }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(
`${baseUrl}/api/v1/auth/verify_token?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,
{
@@ -199,7 +199,7 @@ async function verifyAuthToken(
```typescript
async function getAuthUser(env: Env, uid: string): Promise<{ user?: UserInfo; error?: string }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(
`${baseUrl}/api/v1/auth/user?project_name=${encodeURIComponent(env.PROJECT_NAME)}&uid=${encodeURIComponent(uid)}`,
{ method: 'GET', headers: { 'X-API-Key': env.API_KEY } }
@@ -235,7 +235,7 @@ async function listAuthUsers(
env: Env,
options: { pageToken?: string; maxResults?: number } = {}
): Promise<{ users?: UserInfo[]; nextPageToken?: string; error?: string }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const url = new URL('/api/v1/auth/list_users', baseUrl);
url.searchParams.set('project_name', env.PROJECT_NAME);
if (options.pageToken) url.searchParams.set('page_token', options.pageToken);
+4 -4
View File
@@ -16,11 +16,11 @@ The following environment variables are automatically injected when the Worker i
export interface Env {
DB: D1Database;
API_KEY: string; // Project API Key — used for send_email authentication
BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.dev
BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.cloud
}
```
> `API_KEY` is the sole credential for the Worker to call PinMe platform APIs. When `BASE_URL` is not set, it defaults to `https://pinme.dev`.
> `API_KEY` is the sole credential for the Worker to call PinMe platform APIs. When `BASE_URL` is not set, it defaults to `https://pinme.cloud`.
---
@@ -65,7 +65,7 @@ export interface Env {
```typescript
async function sendEmail(env: Env, to: string, subject: string, html: string): Promise<{ ok: boolean; error?: string }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(`${baseUrl}/api/v4/send_email`, {
method: 'POST',
headers: {
@@ -151,7 +151,7 @@ async function callPinmeAPI<T>(url: string, apiKey: string, body: unknown): Prom
### Usage Example
```typescript
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
// Send email
const emailResult = await callPinmeAPI<{ ok: boolean }>(
+7 -7
View File
@@ -17,11 +17,11 @@ export interface Env {
DB: D1Database;
API_KEY: string; // Project API Key from create_worker
PROJECT_NAME: string; // Actual project_name from create_worker; must match API_KEY
BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.dev
BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.cloud
}
```
> `API_KEY` authenticates the Worker to PinMe. `PROJECT_NAME` is required for `chat/completions` and must belong to the same project as `API_KEY`. When `BASE_URL` is not set, use `https://pinme.dev`.
> `API_KEY` authenticates the Worker to PinMe. `PROJECT_NAME` is required for `chat/completions` and must belong to the same project as `API_KEY`. When `BASE_URL` is not set, use `https://pinme.cloud`.
---
@@ -35,7 +35,7 @@ Use this when the Worker needs to list available OpenRouter models. The response
```typescript
async function listModels(env: Env): Promise<unknown> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(`${baseUrl}/api/v1/models`, {
headers: { 'X-API-Key': env.API_KEY },
});
@@ -81,7 +81,7 @@ Always set `max_results` and `max_total_results` to keep search volume and cost
```typescript
async function searchWithLLM(env: Env, query: string): Promise<string> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(
`${baseUrl}/api/v1/chat/completions?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,
{
@@ -157,7 +157,7 @@ async function callLLM(
messages: Array<{ role: string; content: string }>,
model = 'openai/gpt-4o-mini',
): Promise<{ content: string; error?: string }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(
`${baseUrl}/api/v1/chat/completions?project_name=${encodeURIComponent(env.PROJECT_NAME)}`,
{
@@ -199,7 +199,7 @@ async function handleChat(request: Request, env: Env): Promise<Response> {
```typescript
async function handleChatStream(request: Request, env: Env): Promise<Response> {
const body = await request.text();
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
// Ensure stream=true in the request
let parsed = JSON.parse(body);
@@ -343,7 +343,7 @@ async function callOpenRouterJSON<T>(url: string, apiKey: string, body: unknown)
### Usage Example
```typescript
const baseUrl = env.BASE_URL ?? 'https://pinme.dev';
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
// Call LLM (non-streaming)
const llmResult = await callOpenRouterJSON<{ choices: Array<{ message: { content: string } }> }>(
+34 -34
View File
@@ -139,14 +139,14 @@ pinme update-db # Run SQL migrations only (when only db/ was modifi
│ ├── wrangler.toml # Worker config (auto-generated, do not modify)
│ ├── package.json
│ └── src/
│ └── worker.ts # Backend entry — JSON API only
│ └── worker.ts # Backend entry — primarily used for JSON APIs in this template
├── db/
│ └── 001_init.sql # SQL table definitions
├── frontend/
│ ├── package.json
│ ├── vite.config.ts # Dev proxy: /api → localhost:8787
│ ├── index.html
│ ├── .env # Auto-generated: VITE_WORKER_URL (do not modify)
│ ├── .env # Auto-generated: VITE_API_URL (do not modify)
│ └── src/
│ ├── main.tsx
│ ├── App.tsx
@@ -199,9 +199,9 @@ The backend Worker is deployed at `https://{name}.pinme.pro`. Frontend API reque
---
## Worker Code Patterns (backend/src/worker.ts)
## Worker Code Patterns (`backend/src/worker.ts`)
The Worker backend only serves JSON APIs. **No npm packages allowed** (no hono, express, etc.). Write routes manually:
In this template, the Worker backend is primarily used for JSON APIs. Prefer standard Web APIs and simple manual routing by default. Worker-compatible libraries can be added when needed, but the default template does not rely on extra frameworks. Avoid packages that depend on a full Node.js runtime, a persistent local filesystem, native binaries, or child processes.
```typescript
export interface Env {
@@ -239,20 +239,20 @@ export default {
};
```
### Worker Restrictions
### Worker Constraints and Default Conventions
| Prohibited | Alternative |
|-----------|-------------|
| `import from 'hono'` or any npm package | Manual routing (`if pathname === '/api/...'`) |
| `import fs from 'fs'` / Node.js built-in modules | Web APIs: `crypto`, `fetch`, `URL`, etc. |
| `require()` syntax | ESM `import` only |
| Worker returning HTML | JSON API only |
| Storing passwords in plaintext | Hash with SHA-256 before storing |
| SQL string concatenation | Use `.bind()` parameterized queries |
| Item | Notes |
|------|------|
| Dependency choice | Prefer standard Web APIs and simple manual routing by default. If extra dependencies are needed, prefer Worker-compatible libraries. |
| Node.js capability | Workers now support part of Node.js compatibility, but they are not a full Node.js runtime. Do not assume all Node.js built-in modules are available or behave exactly the same. |
| Filesystem | Do not treat a Worker like a server with a persistent local disk. Even if some `fs` capabilities are available, do not rely on persistence across requests. |
| Response types | This template mainly uses the Worker for JSON APIs. If there is a clear need, it can also be adapted to return HTML or other content. |
| Password storage | Never store passwords in plaintext. Use a dedicated password hashing algorithm such as bcrypt, scrypt, or Argon2. |
| SQL | Do not build SQL by string concatenation. Use parameterized queries such as `.bind()`. |
### Email API Reference (for Worker Backend)
When the backend needs email sending capability, use the PinMe platform API (`https://pinme.dev/api/v4/send_email`).
When the backend needs email sending, use the PinMe platform API (`https://pinme.cloud/api/v4/send_email`).
**1. Configure API_KEY**
@@ -289,7 +289,7 @@ async function handleSendEmail(request: Request, env: Env): Promise<Response> {
return json({ error: 'Invalid email address' }, 400);
}
const response = await fetch('https://pinme.dev/api/v4/send_email', {
const response = await fetch('https://pinme.cloud/api/v4/send_email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -310,9 +310,9 @@ async function handleSendEmail(request: Request, env: Env): Promise<Response> {
## Frontend API Utility (frontend/src/utils/api.ts)
```typescript
// Development: Vite proxies /api localhost:8787
// Production: VITE_WORKER_URL is auto-injected by pinme create
export const API = import.meta.env.VITE_WORKER_URL || '';
// Development: Vite proxies /api to localhost:8787
// Production: VITE_API_URL is auto-injected by pinme create
export const API = import.meta.env.VITE_API_URL || '';
export function getApiUrl(path: string): string {
return API ? `${API}${path}` : path;
@@ -341,41 +341,41 @@ if (meta.changes === 0) return json({ error: 'Not found' }, 404);
### SQL Migration Files
**Format:** `db/NNN_description.sql` (e.g. `001_init.sql`). Executed in filename order.
**Format:** `db/NNN_description.sql` (for example, `001_init.sql`). Files are executed in filename order.
**SQLite Type Constraints:**
| Do Not Use | Alternative |
|-----------|-------------|
| `BOOLEAN` | `INTEGER` (0 = false, 1 = true) |
| `DATETIME` / `TIMESTAMP` | `TEXT`, store ISO 8601 (default: `datetime('now')`) |
| `JSON` type | `TEXT`, use `JSON.stringify()` / `JSON.parse()` |
| `DATETIME` / `TIMESTAMP` | `TEXT`, stored as ISO 8601 (default: `datetime('now')`) |
| `JSON` type | `TEXT`, using `JSON.stringify()` / `JSON.parse()` |
| `VARCHAR(n)` | `TEXT` |
## Capability Limits
## Template Architecture Suggestions
| Limitation | Alternative |
| Scenario | Default Suggestion |
|-----------|-------------|
| File storage (image uploads) | Store external image URLs, or `pinme upload` then store IPFS link |
| WebSocket | Polling API (fetch every 5 seconds) |
| Multiple Workers | Merge into a single Worker with route prefixes |
| Multiple databases | Merge into one D1 |
| File storage (image uploads) | Store external image URLs, or upload with `pinme upload` first and then store the resulting link |
| Real-time communication | This template defaults to regular HTTP APIs. If there is no clear real-time requirement, start with polling |
| Multiple Workers | This template defaults to combining functionality into a single Worker and separating routes by prefix |
| Multiple databases | This template defaults to combining data into one D1 database and only splitting when isolation is truly needed |
## Important Notes
- `pinme.toml`, `backend/wrangler.toml`, `frontend/.env` are auto-generated — do not modify
- Frontend API URL is obtained via `VITE_WORKER_URL` env var — do not hardcode
- Passwords, tokens, and API keys must be stored in secrets, never in config files
- `pinme.toml`, `backend/wrangler.toml`, and `frontend/.env` are generated by PinMe. Do not edit them manually by default. If extra runtime configuration is truly needed, prefer doing it through PinMe-supported mechanisms.
- Obtain the frontend API URL from the `VITE_API_URL` environment variable. Do not hardcode it.
- Passwords, tokens, and API keys must be stored in secrets. Never put them in config files.
## Common Errors
| Error | Solution |
|-------|----------|
| `command not found: pinme` | `npm install -g pinme` |
| `No such file or directory` | Verify the path exists |
| `Permission denied` | Check file/folder permissions |
| Upload failed | Check network connection, retry |
| Not logged in error | Run `pinme login` first |
| `No such file or directory` | Verify that the path exists |
| `Permission denied` | Check file or directory permissions |
| Upload failed | Check the network connection and retry |
| Not logged in | Run `pinme login` first |
## Other Commands