1.11 扩展和集成

好的, 让我们来详细规划第 11 部分: 扩展和集成。我们将这部分分为 3 天, 每天专注于特定的扩展和集成任务。

第 11 部分:扩展和集成 (3 天)

Day 1: 开发浏览器扩展

  1. 设置浏览器扩展项目结构 创建新的目录 browser-extension 并初始化:
mkdir browser-extension
cd browser-extension
npm init -y
npm install --save-dev webpack webpack-cli copy-webpack-plugin
  1. 创建 manifest 文件 在 browser-extension 目录下创建 manifest.json:
{
  "manifest_version": 2,
  "name": "LightweightTodo Pro Extension",
  "version": "1.0",
  "description": "Quickly add todos from any webpage",
  "permissions": ["activeTab", "storage"],
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icon16.png",
      "48": "icon48.png",
      "128": "icon128.png"
    }
  },
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  }
}
  1. 创建 popup 界面 在 browser-extension 目录下创建 popup.html:
<!DOCTYPE html>
<html>
  <head>
    <title>Add Todo</title>
  </head>
  <body>
    <form id="addTodoForm">
      <input type="text" id="todoTitle" placeholder="Todo title" required>
      <textarea id="todoDescription" placeholder="Description"></textarea>
      <button type="submit">Add Todo</button>
    </form>
    <script src="popup.js"></script>
  </body>
</html>
  1. 实现 popup 逻辑 创建 browser-extension/src/popup.ts:
document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('addTodoForm') as HTMLFormElement;
  const titleInput = document.getElementById('todoTitle') as HTMLInputElement;
  const descriptionInput = document.getElementById('todoDescription') as HTMLTextAreaElement;

  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const title = titleInput.value;
    const description = descriptionInput.value;

    try {
      const response = await fetch('https://api.lightweighttodo.com/todos', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + localStorage.getItem('token')
        },
        body: JSON.stringify({ title, description })
      });

      if (response.ok) {
        alert('Todo added successfully!');
        titleInput.value = '';
        descriptionInput.value = '';
      } else {
        throw new Error('Failed to add todo');
      }
    } catch (error) {
      alert('Error: ' + error.message);
    }
  });
});
  1. 配置 webpack 创建 browser-extension/webpack.config.js:
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  entry: './src/popup.ts',
  output: {
    filename: 'popup.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: 'manifest.json', to: 'manifest.json' },
        { from: 'popup.html', to: 'popup.html' },
        { from: 'icons', to: 'icons' },
      ],
    }),
  ],
};

Day 2: 开发公共 API

  1. 设计 API 端点 创建 api-design.md:
# LightweightTodo Pro Public API

Base URL: https://api.lightweighttodo.com/v1

## Authentication
All requests must include an `Authorization` header with a valid API key.

## Endpoints

### List Todos
GET /todos

Query Parameters:
- status: 'all' | 'active' | 'completed'
- page: number
- limit: number

### Get Todo
GET /todos/:id

### Create Todo
POST /todos

Request Body:
{
  "title": string,
  "description": string,
  "dueDate": string (ISO 8601 format),
  "tags": string[]
}

### Update Todo
PUT /todos/:id

Request Body:
{
  "title": string,
  "description": string,
  "dueDate": string (ISO 8601 format),
  "tags": string[],
  "status": 'active' | 'completed'
}

### Delete Todo
DELETE /todos/:id

### List Tags
GET /tags

### Create Tag
POST /tags

Request Body:
{
  "name": string
}

### Delete Tag
DELETE /tags/:id
  1. 实现 API 端点 在后端项目中创建新的控制器 PublicApiController.ts:
import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards } from '@nestjs/common';
import { ApiKeyGuard } from '../guards/api-key.guard';
import { TodoService } from '../services/todo.service';
import { TagService } from '../services/tag.service';

@Controller('v1')
@UseGuards(ApiKeyGuard)
export class PublicApiController {
  constructor(
    private readonly todoService: TodoService,
    private readonly tagService: TagService
  ) {}

  @Get('todos')
  async listTodos(@Query() query) {
    return this.todoService.list(query);
  }

  @Get('todos/:id')
  async getTodo(@Param('id') id: string) {
    return this.todoService.get(id);
  }

  @Post('todos')
  async createTodo(@Body() todoData) {
    return this.todoService.create(todoData);
  }

  @Put('todos/:id')
  async updateTodo(@Param('id') id: string, @Body() todoData) {
    return this.todoService.update(id, todoData);
  }

  @Delete('todos/:id')
  async deleteTodo(@Param('id') id: string) {
    return this.todoService.delete(id);
  }

  @Get('tags')
  async listTags() {
    return this.tagService.list();
  }

