Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 75 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,11 @@ MUSING_CODE=your_secret_validation_code GITHUB_PAT=ghp_your_github_personal_acce
# Analytics & Monitoring
NEXT_PUBLIC_TINYBIRD_TOKEN=your_tinybird_analytics_token # Optional: additional analytics

# Bookmarks Integration
NEXT_PUBLIC_RAINDROP_ACCESS_TOKEN=your_raindrop_io_access_token # For Raindrop.io bookmarks feature
# Raindrop.io OAuth Integration (for Bookmarks)
RAINDROP_CLIENT_ID=your_raindrop_oauth_client_id
RAINDROP_CLIENT_SECRET=your_raindrop_oauth_client_secret
RAINDROP_ENCRYPTION_KEY=your_32_character_encryption_key
RAINDROP_ENCRYPTED_REFRESH_TOKEN=generated_after_oauth_completion

# Revalidation & Cache Management
NEXT_REVALIDATE_SECRET=your_nextjs_revalidation_secret
Expand Down Expand Up @@ -218,25 +221,22 @@ If you've published new GitHub Issues but the blog doesn't show the latest conte
- ISR cache will automatically expire after 24 hours
- The first visitor will trigger page regeneration

## Raindrop.io Setup (Bookmarks Feature)
## Raindrop.io OAuth Setup (Bookmarks Feature)

