[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F7ZMJVFQP7FAQJW5TH9NF8":3,"skill-same:01M6F7ZMJVFQP7FAQJW5TH9NF8":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":61,"ownerHtmlUrl":62,"ownerBlogUrl":6,"ownerDescription":63,"updatedAt":64,"install":65,"resources":78},"01M6F7ZMJVFQP7FAQJW5TH9NF8","canva-reference-architecture",null,"Implement Canva Connect API reference architecture with best-practice project layout.\nUse when designing new Canva integrations, reviewing project structure,\nor establishing architecture standards for Canva applications.\nTrigger with phrases like \"canva architecture\", \"canva project structure\",\n\"how to organize canva\", \"canva layout\", \"canva reference\".","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcanva-reference-architecture\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"reference","canva reference architecture","canva","architecture","project structure","connect","how to","trigger","io","layout","project","structure","organize","design","standards","read","integrations","how","api","code","2026-11-10 10:12:30",false,"---\nname: canva-reference-architecture\ndescription: |\n  Implement Canva Connect API reference architecture with best-practice project layout.\n  Use when designing new Canva integrations, reviewing project structure,\n  or establishing architecture standards for Canva applications.\n  Trigger with phrases like \"canva architecture\", \"canva project structure\",\n  \"how to organize canva\", \"canva layout\", \"canva reference\".\nallowed-tools: Read, Grep\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\ntags: [saas, design, canva]\ncompatible-with: claude-code\n---\n\n# Canva Reference Architecture\n\n## Overview\n\nProduction-ready architecture for Canva Connect API integrations. All interactions use the REST API at `api.canva.com\u002Frest\u002Fv1\u002F*` with OAuth 2.0 PKCE authentication.\n\n## Project Structure\n\n```\nmy-canva-integration\u002F\n├── src\u002F\n│   ├── canva\u002F\n│   │   ├── client.ts           # REST client wrapper with auto-refresh\n│   │   ├── auth.ts             # OAuth 2.0 PKCE flow\n│   │   ├── types.ts            # API request\u002Fresponse TypeScript types\n│   │   └── errors.ts           # CanvaAPIError class\n│   ├── services\u002F\n│   │   ├── design.service.ts   # Design creation, export, listing\n│   │   ├── asset.service.ts    # Asset upload and management\n│   │   ├── template.service.ts # Brand template autofill (Enterprise)\n│   │   └── folder.service.ts   # Folder management\n│   ├── routes\u002F\n│   │   ├── auth.ts             # OAuth callback endpoints\n│   │   ├── designs.ts          # Design CRUD routes\n│   │   ├── exports.ts          # Export trigger\u002Fdownload routes\n│   │   └── webhooks.ts         # Webhook receiver\n│   ├── middleware\u002F\n│   │   ├── auth.ts             # Verify user has valid Canva token\n│   │   └── rate-limit.ts       # Client-side rate limit guard\n│   ├── store\u002F\n│   │   └── tokens.ts           # Encrypted token storage (DB)\n│   └── index.ts\n├── tests\u002F\n│   ├── mocks\u002F\n│   │   └── canva-server.ts     # MSW mock server\n│   ├── unit\u002F\n│   │   └── design.service.test.ts\n│   └── integration\u002F\n│       └── canva-api.test.ts\n├── .env.example\n└── package.json\n```\n\n## Layer Architecture\n\n```\n┌─────────────────────────────────────────┐\n│             Routes Layer                │\n│   (Express\u002FNext.js — HTTP in\u002Fout)       │\n├─────────────────────────────────────────┤\n│           Service Layer                 │\n│  (Business logic, caching, validation)  │\n├─────────────────────────────────────────┤\n│          Canva Client Layer             │\n│   (REST calls, token refresh, retry)    │\n├─────────────────────────────────────────┤\n│         Infrastructure Layer            │\n│    (Token store, cache, queue)          │\n└─────────────────────────────────────────┘\n```\n\n## Service Layer Pattern\n\n```typescript\n\u002F\u002F src\u002Fservices\u002Fdesign.service.ts\nimport { CanvaClient } from '..\u002Fcanva\u002Fclient';\nimport { LRUCache } from 'lru-cache';\n\nexport class DesignService {\n  private cache = new LRUCache\u003Cstring, any>({ max: 200, ttl: 300_000 });\n\n  constructor(private canva: CanvaClient) {}\n\n  async create(opts: {\n    type: 'preset' | 'custom';\n    name?: string;\n    width?: number;\n    height?: number;\n    title: string;\n    assetId?: string;\n  }) {\n    const designType = opts.type === 'preset'\n      ? { type: 'preset' as const, name: opts.name! }\n      : { type: 'custom' as const, width: opts.width!, height: opts.height! };\n\n    return this.canva.request('\u002Fdesigns', {\n      method: 'POST',\n      body: JSON.stringify({\n        design_type: designType,\n        title: opts.title,\n        ...(opts.assetId && { asset_id: opts.assetId }),\n      }),\n    });\n  }\n\n  async get(id: string) {\n    const cached = this.cache.get(id);\n    if (cached) return cached;\n\n    const result = await this.canva.request(`\u002Fdesigns\u002F${id}`);\n    this.cache.set(id, result);\n    return result;\n  }\n\n  async export(designId: string, format: object): Promise\u003Cstring[]> {\n    \u002F\u002F Start export job\n    const { job } = await this.canva.request('\u002Fexports', {\n      method: 'POST',\n      body: JSON.stringify({ design_id: designId, format }),\n    });\n\n    \u002F\u002F Poll for completion\n    return this.pollExport(job.id);\n  }\n\n  private async pollExport(exportId: string, timeoutMs = 60000): Promise\u003Cstring[]> {\n    const start = Date.now();\n    while (Date.now() - start \u003C timeoutMs) {\n      const { job } = await this.canva.request(`\u002Fexports\u002F${exportId}`);\n      if (job.status === 'success') return job.urls;\n      if (job.status === 'failed') throw new Error(`Export failed: ${job.error?.message}`);\n      await new Promise(r => setTimeout(r, 2000));\n    }\n    throw new Error('Export timeout');\n  }\n}\n```\n\n## Data Flow\n\n```\nUser clicks \"Create Design\"\n       │\n       ▼\n┌─────────────┐\n│   Route     │  POST \u002Fapi\u002Fdesigns\n│   Handler   │\n└──────┬──────┘\n       │\n       ▼\n┌─────────────┐\n│  Design     │  Validates input, checks auth\n│  Service    │\n└──────┬──────┘\n       │\n       ▼\n┌─────────────┐\n│  Canva      │  POST api.canva.com\u002Frest\u002Fv1\u002Fdesigns\n│  Client     │  (auto-refreshes token if expired)\n└──────┬──────┘\n       │\n       ▼\n┌─────────────┐\n│  Canva      │  Returns design.id, edit_url, view_url\n│  API        │\n└─────────────┘\n       │\n       ▼\n  Redirect user to edit_url → Canva Editor\n```\n\n## Auth Middleware\n\n```typescript\n\u002F\u002F src\u002Fmiddleware\u002Fauth.ts\nexport function requireCanvaAuth(tokenStore: TokenStore) {\n  return async (req: Request, res: Response, next: NextFunction) => {\n    const userId = req.user?.id;\n    if (!userId) return res.status(401).json({ error: 'Not authenticated' });\n\n    const tokens = await tokenStore.get(userId);\n    if (!tokens) return res.status(403).json({ error: 'Canva not connected' });\n\n    \u002F\u002F Attach client to request for downstream use\n    req.canva = new CanvaClient({\n      clientId: process.env.CANVA_CLIENT_ID!,\n      clientSecret: process.env.CANVA_CLIENT_SECRET!,\n      tokens,\n      onTokenRefresh: (newTokens) => tokenStore.save(userId, newTokens),\n    });\n\n    next();\n  };\n}\n```\n\n## Error Handling\n\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| Circular dependencies | Wrong layering | Services import client, not vice versa |\n| Token not found | User hasn't connected Canva | Redirect to OAuth flow |\n| Cache stale | Design updated in Canva | Invalidate on webhook events |\n| Service timeout | Export taking too long | Increase timeout, add job queue |\n\n## Resources\n\n- [Canva Starter Kit](https:\u002F\u002Fgithub.com\u002Fcanva-sdks\u002Fcanva-connect-api-starter-kit)\n- [Canva API Reference](https:\u002F\u002Fwww.canva.dev\u002Fdocs\u002Fconnect\u002Fapi-reference\u002F)\n\n## Next Steps\n\nFor multi-environment setup, see `canva-multi-env-setup`.\n\n","# Canva Reference Architecture\n\n## Overview\n\nProduction-ready architecture for Canva Connect API integrations. All interactions use the REST API at `api.canva.com\u002Frest\u002Fv1\u002F*` with OAuth 2.0 PKCE authentication.\n\n## Project Structure\n\n```\nmy-canva-integration\u002F\n├── src\u002F\n│   ├── canva\u002F\n│   │   ├── client.ts           # REST client wrapper with auto-refresh\n│   │   ├── auth.ts             # OAuth 2.0 PKCE flow\n│   │   ├── types.ts            # API request\u002Fresponse TypeScript types\n│   │   └── errors.ts           # CanvaAPIError class\n│   ├── services\u002F\n│   │   ├── design.service.ts   # Design creation, export, listing\n│   │   ├── asset.service.ts    # Asset upload and management\n│   │   ├── template.service.ts # Brand template autofill (Enterprise)\n│   │   └── folder.service.ts   # Folder management\n│   ├── routes\u002F\n│   │   ├── auth.ts             # OAuth callback endpoints\n│   │   ├── designs.ts          # Design CRUD routes\n│   │   ├── exports.ts          # Export trigger\u002Fdownload routes\n│   │   └── webhooks.ts         # Webhook receiver\n│   ├── middleware\u002F\n│   │   ├── auth.ts             # Verify user has valid Canva token\n│   │   └── rate-limit.ts       # Client-side rate limit guard\n│   ├── store\u002F\n│   │   └── tokens.ts           # Encrypted token storage (DB)\n│   └── index.ts\n├── tests\u002F\n│   ├── mocks\u002F\n│   │   └── canva-server.ts     # MSW mock server\n│   ├── unit\u002F\n│   │   └── design.service.test.ts\n│   └── integration\u002F\n│       └── canva-api.test.ts\n├── .env.example\n└── package.json\n```\n\n## Layer Architecture\n\n```\n┌─────────────────────────────────────────┐\n│             Routes Layer                │\n│   (Express\u002FNext.js — HTTP in\u002Fout)       │\n├─────────────────────────────────────────┤\n│           Service Layer                 │\n│  (Business logic, caching, validation)  │\n├─────────────────────────────────────────┤\n│          Canva Client Layer             │\n│   (REST calls, token refresh, retry)    │\n├─────────────────────────────────────────┤\n│         Infrastructure Layer            │\n│    (Token store, cache, queue)          │\n└─────────────────────────────────────────┘\n```\n\n## Service Layer Pattern\n\n```typescript\n\u002F\u002F src\u002Fservices\u002Fdesign.service.ts\nimport { CanvaClient } from '..\u002Fcanva\u002Fclient';\nimport { LRUCache } from 'lru-cache';\n\nexport class DesignService {\n  private cache = new LRUCache\u003Cstring, any>({ max: 200, ttl: 300_000 });\n\n  constructor(private canva: CanvaClient) {}\n\n  async create(opts: {\n    type: 'preset' | 'custom';\n    name?: string;\n    width?: number;\n    height?: number;\n    title: string;\n    assetId?: string;\n  }) {\n    const designType = opts.type === 'preset'\n      ? { type: 'preset' as const, name: opts.name! }\n      : { type: 'custom' as const, width: opts.width!, height: opts.height! };\n\n    return this.canva.request('\u002Fdesigns', {\n      method: 'POST',\n      body: JSON.stringify({\n        design_type: designType,\n        title: opts.title,\n        ...(opts.assetId && { asset_id: opts.assetId }),\n      }),\n    });\n  }\n\n  async get(id: string) {\n    const cached = this.cache.get(id);\n    if (cached) return cached;\n\n    const result = await this.canva.request(`\u002Fdesigns\u002F${id}`);\n    this.cache.set(id, result);\n    return result;\n  }\n\n  async export(designId: string, format: object): Promise\u003Cstring[]> {\n    \u002F\u002F Start export job\n    const { job } = await this.canva.request('\u002Fexports', {\n      method: 'POST',\n      body: JSON.stringify({ design_id: designId, format }),\n    });\n\n    \u002F\u002F Poll for completion\n    return this.pollExport(job.id);\n  }\n\n  private async pollExport(exportId: string, timeoutMs = 60000): Promise\u003Cstring[]> {\n    const start = Date.now();\n    while (Date.now() - start \u003C timeoutMs) {\n      const { job } = await this.canva.request(`\u002Fexports\u002F${exportId}`);\n      if (job.status === 'success') return job.urls;\n      if (job.status === 'failed') throw new Error(`Export failed: ${job.error?.message}`);\n      await new Promise(r => setTimeout(r, 2000));\n    }\n    throw new Error('Export timeout');\n  }\n}\n```\n\n## Data Flow\n\n```\nUser clicks \"Create Design\"\n       │\n       ▼\n┌─────────────┐\n│   Route     │  POST \u002Fapi\u002Fdesigns\n│   Handler   │\n└──────┬──────┘\n       │\n       ▼\n┌─────────────┐\n│  Design     │  Validates input, checks auth\n│  Service    │\n└──────┬──────┘\n       │\n       ▼\n┌─────────────┐\n│  Canva      │  POST api.canva.com\u002Frest\u002Fv1\u002Fdesigns\n│  Client     │  (auto-refreshes token if expired)\n└──────┬──────┘\n       │\n       ▼\n┌─────────────┐\n│  Canva      │  Returns design.id, edit_url, view_url\n│  API        │\n└─────────────┘\n       │\n       ▼\n  Redirect user to edit_url → Canva Editor\n```\n\n## Auth Middleware\n\n```typescript\n\u002F\u002F src\u002Fmiddleware\u002Fauth.ts\nexport function requireCanvaAuth(tokenStore: TokenStore) {\n  return async (req: Request, res: Response, next: NextFunction) => {\n    const userId = req.user?.id;\n    if (!userId) return res.status(401).json({ error: 'Not authenticated' });\n\n    const tokens = await tokenStore.get(userId);\n    if (!tokens) return res.status(403).json({ error: 'Canva not connected' });\n\n    \u002F\u002F Attach client to request for downstream use\n    req.canva = new CanvaClient({\n      clientId: process.env.CANVA_CLIENT_ID!,\n      clientSecret: process.env.CANVA_CLIENT_SECRET!,\n      tokens,\n      onTokenRefresh: (newTokens) => tokenStore.save(userId, newTokens),\n    });\n\n    next();\n  };\n}\n```\n\n## Error Handling\n\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| Circular dependencies | Wrong layering | Services import client, not vice versa |\n| Token not found | User hasn't connected Canva | Redirect to OAuth flow |\n| Cache stale | Design updated in Canva | Invalidate on webhook events |\n| Service timeout | Export taking too long | Increase timeout, add job queue |\n\n## Resources\n\n- [Canva Starter Kit](https:\u002F\u002Fgithub.com\u002Fcanva-sdks\u002Fcanva-connect-api-starter-kit)\n- [Canva API Reference](https:\u002F\u002Fwww.canva.dev\u002Fdocs\u002Fconnect\u002Fapi-reference\u002F)\n\n## Next Steps\n\nFor multi-environment setup, see `canva-multi-env-setup`.\n\n",{"name":5,"tags":49,"author":51,"license":15,"version":52,"description":53,"allowed-tools":54,"compatible-with":55},[50,37,26],"saas","Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Implement Canva Connect API reference architecture with best-practice project layout.\nUse when designing new Canva integrations, reviewing project structure,\nor establishing architecture standards for Canva applications.\nTrigger with phrases like \"canva architecture\", \"canva project structure\",\n\"how to organize canva\", \"canva layout\", \"canva reference\".\n","Read, Grep","claude-code","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcanva-reference-architecture",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":66,"prompt":76,"zipAvailable":77},[67,70,73],{"tool":68,"command":69},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill canva-reference-architecture",{"tool":71,"command":72},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill canva-reference-architecture",{"tool":74,"command":75},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill canva-reference-architecture","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcanva-reference-architecture\u002FSKILL.md",true,[],[80,94,104,113,119,126],{"skillNo":81,"name":82,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":90,"publishedAt":44},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[85],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[91,92,93],"model","model hierarchy","model hierarchy skill",{"skillNo":95,"name":96,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":100,"publishedAt":44},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[99],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},[101,102,103],"MCP","zoom","zoom mcp",{"skillNo":105,"name":106,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":109,"publishedAt":44},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[110,111,112,102],"sdk","zoom apps sdk","apps",{"skillNo":114,"name":115,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":116,"categories":117,"keywords":118,"publishedAt":44},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[115],{"skillNo":120,"name":121,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":125,"publishedAt":44},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[124],{"categoryNo":86,"categoryName":87,"taxonomyType":88,"taxonomyTypeDesc":89},[121],{"skillNo":127,"name":128,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":44},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[128]]