  @Post('tags')
  async createTag(@Body() tagData) {
    return this.tagService.create(tagData);
  }

  @Delete('tags/:id')
  async deleteTag(@Param('id') id: string) {
    return this.tagService.delete(id);
  }
}
  1. 实现 API 密钥认证 创建 src/guards/api-key.guard.ts:
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const apiKey = request.get('Authorization');

    if (!apiKey) {
      throw new UnauthorizedException('API key is missing');
    }

    // In a real application, you would validate the API key against a database
    if (apiKey !== 'valid-api-key') {
      throw new UnauthorizedException('Invalid API key');
    }

    return true;
  }
}
  1. 编写 API 文档 使用 Swagger 生成 API 文档。安装所需依赖:
npm install --save @nestjs/swagger swagger-ui-express

在 main.ts 中设置 Swagger:

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('LightweightTodo Pro API')
    .setDescription('The LightweightTodo Pro API description')
    .setVersion('1.0')
    .addApiKey({ type: 'apiKey', name: 'Authorization', in: 'header' }, 'API-Key')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

Day 3: 集成第三方服务

  1. 集成 Google Calendar 安装所需依赖:
npm install googleapis

创建 src/services/google-calendar.service.ts:

import { Injectable } from '@nestjs/common';
import { google } from 'googleapis';

@Injectable()
export class GoogleCalendarService {
  private calendar;

  constructor() {
    const auth = new google.auth.JWT(
      process.env.GOOGLE_CLIENT_EMAIL,
      null,
      process.env.GOOGLE_PRIVATE_KEY,
      ['https://www.googleapis.com/auth/calendar']
    );

    this.calendar = google.calendar({ version: 'v3', auth });
  }

  async addEvent(todo) {
    const event = {
      summary: todo.title,
      description: todo.description,
      start: {
        dateTime: todo.dueDate,
        timeZone: 'UTC',
      },
      end: {
        dateTime: todo.dueDate,
        timeZone: 'UTC',
      },
    };

    try {
      const response = await this.calendar.events.insert({
        calendarId: 'primary',
        resource: event,
      });
      return response.data;
    } catch (error) {
      console.error('Error adding event to Google Calendar:', error);
      throw error;
    }
  }
}
  1. 集成 Slack 通知 安装所需依赖:
npm install @slack/web-api

创建 src/services/slack.service.ts:

import { Injectable } from '@nestjs/common';
import { WebClient } from '@slack/web-api';

@Injectable()
export class SlackService {
  private slack: WebClient;

  constructor() {
    this.slack = new WebClient(process.env.SLACK_BOT_TOKEN);
  }

  async sendNotification(message: string) {
    try {
      await this.slack.chat.postMessage({
        channel: process.env.SLACK_CHANNEL_ID,
        text: message,
      });
    } catch (error) {
      console.error('Error sending Slack notification:', error);
      throw error;
    }
  }
}
  1. 实现集成逻辑 更新 TodoService 以使用新的集成服务:
import { Injectable } from '@nestjs/common';
import { GoogleCalendarService } from './google-calendar.service';
import { SlackService } from './slack.service';

@Injectable()
export class TodoService {
  constructor(
    private readonly googleCalendarService: GoogleCalendarService,
    private readonly slackService: SlackService
  ) {}

  async create(todoData) {
    // 创建todo的逻辑...

    // 添加到Google Calendar
    await this.googleCalendarService.addEvent(createdTodo);

    // 发送Slack通知
    await this.slackService.sendNotification(`New todo created: ${createdTodo.title}`);

    return createdTodo;
  }

  // 其他方法...
}
  1. 配置环境变量 更新 .env 文件:
GOOGLE_CLIENT_EMAIL=your-google-client-email
GOOGLE_PRIVATE_KEY=your-google-private-key
SLACK_BOT_TOKEN=your-slack-bot-token
SLACK_CHANNEL_ID=your-slack-channel-id
  1. 更新文档 在 README.md 中添加新的集成信息:
## Integrations

### Google Calendar
LightweightTodo Pro now integrates with Google Calendar. When you create a todo with a due date, it will automatically be added to your Google Calendar.

### Slack Notifications
Receive Slack notifications for important todo updates. Configure your Slack workspace and channel in the settings to enable this feature.

## API
LightweightTodo Pro now offers a public API for developers. Check out our [API documentation](api-docs.md) for more information on how to integrate LightweightTodo Pro into your own applications.

这个详细计划涵盖了第 11 部分的扩展和集成, 包括开发浏览器扩展、创建公共 API, 以及集成第三方服务如 Google Calendar 和 Slack。通过这些步骤, LightweightTodo Pro 的功能得到了显著扩展, 为用户提供了更多的使用方式和集成选项。