diff --git a/.gitignore b/.gitignore index 659429a..898e243 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ main.js # obsidian data.json + +# Claude +.claude diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..37e388e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,132 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +IMPORTANT: in all interactions and commit messages, be extremely concise and sacrifice grammar for the sake of concision. + +## Project Overview + +This is an Obsidian plugin that exposes a secure REST API for programmatic interaction with Obsidian vaults. It allows external tools and automation to read, create, update, delete notes, execute commands, and search content via HTTP/HTTPS requests. + +# Development Guidelines + +## Test-Driven Development + +Write tests before implementing functionality. Follow this cycle: + +1. Write a failing test that defines the desired behavior +2. Implement the minimum code needed to make the test pass +3. Refactor while keeping tests green + +When creating new features or fixing bugs, start by adding or modifying tests. Ensure all tests pass before considering work complete. Place tests in appropriate directories following the project's existing test structure. + +## GitHub Issue Integration + +Before starting work, check if a relevant GitHub issue exists. If working on a specific issue, reference it in commit messages using the issue number (e.g., "Fixes #123" or "Addresses #123"). + +When encountering bugs or identifying potential improvements during development, create GitHub issues to track them rather than immediately implementing fixes outside the current scope. + +For significant changes, review related issues to understand context and avoid duplicate work. Update issue status and add comments when making meaningful progress. + +## Code Quality + +Maintain consistency with existing code style and architecture patterns. Keep changes focused on the task at hand. Write clear commit messages that explain why changes were made, not just what changed. + + +## Development Commands + +### Build and Development +- `npm run dev` - Start development build with watch mode (uses esbuild) +- `npm run build` - Build for production (runs TypeScript type checking first, then esbuild bundle) +- `npm test` - Run Jest test suite + +### Documentation +- `npm run build-docs` - Generate OpenAPI documentation from Jsonnet source +- `npm run serve-docs` - Serve Swagger UI for API documentation (requires Docker) + +## Architecture + +### Core Components + +**Main Plugin (`src/main.ts`)** +- `LocalRestApi` class: Plugin entry point that manages HTTPS/HTTP server lifecycle +- Generates self-signed certificates on first run using node-forge +- Creates secure (HTTPS on port 27124) and optional insecure (HTTP on port 27123) servers +- Delegates request handling to `RequestHandler` + +**Request Handler (`src/requestHandler.ts`)** +- `RequestHandler` class: Core Express.js application that defines all API routes +- Implements bearer token authentication middleware +- Handles all vault operations (read/write/patch/delete files) +- Supports periodic notes (daily, weekly, monthly, yearly) +- Implements search functionality (simple search and JSON logic queries) +- Provides command execution capabilities +- Uses markdown-patch library for PATCH operations on notes + +**Public API (`src/api.ts`)** +- `LocalRestApiPublicApi` class: Allows other Obsidian plugins to register custom API extensions +- Plugins get their own Express router to add custom routes +- Extensions can be unregistered when plugins unload + +### Key API Endpoints + +The plugin exposes these endpoint categories: +- `/active/` - Operations on currently active file +- `/vault/*` - Operations on vault files by path +- `/periodic/:period/` - Daily/weekly/monthly/yearly note operations +- `/commands/` - List and execute Obsidian commands +- `/search/` - Content search with various query methods +- `/open/*` - Open files in Obsidian + +All endpoints (except certificate and docs) require bearer token authentication via `Authorization` header. + +### Testing + +The test suite uses Jest with ts-jest for TypeScript support. Tests mock the Obsidian API using `mocks/obsidian.ts`. The main test file is `src/requestHandler.test.ts`. + +To run a single test file: +```bash +npm test -- requestHandler.test.ts +``` + +### Build Process + +Uses esbuild for bundling: +- Entry point: `src/main.ts` +- Output: `main.js` (CommonJS format) +- External dependencies: Obsidian API and built-in Node modules +- YAML files loaded as text (for OpenAPI spec embedding) +- Development builds include inline source maps + +### TypeScript Configuration + +- Target: ES6 +- Module: ESNext with Node resolution +- Strict type checking enabled (`noImplicitAny`) +- Test files excluded from main compilation + +## Important Patterns + +### Authentication +All API requests (except `/`, certificate endpoint, and OpenAPI spec) require bearer token authentication. The API key is auto-generated on first run using SHA-256 hash of random bytes. + +### Content Types +The API accepts multiple content types: +- `text/markdown` - Standard markdown content +- `application/json` - JSON data +- `application/vnd.olrapi.note+json` - Special note format with frontmatter +- `application/vnd.olrapi.jsonlogic+json` - JSON Logic queries for filtering +- `application/vnd.olrapi.dataview.dql+txt` - Dataview query language + +### PATCH Operations +Uses the `markdown-patch` library for structured updates. Supports: +- Heading-based insertions (specify heading boundary and position) +- Target-based patches with operation types (insert, append, prepend, replace) +- Content-type-aware patching + +### Certificate Management +Self-signed certificates are auto-generated with: +- 2048-bit RSA keypairs +- 365-day validity +- Subject Alternative Names support for custom hostnames +- Must be trusted by client browsers for HTTPS connections diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c1f4ea9 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +.PHONY: help build install dev clean + +# Default vault path - override with: make install VAULT_PATH=/path/to/vault +VAULT_PATH ?= $(HOME)/Documents/Obsidian/MyVault +PLUGIN_DIR = $(VAULT_PATH)/.obsidian/plugins/obsidian-local-rest-api + +help: + @echo "Local REST API Development Commands" + @echo "" + @echo " make build - Build plugin (creates main.js)" + @echo " make install - Install plugin to Obsidian vault" + @echo " make dev - Build in watch mode" + @echo " make clean - Remove build artifacts" + @echo "" + @echo "Installation:" + @echo " 1. Set VAULT_PATH: export VAULT_PATH=/path/to/your/vault" + @echo " 2. Run: make build install" + @echo " 3. In Obsidian: Settings → Community Plugins → Enable 'Local REST API'" + @echo " 4. Get API key from plugin settings" + @echo "" + @echo "Testing:" + @echo " make test - Run tests" + @echo "" + @echo "Current vault path: $(VAULT_PATH)" + +build: + @echo "Building plugin..." + npm run build + @echo "✓ Build complete: main.js created" + +install: build + @echo "Installing to Obsidian vault..." + @if [ ! -d "$(VAULT_PATH)" ]; then \ + echo "Error: Vault not found at $(VAULT_PATH)"; \ + echo "Set VAULT_PATH: export VAULT_PATH=/path/to/your/vault"; \ + exit 1; \ + fi + @mkdir -p "$(PLUGIN_DIR)" + @cp main.js "$(PLUGIN_DIR)/" + @cp manifest.json "$(PLUGIN_DIR)/" + @cp styles.css "$(PLUGIN_DIR)/" + @echo "✓ Installed to $(PLUGIN_DIR)" + @echo "" + @echo "Next steps:" + @echo " 1. Restart Obsidian (or reload plugins)" + @echo " 2. Settings → Community Plugins" + @echo " 3. Enable 'Local REST API'" + @echo " 4. Find API key in plugin settings" + +dev: + @echo "Starting dev mode (watch)..." + npm run dev + +test: + npm test + +clean: + @echo "Cleaning build artifacts..." + @rm -f main.js + @echo "✓ Clean complete" diff --git a/Obsidian_Local_REST_API.postman_collection.json b/Obsidian_Local_REST_API.postman_collection.json new file mode 100644 index 0000000..684b3ac --- /dev/null +++ b/Obsidian_Local_REST_API.postman_collection.json @@ -0,0 +1,455 @@ +{ + "info": { + "_postman_id": "obsidian-local-rest-api-collection", + "name": "Obsidian Local REST API", + "description": "Collection for testing Obsidian Local REST API endpoints including the new /tags/ endpoint", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "obsidian-local-rest-api" + }, + "item": [ + { + "name": "Tags", + "item": [ + { + "name": "Get All Tags", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/tags/", + "host": [ + "{{base_url}}" + ], + "path": [ + "tags", + "" + ] + }, + "description": "Get all tags from the vault with their usage counts" + }, + "response": [] + } + ] + }, + { + "name": "Commands", + "item": [ + { + "name": "List Commands", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/commands/", + "host": [ + "{{base_url}}" + ], + "path": [ + "commands", + "" + ] + }, + "description": "List all available Obsidian commands" + }, + "response": [] + }, + { + "name": "Execute Command", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/commands/:commandId/", + "host": [ + "{{base_url}}" + ], + "path": [ + "commands", + ":commandId", + "" + ], + "variable": [ + { + "key": "commandId", + "value": "editor:toggle-bold" + } + ] + }, + "description": "Execute a specific Obsidian command" + }, + "response": [] + } + ] + }, + { + "name": "Vault", + "item": [ + { + "name": "List Vault Root", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/vault/", + "host": [ + "{{base_url}}" + ], + "path": [ + "vault", + "" + ] + }, + "description": "List files in vault root directory" + }, + "response": [] + }, + { + "name": "Get Note Content", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/vault/:notePath", + "host": [ + "{{base_url}}" + ], + "path": [ + "vault", + ":notePath" + ], + "variable": [ + { + "key": "notePath", + "value": "MyNote.md" + } + ] + }, + "description": "Get content of a specific note" + }, + "response": [] + }, + { + "name": "Create/Update Note", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "text/markdown", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "# My Note\n\nThis is the content of my note.\n\n#tag1 #tag2" + }, + "url": { + "raw": "{{base_url}}/vault/:notePath", + "host": [ + "{{base_url}}" + ], + "path": [ + "vault", + ":notePath" + ], + "variable": [ + { + "key": "notePath", + "value": "MyNote.md" + } + ] + }, + "description": "Create or update a note" + }, + "response": [] + }, + { + "name": "Delete Note", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/vault/:notePath", + "host": [ + "{{base_url}}" + ], + "path": [ + "vault", + ":notePath" + ], + "variable": [ + { + "key": "notePath", + "value": "MyNote.md" + } + ] + }, + "description": "Delete a note from the vault" + }, + "response": [] + }, + { + "name": "Patch Note", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "text/markdown", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "New content to append" + }, + "url": { + "raw": "{{base_url}}/vault/:notePath", + "host": [ + "{{base_url}}" + ], + "path": [ + "vault", + ":notePath" + ], + "variable": [ + { + "key": "notePath", + "value": "MyNote.md" + } + ] + }, + "description": "Patch a note (insert content at specific location)" + }, + "response": [] + } + ] + }, + { + "name": "Search", + "item": [ + { + "name": "Simple Search", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + }, + { + "key": "Content-Type", + "value": "text/plain", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "search query" + }, + "url": { + "raw": "{{base_url}}/search/simple/", + "host": [ + "{{base_url}}" + ], + "path": [ + "search", + "simple", + "" + ] + }, + "description": "Simple text search across vault" + }, + "response": [] + } + ] + }, + { + "name": "Active File", + "item": [ + { + "name": "Get Active File", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/active/", + "host": [ + "{{base_url}}" + ], + "path": [ + "active", + "" + ] + }, + "description": "Get content of currently active file" + }, + "response": [] + } + ] + }, + { + "name": "Periodic Notes", + "item": [ + { + "name": "Get Daily Note", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/periodic/daily/", + "host": [ + "{{base_url}}" + ], + "path": [ + "periodic", + "daily", + "" + ] + }, + "description": "Get today's daily note" + }, + "response": [] + }, + { + "name": "Get Weekly Note", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{api_key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/periodic/weekly/", + "host": [ + "{{base_url}}" + ], + "path": [ + "periodic", + "weekly", + "" + ] + }, + "description": "Get this week's weekly note" + }, + "response": [] + } + ] + }, + { + "name": "Info", + "item": [ + { + "name": "Root Endpoint", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/", + "host": [ + "{{base_url}}" + ], + "path": [ + "" + ] + }, + "description": "Get API info (no auth required)" + }, + "response": [] + }, + { + "name": "Get Certificate", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/obsidian-local-rest-api.crt", + "host": [ + "{{base_url}}" + ], + "path": [ + "obsidian-local-rest-api.crt" + ] + }, + "description": "Download the SSL certificate (no auth required)" + }, + "response": [] + } + ] + } + ], + "variable": [ + { + "key": "base_url", + "value": "https://127.0.0.1:27124", + "type": "string" + }, + { + "key": "api_key", + "value": "YOUR_API_KEY_HERE", + "type": "string" + } + ] +} diff --git a/manifest.json b/manifest.json index dd9fd0c..2b2f2d6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-local-rest-api", "name": "Local REST API", - "version": "3.2.0", + "version": "3.2.1", "minAppVersion": "0.12.0", "description": "Get, change or otherwise interact with your notes in Obsidian via a REST API.", "author": "Adam Coddington", diff --git a/mocks/obsidian.ts b/mocks/obsidian.ts index 21452da..0eeda15 100644 --- a/mocks/obsidian.ts +++ b/mocks/obsidian.ts @@ -110,10 +110,15 @@ export class CachedMetadata { export class MetadataCache { _getFileCache = new CachedMetadata(); + _getTags: Record = {}; getFileCache(file: TFile): CachedMetadata { return this._getFileCache; } + + getTags(): Record { + return this._getTags; + } } export class Workspace { diff --git a/package-lock.json b/package-lock.json index 43bc87a..b3922ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-local-rest-api", - "version": "3.2.0", + "version": "3.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-local-rest-api", - "version": "3.2.0", + "version": "3.2.1", "license": "MIT", "dependencies": { "@types/response-time": "^2.3.5", @@ -90,6 +90,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -549,6 +550,7 @@ "version": "6.5.2", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -557,6 +559,7 @@ "version": "6.36.8", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz", "integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "style-mod": "^4.1.0", @@ -595,7 +598,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -619,7 +621,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -630,7 +631,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -643,7 +643,6 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -654,7 +653,6 @@ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -669,7 +667,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -680,7 +677,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -693,7 +689,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "peer": true, "engines": { "node": ">=12.22" }, @@ -707,8 +702,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "peer": true + "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1377,6 +1371,7 @@ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", "dev": true, + "peer": true, "dependencies": { "jest-matcher-utils": "^27.0.0", "pretty-format": "^27.0.0" @@ -1570,6 +1565,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -1723,8 +1719,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/abab": { "version": "2.0.6", @@ -1750,6 +1745,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1784,7 +1780,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1815,7 +1810,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1895,8 +1889,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/array-flatten": { "version": "1.1.1", @@ -2105,6 +2098,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", @@ -2505,8 +2499,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -2588,7 +2581,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -2973,7 +2965,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -3098,7 +3089,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3109,7 +3099,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -3126,7 +3115,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -3136,7 +3124,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3149,7 +3136,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "peer": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -3180,7 +3166,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -3193,7 +3178,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -3354,8 +3338,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fast-glob": { "version": "3.3.3", @@ -3395,8 +3378,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fast-safe-stringify": { "version": "2.1.1", @@ -3427,7 +3409,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -3490,7 +3471,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3507,7 +3487,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -3521,8 +3500,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/form-data": { "version": "4.0.2", @@ -3698,7 +3676,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -3738,7 +3715,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -3937,7 +3913,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4075,7 +4050,6 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4190,6 +4164,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -4815,7 +4790,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -4900,8 +4874,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-logic-js": { "version": "2.0.5", @@ -4918,15 +4891,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json5": { "version": "2.2.3", @@ -4945,7 +4916,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -4973,7 +4943,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -5009,7 +4978,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -5036,8 +5004,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -5507,7 +5474,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -5525,7 +5491,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -5541,7 +5506,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -5571,7 +5535,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -5768,7 +5731,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -5996,7 +5958,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -6596,8 +6557,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/throat": { "version": "6.0.2", @@ -6733,7 +6693,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -6755,7 +6714,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -6789,6 +6747,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6849,7 +6808,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -7005,7 +6963,6 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7136,7 +7093,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index dd963ba..72f49da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-local-rest-api", - "version": "3.2.0", + "version": "3.2.1", "description": "Get, change or otherwise interact with your notes in Obsidian via a REST API.", "main": "main.js", "types": "main.d.ts", diff --git a/src/requestHandler.test.ts b/src/requestHandler.test.ts index 9638726..1c9cae4 100644 --- a/src/requestHandler.test.ts +++ b/src/requestHandler.test.ts @@ -742,6 +742,31 @@ describe("requestHandler", () => { }); }); + describe("tagsGet", () => { + test("acceptable", async () => { + app.metadataCache._getTags = { + "project": 3, + "important": 1, + "work/tasks": 2 + }; + + const result = await request(server) + .get(`/tags/`) + .set("Authorization", `Bearer ${API_KEY}`) + .expect(200); + + expect(result.body.tags).toEqual({ + "project": 3, + "important": 1, + "work/tasks": 2 + }); + }); + + test("unauthorized", async () => { + await request(server).get(`/tags/`).expect(401); + }); + }); + describe("commandGet", () => { test("acceptable", async () => { const arbitraryCommand = new Command(); diff --git a/src/requestHandler.ts b/src/requestHandler.ts index 106183b..d7c482c 100644 --- a/src/requestHandler.ts +++ b/src/requestHandler.ts @@ -946,6 +946,14 @@ export default class RequestHandler { ); } + async tagsGet(req: express.Request, res: express.Response): Promise { + const allTags = (this.app.metadataCache as any).getTags(); + + res.json({ + tags: allTags, + }); + } + async commandGet(req: express.Request, res: express.Response): Promise { const commands: Command[] = []; for (const commandName in this.app.commands.commands) { @@ -1283,6 +1291,8 @@ export default class RequestHandler { .post(this.periodicPost.bind(this)) .delete(this.periodicDelete.bind(this)); + this.api.route("/tags/").get(this.tagsGet.bind(this)); + this.api.route("/commands/").get(this.commandGet.bind(this)); this.api.route("/commands/:commandId/").post(this.commandPost.bind(this)); diff --git a/versions.json b/versions.json index 774e115..f4bd2a4 100644 --- a/versions.json +++ b/versions.json @@ -54,5 +54,6 @@ "3.0.4": "0.12.0", "3.0.5": "0.12.0", "3.1.0": "0.12.0", - "3.2.0": "0.12.0" + "3.2.0": "0.12.0", + "3.2.1": "0.12.0" } \ No newline at end of file