This project includes a bookmarks feature that integrates with [Raindrop.io](https://raindrop.io) to display your saved
bookmarks. Follow these steps to set up the integration properly:
This project includes a bookmarks feature that integrates with [Raindrop.io](https://raindrop.io) using OAuth 2.0 authentication to securely access your saved bookmarks. The system supports multiple token storage strategies and automatic token refresh.

### Prerequisites

- A Raindrop.io account (free account works)
- Some bookmark collections created in your Raindrop.io account

### Step 1: Create a Raindrop.io Application
### Step 1: Create a Raindrop.io OAuth Application

1. **Login to Raindrop.io**:

- Go to [https://raindrop.io](https://raindrop.io)
- Sign in to your account

2. **Access Developer Settings**:

- Click on your profile avatar in the top-right corner
- Select "Settings"
- In the left sidebar, find and click "Integrations"
Expand All @@ -245,26 +245,33 @@ bookmarks. Follow these steps to set up the integration properly:
3. **Create New Application**:
- Click "Create new app"
- Fill in the application details:
- **Name**: `Personal Website` (or any name you prefer)
- **Description**: `For personal website bookmarks display`
- **Name**: `Personal Website OAuth` (or any name you prefer)
- **Description**: `OAuth integration for personal website bookmarks`
- **Site**: Your website URL (e.g., `https://yourdomain.com`)
- **Redirect URI**: Your website URL (same as above)
- **Redirect URI**: `https://yourdomain.com/api/auth/raindrop/callback` ⚠️ **Critical: Must be exact**

### Step 2: Configure Environment Variables

Add the following variables to your Vercel project settings or `.env` file:

```bash
# Raindrop.io OAuth Credentials
RAINDROP_CLIENT_ID=your_client_id_from_raindrop_app
RAINDROP_CLIENT_SECRET=your_client_secret_from_raindrop_app

### Step 2: Generate Access Token
# Encryption Key for Token Storage (32 characters)
RAINDROP_ENCRYPTION_KEY=generate_a_32_character_random_string

1. **Get Test Token**:
- After creating the application, you'll see the app details page
- In the "Credentials" section, find the "Test token" row
- Click "Create test token" button
- Copy the generated token (this is your access token)
# Base URL for OAuth Callbacks
NEXT_PUBLIC_BASE_URL=https://yourdomain.com

> **Important**: Use the "Test token", NOT the "Client secret". The Client secret is used for OAuth flows, while the
> Test token is for direct API access.
# Encrypted Tokens (Set after OAuth completion - see Step 4)
RAINDROP_ENCRYPTED_REFRESH_TOKEN=will_be_generated_during_oauth_setup
```

### Step 3: Configure Collection IDs

1. **Find Your Collection IDs**:

- In Raindrop.io, go to your collections
- The collection ID can be found in the URL when viewing a collection
- For example, in `https://app.raindrop.io/my/12345678`, the ID is `12345678`
Expand All @@ -275,44 +282,68 @@ bookmarks. Follow these steps to set up the integration properly:
```javascript
export const COLLECTION_IDS = [
12345678, // Replace with your actual collection IDs
87654321
// Add more collection IDs as needed
87654321 // Add more collection IDs as needed
]
```

### Step 4: Set Environment Variable
### Step 4: Complete OAuth Setup

Add the access token to your `.env` file:
1. **Access OAuth Setup Page**:
- Navigate to `/admin/raindrop-setup` on your website
- You should see the OAuth setup interface

```
NEXT_PUBLIC_RAINDROP_ACCESS_TOKEN=your_actual_test_token_here
```
2. **Start OAuth Flow**:
- Click "开始 OAuth 认证" (Start OAuth Authentication)
- You'll be redirected to Raindrop.io for authorization
- Click "Agree" to authorize your application

### Step 5: Restart Development Server
3. **Copy Encrypted Token**:
- After successful authorization, check your Vercel function logs
- Look for a message like:
```
🔐 Encrypted token (add this to RAINDROP_ENCRYPTED_REFRESH_TOKEN env var):
[long encrypted string]
```
- Copy this encrypted string

After updating the environment variables, restart your Next.js development server:
4. **Set Final Environment Variable**:
- In Vercel project settings, add/update:
- `RAINDROP_ENCRYPTED_REFRESH_TOKEN=[paste the encrypted string here]`
- Redeploy or wait for automatic deployment

```bash
npm run dev
# or
bun dev
```
5. **Verify Setup**:
- Return to `/admin/raindrop-setup`
- Status should now show "已认证" (Authenticated)
- Visit `/bookmarks` to see your bookmark collections

### Troubleshooting
### Token Storage Strategies

- **401 Unauthorized Error**: Ensure you're using the Test token, not the Client secret
- **Empty Bookmarks**: Verify that your collection IDs in `constants.js` match your actual Raindrop.io collections
- **Network Errors**: Check if your Raindrop.io account has the collections you're trying to access
The system automatically selects the best available storage method:

### API Rate Limits
1. **Vercel KV** (if `KV_REST_API_URL` and `KV_REST_API_TOKEN` are set)
2. **Supabase** (if `NEXT_PUBLIC_SUPABASE_URL` and `SUPABASE_ANON_KEY` are set)
3. **Environment Variables** (fallback - requires manual encrypted token setup)

### Features

- **Automatic Token Refresh**: Access tokens are automatically refreshed when needed
- **Secure Storage**: All tokens are encrypted before storage
- **Build-time Safe**: Gracefully handles missing authentication during static generation
- **Multiple Storage Options**: Supports different deployment environments
- **Admin Interface**: Web-based setup and status monitoring

### Troubleshooting

Raindrop.io has API rate limits. The current implementation includes:
- **"未认证" Status**: Complete the OAuth flow and set the encrypted token environment variable
- **Build Failures**: Ensure all environment variables are set correctly
- **Empty Bookmarks**: Verify collection IDs in `constants.js` match your Raindrop.io collections
- **OAuth Errors**: Check that redirect URI exactly matches your application settings

- Cache duration: 2 days for bookmark data
- Request timeout: 10 seconds
- Automatic error handling for failed requests
### API Information

For more information about Raindrop.io API, visit: [https://developer.raindrop.io](https://developer.raindrop.io)
- **Token Lifetime**: Access tokens expire after 2 weeks, automatically refreshed
- **Cache Duration**: Bookmark data is cached for 2 days
- **Rate Limits**: Automatic handling with appropriate timeouts

## Analytics & Monitoring

Expand Down
169 changes: 169 additions & 0 deletions docs/work-log-2025-08-31.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# 工作日志 - 2025年8月31日

**日期**: 2025-08-31
**时间**: 20:00 - 23:00 CST
**主要任务**: 修复 Raindrop.io OAuth 认证和书签同步功能

## 问题描述

用户在成功合并 PR 并部署后发现书签页面(`/bookmarks`)无法显示内容,根本原因是 Raindrop.io OAuth 认证未配置完成。

## 解决过程

### 1. 问题诊断 (20:00-20:30)

- **发现**: 书签页面调用 `getBookmarks()` 函数失败
- **根本原因**: "No refresh token available. Please complete OAuth setup"
- **影响范围**: 所有需要 Raindrop.io API 的功能都无法使用

### 2. OAuth 流程修复 (20:30-22:00)

#### 2.1 修复 OAuth 授权 URL
- **文件**: `src/app/api/auth/raindrop/route.js:12`
- **问题**: 使用了 v1 API 端点导致 500 错误
- **修复**: 改为 v2 API 端点 `https://api.raindrop.io/v2/oauth/authorize`

#### 2.2 修复环境变量配置
- **问题**: Vercel 中的 `RAINDROP_CLIENT_SECRET` 配置错误
- **解决**: 用户更正了 Vercel 环境变量

#### 2.3 修复 Token Manager 选择逻辑
- **文件**: `src/app/api/auth/raindrop/callback/route.js:4-15`
- **问题**: 回调函数没有使用动态 token manager 选择
- **修复**: 实现与 `raindrop-with-auth.js` 相同的动态选择逻辑

#### 2.4 修复状态检查 API
- **文件**: `src/app/api/auth/raindrop/status/route.js`
- **问题**: 硬编码使用 `token-manager` 而不是动态选择
- **修复**: 实现动态 token manager 选择和兼容不同接口

### 3. 核心问题发现 (22:00-22:30)

#### 3.1 env-token-manager 工作机制理解
- **关键发现**: `env-token-manager` 需要手动设置 `RAINDROP_ENCRYPTED_REFRESH_TOKEN` 环境变量
- **工作流程**: OAuth成功 → 加密token → 输出到日志 → 手动设置环境变量
- **缺失步骤**: 用户需要将加密的 token 添加到 Vercel 环境变量

#### 3.2 最终解决方案
- **环境变量**: `RAINDROP_ENCRYPTED_REFRESH_TOKEN`
- **值**: 从 OAuth 回调日志中获取的加密字符串
- **结果**: 认证状态从"未认证"变为"已认证",书签功能正常工作

### 4. 构建时认证问题修复 (22:30-23:00)

#### 4.1 静态生成时的认证错误
- **文件**: `src/lib/raindrop-with-auth.js:26-85`
- **问题**: 构建时调用认证 API 导致部署失败
- **修复**: 添加优雅的错误处理,构建时返回空数据而不是抛出错误

## 关键代码修改

### 1. OAuth 回调增强 (`src/app/api/auth/raindrop/callback/route.js`)

```javascript
// 添加详细的环境变量调试
console.info('Raw environment variables:', {
NODE_ENV: process.env.NODE_ENV,
hasVercelEnv: !!process.env.VERCEL,
allEnvKeys: Object.keys(process.env).filter(key => key.includes('RAINDROP')),
clientIdValue: clientId || 'MISSING',
clientSecretValue: clientSecret ? `${clientSecret.substring(0, 4)}...` : 'MISSING',
baseUrlValue: baseUrl
})

// 改进的错误处理
if (tokenData.result === false) {
console.error('Raindrop.io API error:', tokenData)
throw new Error(`Raindrop.io API error: ${tokenData.errorMessage || 'Unknown error'} (status: ${tokenData.status})`)
}
```

### 2. 认证请求优雅处理 (`src/lib/raindrop-with-auth.js`)

```javascript
// 构建时认证处理
try {
accessToken = await tokenManager.getValidAccessToken()
} catch (authError) {
console.warn('Authentication not available:', authError.message)
throw new Error('Authentication required but not configured')
}

// 构建时返回空数据
if (error.message.includes('Authentication required but not configured')) {
console.warn('Bookmarks not available during build - authentication not configured')
return []
}
```

### 3. 动态 Token Manager 选择

所有 API 端点现在都使用统一的动态选择逻辑:

```javascript
function getTokenManager() {
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
const { tokenManager } = require('@/lib/auth/token-manager')
return tokenManager
}
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.SUPABASE_ANON_KEY) {
const { getTokenManager } = require('@/lib/auth/supabase-token-manager')
return getTokenManager()
}
const { getTokenManager } = require('@/lib/auth/env-token-manager')
return getTokenManager()
}
```

## 环境变量要求

### 必需的环境变量

```bash
# Raindrop.io 应用凭据
RAINDROP_CLIENT_ID=6826e3e0149b4706a991e02d
RAINDROP_CLIENT_SECRET=26a1ed7a-17ff-4700-b396-1ba0d989db5e

# 加密密钥
RAINDROP_ENCRYPTION_KEY=c567419f3383e7c5b0d6148867f81328

# 加密的刷新令牌(OAuth完成后设置)
RAINDROP_ENCRYPTED_REFRESH_TOKEN=[从OAuth回调日志中获取]

# 基础URL
NEXT_PUBLIC_BASE_URL=https://me.deeptoai.com
```

## 经验教训

### 关键失误
1. **对 env-token-manager 工作机制的误解** - 以为是自动存储,实际需要手动设置环境变量
2. **忽略成功日志中的操作指示** - 日志明确说明需要设置环境变量
3. **症状治疗而非根本治疗** - 过度修改代码而不是理解现有架构

### 正确的诊断顺序
1. 完整的认证链路检查 (OAuth → Token交换 → Token存储 → Token读取 → API认证)
2. 仔细阅读现有代码逻辑和存储机制
3. 关注成功日志而不只是错误日志
4. 理解不同存储策略的工作方式

### 优化建议
- **先理解再修改**: 在添加新代码之前完全理解现有实现
- **日志是朋友**: 成功日志和错误日志一样重要
- **最小可行诊断**: 先找到根本原因再进行修复

## 最终结果

- ✅ OAuth 认证流程完全正常工作
- ✅ 书签页面正确显示内容
- ✅ 认证状态正确显示
- ✅ 构建过程不再因认证问题失败
- ✅ 所有 API 端点使用统一的 token manager 选择逻辑

**总耗时**: 约3小时
**实际需要时间**: 约10分钟(如果直接识别根本原因)

---

*记录者: Claude Code*
*最后更新: 2025-08-31 23:00*
Loading
Loading