@brainfish-ai/devdoc 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +33 -0
- package/README.md +415 -0
- package/bin/devdoc.js +13 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/check.d.ts +1 -0
- package/dist/cli/commands/check.js +143 -0
- package/dist/cli/commands/create.d.ts +24 -0
- package/dist/cli/commands/create.js +387 -0
- package/dist/cli/commands/deploy.d.ts +9 -0
- package/dist/cli/commands/deploy.js +433 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +139 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.js +238 -0
- package/dist/cli/commands/keys.d.ts +12 -0
- package/dist/cli/commands/keys.js +165 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.js +56 -0
- package/dist/cli/commands/upload.d.ts +13 -0
- package/dist/cli/commands/upload.js +238 -0
- package/dist/cli/commands/whoami.d.ts +8 -0
- package/dist/cli/commands/whoami.js +91 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/config/index.d.ts +80 -0
- package/dist/config/index.js +133 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.js +50 -0
- package/package.json +51 -0
- package/renderer/app/api/assets/[...path]/route.ts +123 -0
- package/renderer/app/api/assets/route.ts +124 -0
- package/renderer/app/api/assets/upload/route.ts +177 -0
- package/renderer/app/api/auth-schemes/route.ts +77 -0
- package/renderer/app/api/chat/route.ts +858 -0
- package/renderer/app/api/codegen/route.ts +72 -0
- package/renderer/app/api/collections/route.ts +1016 -0
- package/renderer/app/api/debug/route.ts +53 -0
- package/renderer/app/api/deploy/route.ts +234 -0
- package/renderer/app/api/device/route.ts +42 -0
- package/renderer/app/api/docs/route.ts +187 -0
- package/renderer/app/api/keys/regenerate/route.ts +80 -0
- package/renderer/app/api/openapi-spec/route.ts +151 -0
- package/renderer/app/api/projects/[slug]/route.ts +153 -0
- package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
- package/renderer/app/api/projects/register/route.ts +152 -0
- package/renderer/app/api/proxy/route.ts +149 -0
- package/renderer/app/api/proxy-stream/route.ts +168 -0
- package/renderer/app/api/redirects/route.ts +47 -0
- package/renderer/app/api/schema/route.ts +65 -0
- package/renderer/app/api/subdomains/check/route.ts +172 -0
- package/renderer/app/api/suggestions/route.ts +144 -0
- package/renderer/app/favicon.ico +0 -0
- package/renderer/app/globals.css +1103 -0
- package/renderer/app/layout.tsx +47 -0
- package/renderer/app/llms-full.txt/route.ts +346 -0
- package/renderer/app/llms.txt/route.ts +279 -0
- package/renderer/app/page.tsx +14 -0
- package/renderer/app/robots.txt/route.ts +84 -0
- package/renderer/app/sitemap.xml/route.ts +199 -0
- package/renderer/components/docs/index.ts +12 -0
- package/renderer/components/docs/mdx/accordion.tsx +169 -0
- package/renderer/components/docs/mdx/badge.tsx +132 -0
- package/renderer/components/docs/mdx/callouts.tsx +154 -0
- package/renderer/components/docs/mdx/cards.tsx +213 -0
- package/renderer/components/docs/mdx/changelog.tsx +120 -0
- package/renderer/components/docs/mdx/code-block.tsx +186 -0
- package/renderer/components/docs/mdx/code-group.tsx +421 -0
- package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
- package/renderer/components/docs/mdx/frame.tsx +112 -0
- package/renderer/components/docs/mdx/highlight.tsx +151 -0
- package/renderer/components/docs/mdx/iframe.tsx +134 -0
- package/renderer/components/docs/mdx/image.tsx +235 -0
- package/renderer/components/docs/mdx/index.ts +204 -0
- package/renderer/components/docs/mdx/mermaid.tsx +240 -0
- package/renderer/components/docs/mdx/param-field.tsx +200 -0
- package/renderer/components/docs/mdx/steps.tsx +113 -0
- package/renderer/components/docs/mdx/tabs.tsx +86 -0
- package/renderer/components/docs/mdx-renderer.tsx +100 -0
- package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
- package/renderer/components/docs/navigation/index.ts +8 -0
- package/renderer/components/docs/navigation/page-nav.tsx +64 -0
- package/renderer/components/docs/navigation/sidebar.tsx +515 -0
- package/renderer/components/docs/navigation/toc.tsx +113 -0
- package/renderer/components/docs/notice.tsx +105 -0
- package/renderer/components/docs-header.tsx +274 -0
- package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
- package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
- package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
- package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
- package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
- package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
- package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
- package/renderer/components/docs-viewer/agent/index.tsx +6 -0
- package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
- package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
- package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
- package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
- package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
- package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
- package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
- package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
- package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
- package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
- package/renderer/components/docs-viewer/content/index.tsx +29 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
- package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
- package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
- package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
- package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
- package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
- package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
- package/renderer/components/docs-viewer/index.tsx +1466 -0
- package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
- package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
- package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
- package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
- package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
- package/renderer/components/docs-viewer/playground/index.tsx +671 -0
- package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
- package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
- package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
- package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
- package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
- package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
- package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
- package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
- package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
- package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
- package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
- package/renderer/components/docs-viewer/search/index.ts +2 -0
- package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
- package/renderer/components/docs-viewer/search/use-search.ts +117 -0
- package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
- package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
- package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
- package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
- package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
- package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
- package/renderer/components/theme-provider.tsx +11 -0
- package/renderer/components/theme-toggle.tsx +76 -0
- package/renderer/components/ui/badge.tsx +46 -0
- package/renderer/components/ui/button.tsx +59 -0
- package/renderer/components/ui/dialog.tsx +118 -0
- package/renderer/components/ui/dropdown-menu.tsx +257 -0
- package/renderer/components/ui/input.tsx +21 -0
- package/renderer/components/ui/label.tsx +24 -0
- package/renderer/components/ui/navigation-menu.tsx +168 -0
- package/renderer/components/ui/select.tsx +190 -0
- package/renderer/components/ui/spinner.tsx +114 -0
- package/renderer/components/ui/tabs.tsx +66 -0
- package/renderer/components/ui/tooltip.tsx +61 -0
- package/renderer/hooks/use-code-copy.ts +88 -0
- package/renderer/hooks/use-openapi-title.ts +44 -0
- package/renderer/lib/api-docs/agent/index.ts +6 -0
- package/renderer/lib/api-docs/agent/indexer.ts +323 -0
- package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
- package/renderer/lib/api-docs/agent/types.ts +116 -0
- package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
- package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
- package/renderer/lib/api-docs/auth/crypto.ts +89 -0
- package/renderer/lib/api-docs/auth/index.ts +4 -0
- package/renderer/lib/api-docs/code-editor/db.ts +164 -0
- package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
- package/renderer/lib/api-docs/code-editor/index.ts +6 -0
- package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
- package/renderer/lib/api-docs/code-editor/types.ts +105 -0
- package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
- package/renderer/lib/api-docs/codegen/har.ts +251 -0
- package/renderer/lib/api-docs/codegen/index.ts +159 -0
- package/renderer/lib/api-docs/factories.ts +151 -0
- package/renderer/lib/api-docs/index.ts +17 -0
- package/renderer/lib/api-docs/mobile-context.tsx +112 -0
- package/renderer/lib/api-docs/navigation-context.tsx +88 -0
- package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
- package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
- package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
- package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
- package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
- package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
- package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
- package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
- package/renderer/lib/api-docs/playground/context.tsx +107 -0
- package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
- package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
- package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
- package/renderer/lib/api-docs/playground/types.ts +35 -0
- package/renderer/lib/api-docs/types.ts +269 -0
- package/renderer/lib/api-docs/utils.ts +311 -0
- package/renderer/lib/cache.ts +193 -0
- package/renderer/lib/docs/config/index.ts +29 -0
- package/renderer/lib/docs/config/loader.ts +142 -0
- package/renderer/lib/docs/config/schema.ts +298 -0
- package/renderer/lib/docs/index.ts +12 -0
- package/renderer/lib/docs/mdx/compiler.ts +176 -0
- package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
- package/renderer/lib/docs/mdx/index.ts +26 -0
- package/renderer/lib/docs/navigation/generator.ts +348 -0
- package/renderer/lib/docs/navigation/index.ts +12 -0
- package/renderer/lib/docs/navigation/types.ts +123 -0
- package/renderer/lib/docs-navigation-context.tsx +80 -0
- package/renderer/lib/multi-tenant/context.ts +105 -0
- package/renderer/lib/storage/blob.ts +845 -0
- package/renderer/lib/utils.ts +6 -0
- package/renderer/next.config.ts +76 -0
- package/renderer/package.json +66 -0
- package/renderer/postcss.config.mjs +5 -0
- package/renderer/public/assets/images/screenshot.png +0 -0
- package/renderer/public/assets/logo/dark.svg +9 -0
- package/renderer/public/assets/logo/light.svg +9 -0
- package/renderer/public/assets/logo.svg +9 -0
- package/renderer/public/file.svg +1 -0
- package/renderer/public/globe.svg +1 -0
- package/renderer/public/icon.png +0 -0
- package/renderer/public/logo.svg +9 -0
- package/renderer/public/window.svg +1 -0
- package/renderer/tsconfig.json +28 -0
- package/templates/basic/README.md +139 -0
- package/templates/basic/assets/favicon.svg +4 -0
- package/templates/basic/assets/logo.svg +9 -0
- package/templates/basic/docs.json +47 -0
- package/templates/basic/guides/configuration.mdx +149 -0
- package/templates/basic/guides/overview.mdx +96 -0
- package/templates/basic/index.mdx +39 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/quickstart.mdx +92 -0
- package/templates/basic/vercel.json +6 -0
- package/templates/graphql/README.md +139 -0
- package/templates/graphql/api-reference/schema.graphql +305 -0
- package/templates/graphql/assets/favicon.svg +4 -0
- package/templates/graphql/assets/logo.svg +9 -0
- package/templates/graphql/docs.json +54 -0
- package/templates/graphql/guides/configuration.mdx +149 -0
- package/templates/graphql/guides/overview.mdx +96 -0
- package/templates/graphql/index.mdx +39 -0
- package/templates/graphql/package.json +14 -0
- package/templates/graphql/quickstart.mdx +92 -0
- package/templates/graphql/vercel.json +6 -0
- package/templates/openapi/README.md +139 -0
- package/templates/openapi/api-reference/openapi.json +419 -0
- package/templates/openapi/assets/favicon.svg +4 -0
- package/templates/openapi/assets/logo.svg +9 -0
- package/templates/openapi/docs.json +61 -0
- package/templates/openapi/guides/configuration.mdx +149 -0
- package/templates/openapi/guides/overview.mdx +96 -0
- package/templates/openapi/index.mdx +39 -0
- package/templates/openapi/package.json +14 -0
- package/templates/openapi/quickstart.mdx +92 -0
- package/templates/openapi/vercel.json +6 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Mode Types
|
|
3
|
+
* Types for the AI-powered API documentation assistant
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BrainfishRESTRequest } from '../types'
|
|
7
|
+
|
|
8
|
+
// Chat message types
|
|
9
|
+
export interface AgentMessage {
|
|
10
|
+
id: string
|
|
11
|
+
role: 'user' | 'assistant' | 'system'
|
|
12
|
+
content: string
|
|
13
|
+
createdAt: Date
|
|
14
|
+
toolInvocations?: ToolInvocation[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToolInvocation {
|
|
18
|
+
toolCallId: string
|
|
19
|
+
toolName: string
|
|
20
|
+
args: Record<string, unknown>
|
|
21
|
+
result?: unknown
|
|
22
|
+
state: 'pending' | 'result' | 'error'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Endpoint index for AI context
|
|
26
|
+
export interface EndpointIndex {
|
|
27
|
+
id: string
|
|
28
|
+
name: string
|
|
29
|
+
method: string
|
|
30
|
+
path: string
|
|
31
|
+
description: string | null
|
|
32
|
+
parameters: string[]
|
|
33
|
+
hasBody: boolean
|
|
34
|
+
tags: string[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Prefill data from agent
|
|
38
|
+
export interface PrefillData {
|
|
39
|
+
params?: Array<{ key: string; value: string }>
|
|
40
|
+
headers?: Array<{ key: string; value: string }>
|
|
41
|
+
body?: string
|
|
42
|
+
pathVariables?: Array<{ key: string; value: string }>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Agent context state
|
|
46
|
+
export interface AgentState {
|
|
47
|
+
mode: 'agent' | 'docs'
|
|
48
|
+
messages: AgentMessage[]
|
|
49
|
+
isLoading: boolean
|
|
50
|
+
currentEndpoint: BrainfishRESTRequest | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Tool definitions
|
|
54
|
+
export type AgentToolName =
|
|
55
|
+
| 'search_endpoints'
|
|
56
|
+
| 'navigate_to_endpoint'
|
|
57
|
+
| 'prefill_parameters'
|
|
58
|
+
| 'get_endpoint_details'
|
|
59
|
+
| 'get_current_request'
|
|
60
|
+
| 'check_request_validity'
|
|
61
|
+
| 'send_request'
|
|
62
|
+
| 'switch_to_notes'
|
|
63
|
+
| 'list_notes'
|
|
64
|
+
| 'open_file'
|
|
65
|
+
| 'create_folder'
|
|
66
|
+
| 'create_file'
|
|
67
|
+
| 'write_file'
|
|
68
|
+
| 'delete_file'
|
|
69
|
+
|
|
70
|
+
// Request validation result
|
|
71
|
+
export interface RequestValidationResult {
|
|
72
|
+
isValid: boolean
|
|
73
|
+
endpoint: {
|
|
74
|
+
id: string
|
|
75
|
+
name: string
|
|
76
|
+
method: string
|
|
77
|
+
path: string
|
|
78
|
+
}
|
|
79
|
+
validation: {
|
|
80
|
+
pathParams: {
|
|
81
|
+
required: string[]
|
|
82
|
+
filled: string[]
|
|
83
|
+
missing: string[]
|
|
84
|
+
}
|
|
85
|
+
queryParams: {
|
|
86
|
+
required: string[]
|
|
87
|
+
filled: string[]
|
|
88
|
+
missing: string[]
|
|
89
|
+
}
|
|
90
|
+
body: {
|
|
91
|
+
required: boolean
|
|
92
|
+
hasContent: boolean
|
|
93
|
+
requiredFields: string[]
|
|
94
|
+
missingFields: string[]
|
|
95
|
+
}
|
|
96
|
+
auth: {
|
|
97
|
+
required: boolean
|
|
98
|
+
configured: boolean
|
|
99
|
+
type: string | null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
summary: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Search results
|
|
106
|
+
export interface EndpointSearchResult {
|
|
107
|
+
endpoint: EndpointIndex
|
|
108
|
+
score: number
|
|
109
|
+
matchReason: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Navigation action
|
|
113
|
+
export interface NavigationAction {
|
|
114
|
+
endpointId: string
|
|
115
|
+
reason: string
|
|
116
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useEffect,
|
|
8
|
+
useCallback,
|
|
9
|
+
useRef,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from 'react'
|
|
12
|
+
import type { BrainfishRESTAuth } from '../types'
|
|
13
|
+
import { authStorage, fetchDeviceId } from './auth-storage'
|
|
14
|
+
|
|
15
|
+
/** Security scheme info from OpenAPI spec */
|
|
16
|
+
export interface SpecSecurityScheme {
|
|
17
|
+
name: string
|
|
18
|
+
type: 'basic' | 'bearer' | 'api-key' | 'oauth2' | 'none'
|
|
19
|
+
auth: BrainfishRESTAuth
|
|
20
|
+
description?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AuthContextValue {
|
|
24
|
+
/** Current global authentication configuration */
|
|
25
|
+
globalAuth: BrainfishRESTAuth | null
|
|
26
|
+
/** Whether global auth is configured and active */
|
|
27
|
+
isAuthenticated: boolean
|
|
28
|
+
/** Set the global authentication */
|
|
29
|
+
setGlobalAuth: (auth: BrainfishRESTAuth) => void
|
|
30
|
+
/** Clear the global authentication */
|
|
31
|
+
clearAuth: () => void
|
|
32
|
+
/** Get effective auth for an endpoint (global or override) */
|
|
33
|
+
getEffectiveAuth: (endpointAuth: BrainfishRESTAuth) => BrainfishRESTAuth
|
|
34
|
+
/** Check if using global auth for an endpoint */
|
|
35
|
+
isUsingGlobalAuth: (endpointAuth: BrainfishRESTAuth) => boolean
|
|
36
|
+
/** Security schemes from the OpenAPI spec */
|
|
37
|
+
specSchemes: SpecSecurityScheme[]
|
|
38
|
+
/** Default auth recommended by the spec */
|
|
39
|
+
specDefaultAuth: BrainfishRESTAuth | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const AuthContext = createContext<AuthContextValue | null>(null)
|
|
43
|
+
|
|
44
|
+
const DEFAULT_AUTH: BrainfishRESTAuth = { authType: 'none', authActive: true }
|
|
45
|
+
|
|
46
|
+
interface AuthProviderProps {
|
|
47
|
+
children: ReactNode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function AuthProvider({ children }: AuthProviderProps) {
|
|
51
|
+
const [globalAuth, setGlobalAuthState] = useState<BrainfishRESTAuth | null>(null)
|
|
52
|
+
const [isInitialized, setIsInitialized] = useState(false)
|
|
53
|
+
const [specSchemes, setSpecSchemes] = useState<SpecSecurityScheme[]>([])
|
|
54
|
+
const [specDefaultAuth, setSpecDefaultAuth] = useState<BrainfishRESTAuth | null>(null)
|
|
55
|
+
const deviceIdRef = useRef<string | null>(null)
|
|
56
|
+
|
|
57
|
+
// Load auth from localStorage on mount (async with encryption)
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
async function initializeAuth() {
|
|
60
|
+
try {
|
|
61
|
+
// Fetch device ID from backend (required for encryption)
|
|
62
|
+
const deviceId = await fetchDeviceId()
|
|
63
|
+
deviceIdRef.current = deviceId
|
|
64
|
+
|
|
65
|
+
// Load encrypted auth from storage
|
|
66
|
+
const stored = await authStorage.load(deviceId)
|
|
67
|
+
if (stored) {
|
|
68
|
+
setGlobalAuthState(stored)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fetch security schemes from the spec
|
|
72
|
+
const schemesResponse = await fetch('/api/auth-schemes')
|
|
73
|
+
if (schemesResponse.ok) {
|
|
74
|
+
const data = await schemesResponse.json()
|
|
75
|
+
setSpecSchemes(data.schemes || [])
|
|
76
|
+
setSpecDefaultAuth(data.defaultAuth || null)
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('[AuthProvider] Failed to initialize auth:', error)
|
|
80
|
+
// Clear any potentially corrupted data
|
|
81
|
+
authStorage.clear()
|
|
82
|
+
} finally {
|
|
83
|
+
setIsInitialized(true)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
initializeAuth()
|
|
88
|
+
}, [])
|
|
89
|
+
|
|
90
|
+
const setGlobalAuth = useCallback(async (auth: BrainfishRESTAuth) => {
|
|
91
|
+
if (!deviceIdRef.current) {
|
|
92
|
+
console.error('[AuthProvider] Cannot save auth: no device ID')
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
setGlobalAuthState(auth)
|
|
96
|
+
await authStorage.save(auth, deviceIdRef.current)
|
|
97
|
+
}, [])
|
|
98
|
+
|
|
99
|
+
const clearAuth = useCallback(() => {
|
|
100
|
+
setGlobalAuthState(null)
|
|
101
|
+
authStorage.clear()
|
|
102
|
+
}, [])
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get effective auth for an endpoint
|
|
106
|
+
* - If endpoint has 'inherit' auth type, use global auth
|
|
107
|
+
* - If endpoint has 'none' auth type and global is set, use global
|
|
108
|
+
* - Otherwise use endpoint's own auth
|
|
109
|
+
*/
|
|
110
|
+
const getEffectiveAuth = useCallback(
|
|
111
|
+
(endpointAuth: BrainfishRESTAuth): BrainfishRESTAuth => {
|
|
112
|
+
// If endpoint explicitly wants to inherit, use global
|
|
113
|
+
if (endpointAuth.authType === 'inherit') {
|
|
114
|
+
return globalAuth || DEFAULT_AUTH
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If endpoint has no auth but global is configured, suggest global
|
|
118
|
+
// but don't override - this allows explicit "no auth" endpoints
|
|
119
|
+
if (endpointAuth.authType === 'none' && globalAuth && globalAuth.authType !== 'none') {
|
|
120
|
+
// Return global auth but the UI should indicate it's using global
|
|
121
|
+
return globalAuth
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Use endpoint's own auth
|
|
125
|
+
return endpointAuth
|
|
126
|
+
},
|
|
127
|
+
[globalAuth]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if an endpoint is using global auth
|
|
132
|
+
*/
|
|
133
|
+
const isUsingGlobalAuth = useCallback(
|
|
134
|
+
(endpointAuth: BrainfishRESTAuth): boolean => {
|
|
135
|
+
if (!globalAuth || globalAuth.authType === 'none') {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Using global if inheriting or if endpoint has no auth
|
|
140
|
+
return (
|
|
141
|
+
endpointAuth.authType === 'inherit' ||
|
|
142
|
+
endpointAuth.authType === 'none'
|
|
143
|
+
)
|
|
144
|
+
},
|
|
145
|
+
[globalAuth]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
const isAuthenticated =
|
|
149
|
+
globalAuth !== null && globalAuth.authType !== 'none'
|
|
150
|
+
|
|
151
|
+
const value: AuthContextValue = {
|
|
152
|
+
globalAuth,
|
|
153
|
+
isAuthenticated,
|
|
154
|
+
setGlobalAuth,
|
|
155
|
+
clearAuth,
|
|
156
|
+
getEffectiveAuth,
|
|
157
|
+
isUsingGlobalAuth,
|
|
158
|
+
specSchemes,
|
|
159
|
+
specDefaultAuth,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Don't render until we've checked sessionStorage
|
|
163
|
+
if (!isInitialized) {
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function useAuth() {
|
|
171
|
+
const context = useContext(AuthContext)
|
|
172
|
+
if (!context) {
|
|
173
|
+
throw new Error('useAuth must be used within an AuthProvider')
|
|
174
|
+
}
|
|
175
|
+
return context
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get a human-readable label for auth type
|
|
180
|
+
*/
|
|
181
|
+
export function getAuthTypeLabel(auth: BrainfishRESTAuth | null | undefined): string {
|
|
182
|
+
if (!auth || !auth.authType) {
|
|
183
|
+
return 'Not configured'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
switch (auth.authType) {
|
|
187
|
+
case 'none':
|
|
188
|
+
return 'No Auth'
|
|
189
|
+
case 'inherit':
|
|
190
|
+
return 'Inherited'
|
|
191
|
+
case 'basic':
|
|
192
|
+
return 'Basic Auth'
|
|
193
|
+
case 'bearer':
|
|
194
|
+
return 'Bearer Token'
|
|
195
|
+
case 'api-key':
|
|
196
|
+
return `API Key (${auth.key || 'key'})`
|
|
197
|
+
case 'oauth-2':
|
|
198
|
+
return 'OAuth 2.0'
|
|
199
|
+
default:
|
|
200
|
+
return 'Not configured'
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get a masked preview of auth value
|
|
206
|
+
*/
|
|
207
|
+
export function getAuthPreview(auth: BrainfishRESTAuth): string {
|
|
208
|
+
switch (auth.authType) {
|
|
209
|
+
case 'none':
|
|
210
|
+
case 'inherit':
|
|
211
|
+
return ''
|
|
212
|
+
case 'basic':
|
|
213
|
+
return auth.username ? `${auth.username}:****` : ''
|
|
214
|
+
case 'bearer':
|
|
215
|
+
return auth.token ? `${auth.token.slice(0, 8)}••••` : ''
|
|
216
|
+
case 'api-key':
|
|
217
|
+
return auth.value ? `${auth.value.slice(0, 4)}••••` : ''
|
|
218
|
+
case 'oauth-2':
|
|
219
|
+
return auth.grantTypeInfo.token
|
|
220
|
+
? `${auth.grantTypeInfo.token.slice(0, 8)}••••`
|
|
221
|
+
: ''
|
|
222
|
+
default:
|
|
223
|
+
return ''
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { BrainfishRESTAuth } from '../types'
|
|
4
|
+
import { encrypt, decrypt } from './crypto'
|
|
5
|
+
|
|
6
|
+
const AUTH_STORAGE_KEY = 'brainfish-api-global-auth'
|
|
7
|
+
|
|
8
|
+
export interface StoredAuth {
|
|
9
|
+
globalAuth: BrainfishRESTAuth
|
|
10
|
+
timestamp: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fetch device ID from backend API
|
|
15
|
+
* The device ID is stored in an httpOnly cookie
|
|
16
|
+
* Throws error if device ID cannot be fetched (no fallback)
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchDeviceId(): Promise<string> {
|
|
19
|
+
const response = await fetch('/api/device', {
|
|
20
|
+
credentials: 'include',
|
|
21
|
+
})
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error('Failed to fetch device ID')
|
|
24
|
+
}
|
|
25
|
+
const data = await response.json()
|
|
26
|
+
if (!data.deviceId) {
|
|
27
|
+
throw new Error('No device ID returned from server')
|
|
28
|
+
}
|
|
29
|
+
return data.deviceId
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Encrypted localStorage wrapper for global authentication
|
|
34
|
+
* Uses device ID from backend as encryption key
|
|
35
|
+
* No fallbacks - encryption is required
|
|
36
|
+
*/
|
|
37
|
+
export const authStorage = {
|
|
38
|
+
/**
|
|
39
|
+
* Save auth configuration to localStorage (encrypted)
|
|
40
|
+
* Requires valid device ID - throws if missing
|
|
41
|
+
*/
|
|
42
|
+
async save(auth: BrainfishRESTAuth, deviceId: string): Promise<void> {
|
|
43
|
+
if (!deviceId) {
|
|
44
|
+
throw new Error('Device ID required for secure storage')
|
|
45
|
+
}
|
|
46
|
+
const data: StoredAuth = {
|
|
47
|
+
globalAuth: auth,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
}
|
|
50
|
+
const encrypted = await encrypt(JSON.stringify(data), deviceId)
|
|
51
|
+
localStorage.setItem(AUTH_STORAGE_KEY, encrypted)
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load auth configuration from localStorage (decrypted)
|
|
56
|
+
* Returns null if no data or decryption fails
|
|
57
|
+
*/
|
|
58
|
+
async load(deviceId: string): Promise<BrainfishRESTAuth | null> {
|
|
59
|
+
if (!deviceId) return null
|
|
60
|
+
|
|
61
|
+
const stored = localStorage.getItem(AUTH_STORAGE_KEY)
|
|
62
|
+
if (!stored) return null
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const jsonData = await decrypt(stored, deviceId)
|
|
66
|
+
const data = JSON.parse(jsonData) as StoredAuth
|
|
67
|
+
|
|
68
|
+
if (!data.globalAuth?.authType) {
|
|
69
|
+
localStorage.removeItem(AUTH_STORAGE_KEY)
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return data.globalAuth
|
|
74
|
+
} catch {
|
|
75
|
+
// Decryption failed - clear corrupted/invalid data
|
|
76
|
+
localStorage.removeItem(AUTH_STORAGE_KEY)
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Clear stored auth configuration
|
|
83
|
+
*/
|
|
84
|
+
clear(): void {
|
|
85
|
+
localStorage.removeItem(AUTH_STORAGE_KEY)
|
|
86
|
+
},
|
|
87
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Web Crypto API utilities for encrypting/decrypting auth credentials
|
|
5
|
+
* Uses AES-GCM with a key derived from device ID
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ALGORITHM = 'AES-GCM'
|
|
9
|
+
const KEY_LENGTH = 256
|
|
10
|
+
const IV_LENGTH = 12 // 96 bits for AES-GCM
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Derive a crypto key from a device ID using PBKDF2
|
|
14
|
+
*/
|
|
15
|
+
async function deriveKey(deviceId: string): Promise<CryptoKey> {
|
|
16
|
+
const encoder = new TextEncoder()
|
|
17
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
18
|
+
'raw',
|
|
19
|
+
encoder.encode(deviceId),
|
|
20
|
+
'PBKDF2',
|
|
21
|
+
false,
|
|
22
|
+
['deriveKey']
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Use a fixed salt (could be made dynamic per-user if needed)
|
|
26
|
+
const salt = encoder.encode('brainfish-api-docs-v1')
|
|
27
|
+
|
|
28
|
+
return crypto.subtle.deriveKey(
|
|
29
|
+
{
|
|
30
|
+
name: 'PBKDF2',
|
|
31
|
+
salt,
|
|
32
|
+
iterations: 100000,
|
|
33
|
+
hash: 'SHA-256',
|
|
34
|
+
},
|
|
35
|
+
keyMaterial,
|
|
36
|
+
{ name: ALGORITHM, length: KEY_LENGTH },
|
|
37
|
+
false,
|
|
38
|
+
['encrypt', 'decrypt']
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Encrypt a string using AES-GCM with device ID as key
|
|
44
|
+
*/
|
|
45
|
+
export async function encrypt(plaintext: string, deviceId: string): Promise<string> {
|
|
46
|
+
const key = await deriveKey(deviceId)
|
|
47
|
+
const encoder = new TextEncoder()
|
|
48
|
+
const data = encoder.encode(plaintext)
|
|
49
|
+
|
|
50
|
+
// Generate random IV
|
|
51
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))
|
|
52
|
+
|
|
53
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
54
|
+
{ name: ALGORITHM, iv },
|
|
55
|
+
key,
|
|
56
|
+
data
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// Combine IV + encrypted data and encode as base64
|
|
60
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength)
|
|
61
|
+
combined.set(iv)
|
|
62
|
+
combined.set(new Uint8Array(encrypted), iv.length)
|
|
63
|
+
|
|
64
|
+
return btoa(String.fromCharCode(...combined))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decrypt a string using AES-GCM with device ID as key
|
|
69
|
+
*/
|
|
70
|
+
export async function decrypt(ciphertext: string, deviceId: string): Promise<string> {
|
|
71
|
+
const key = await deriveKey(deviceId)
|
|
72
|
+
|
|
73
|
+
// Decode base64
|
|
74
|
+
const combined = Uint8Array.from(atob(ciphertext), (c) => c.charCodeAt(0))
|
|
75
|
+
|
|
76
|
+
// Extract IV and encrypted data
|
|
77
|
+
const iv = combined.slice(0, IV_LENGTH)
|
|
78
|
+
const encrypted = combined.slice(IV_LENGTH)
|
|
79
|
+
|
|
80
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
81
|
+
{ name: ALGORITHM, iv },
|
|
82
|
+
key,
|
|
83
|
+
encrypted
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const decoder = new TextDecoder()
|
|
87
|
+
return decoder.decode(decrypted)
|
|
88
|
+
}
|
|
89
|
+
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import Dexie, { type Table } from 'dexie'
|
|
2
|
+
import type { Workspace, NoteFile, EditorState } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simplified database for Notes workspace
|
|
6
|
+
* - One workspace per API (not multiple projects)
|
|
7
|
+
* - Notes are files for brainstorming (code, diagrams, markdown)
|
|
8
|
+
*/
|
|
9
|
+
class NotesDatabase extends Dexie {
|
|
10
|
+
workspaces!: Table<Workspace>
|
|
11
|
+
notes!: Table<NoteFile>
|
|
12
|
+
editorStates!: Table<EditorState>
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super('brainfish-notes')
|
|
16
|
+
|
|
17
|
+
this.version(1).stores({
|
|
18
|
+
// Single workspace per API
|
|
19
|
+
workspaces: 'id, apiSpecUrl, updatedAt',
|
|
20
|
+
|
|
21
|
+
// Notes/files in workspace
|
|
22
|
+
notes: 'id, workspaceId, [workspaceId+path], updatedAt',
|
|
23
|
+
|
|
24
|
+
// Editor state per workspace
|
|
25
|
+
editorStates: 'workspaceId',
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Singleton database instance
|
|
31
|
+
export const db = new NotesDatabase()
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate a workspace ID from API spec URL
|
|
35
|
+
*/
|
|
36
|
+
export function getWorkspaceId(apiSpecUrl: string): string {
|
|
37
|
+
// Simple hash for consistent ID
|
|
38
|
+
let hash = 0
|
|
39
|
+
for (let i = 0; i < apiSpecUrl.length; i++) {
|
|
40
|
+
const char = apiSpecUrl.charCodeAt(i)
|
|
41
|
+
hash = ((hash << 5) - hash) + char
|
|
42
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
43
|
+
}
|
|
44
|
+
return `ws-${Math.abs(hash).toString(16)}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Database operations - simplified for notes workspace
|
|
49
|
+
*/
|
|
50
|
+
export const dbOperations = {
|
|
51
|
+
// Workspace operations (auto-created, one per API)
|
|
52
|
+
|
|
53
|
+
async getOrCreateWorkspace(apiSpecUrl: string, name: string): Promise<Workspace> {
|
|
54
|
+
const id = getWorkspaceId(apiSpecUrl)
|
|
55
|
+
|
|
56
|
+
let workspace = await db.workspaces.get(id)
|
|
57
|
+
|
|
58
|
+
if (!workspace) {
|
|
59
|
+
workspace = {
|
|
60
|
+
id,
|
|
61
|
+
name,
|
|
62
|
+
apiSpecUrl,
|
|
63
|
+
createdAt: new Date(),
|
|
64
|
+
updatedAt: new Date(),
|
|
65
|
+
}
|
|
66
|
+
await db.workspaces.add(workspace)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return workspace
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async getWorkspace(id: string): Promise<Workspace | undefined> {
|
|
73
|
+
return db.workspaces.get(id)
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async updateWorkspace(id: string, updates: Partial<Workspace>): Promise<void> {
|
|
77
|
+
await db.workspaces.update(id, {
|
|
78
|
+
...updates,
|
|
79
|
+
updatedAt: new Date(),
|
|
80
|
+
})
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Note/file operations
|
|
84
|
+
|
|
85
|
+
async createNote(workspaceId: string, path: string, content: string = ''): Promise<NoteFile> {
|
|
86
|
+
const note: NoteFile = {
|
|
87
|
+
id: crypto.randomUUID(),
|
|
88
|
+
workspaceId,
|
|
89
|
+
path,
|
|
90
|
+
content,
|
|
91
|
+
createdAt: new Date(),
|
|
92
|
+
updatedAt: new Date(),
|
|
93
|
+
}
|
|
94
|
+
await db.notes.add(note)
|
|
95
|
+
|
|
96
|
+
// Update workspace timestamp
|
|
97
|
+
await db.workspaces.update(workspaceId, { updatedAt: new Date() })
|
|
98
|
+
|
|
99
|
+
return note
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async getNote(workspaceId: string, path: string): Promise<NoteFile | undefined> {
|
|
103
|
+
return db.notes
|
|
104
|
+
.where('[workspaceId+path]')
|
|
105
|
+
.equals([workspaceId, path])
|
|
106
|
+
.first()
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async listNotes(workspaceId: string): Promise<NoteFile[]> {
|
|
110
|
+
return db.notes
|
|
111
|
+
.where('workspaceId')
|
|
112
|
+
.equals(workspaceId)
|
|
113
|
+
.toArray()
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
async updateNote(workspaceId: string, path: string, content: string): Promise<void> {
|
|
117
|
+
await db.notes
|
|
118
|
+
.where('[workspaceId+path]')
|
|
119
|
+
.equals([workspaceId, path])
|
|
120
|
+
.modify({
|
|
121
|
+
content,
|
|
122
|
+
updatedAt: new Date(),
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
await db.workspaces.update(workspaceId, { updatedAt: new Date() })
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async deleteNote(workspaceId: string, path: string): Promise<void> {
|
|
129
|
+
await db.notes
|
|
130
|
+
.where('[workspaceId+path]')
|
|
131
|
+
.equals([workspaceId, path])
|
|
132
|
+
.delete()
|
|
133
|
+
|
|
134
|
+
await db.workspaces.update(workspaceId, { updatedAt: new Date() })
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
async renameNote(workspaceId: string, oldPath: string, newPath: string): Promise<void> {
|
|
138
|
+
await db.notes
|
|
139
|
+
.where('[workspaceId+path]')
|
|
140
|
+
.equals([workspaceId, oldPath])
|
|
141
|
+
.modify({
|
|
142
|
+
path: newPath,
|
|
143
|
+
updatedAt: new Date(),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
await db.workspaces.update(workspaceId, { updatedAt: new Date() })
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Editor state operations
|
|
150
|
+
|
|
151
|
+
async getEditorState(workspaceId: string): Promise<EditorState | undefined> {
|
|
152
|
+
return db.editorStates.get(workspaceId)
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
async saveEditorState(state: EditorState): Promise<void> {
|
|
156
|
+
await db.editorStates.put(state)
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Clear all notes in workspace (reset)
|
|
160
|
+
async clearWorkspace(workspaceId: string): Promise<void> {
|
|
161
|
+
await db.notes.where('workspaceId').equals(workspaceId).delete()
|
|
162
|
+
await db.editorStates.delete(workspaceId)
|
|
163
|
+
},
|
|
164
|
+
}
|