diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d32053b..1896f11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,13 @@ repos: -- hooks: +- repo: https://github.com/commitizen-tools/commitizen + rev: v4.8.2 + hooks: - id: commitizen - id: commitizen-branch stages: - pre-push - repo: https://github.com/commitizen-tools/commitizen - rev: v4.8.2 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/README.md b/README.md index c57c507..0dfa5d9 100644 --- a/README.md +++ b/README.md @@ -8,24 +8,46 @@ The Nextcloud MCP (Model Context Protocol) server allows Large Language Models ( ## Features -Currently, the server primarily interacts with the Nextcloud Notes API, providing tools and resources to manage notes. +The server provides integration with multiple Nextcloud apps, enabling LLMs to interact with your Nextcloud data through a rich set of tools and resources. -### Available Tools +## Supported Nextcloud Apps -* `nc_notes_create_note`: Create a new note. -* `nc_notes_update_note`: Update an existing note by ID. -* `nc_notes_append_content`: Append content to an existing note with a clear separator. -* `nc_notes_delete_note`: Delete a note by ID. -* `nc_notes_search_notes`: Search notes by title or content. -* `nc_get_note`: Get a specific note by ID. +| App | Support Status | Description | +|-----|----------------|-------------| +| **Notes** | ✅ Full Support | Create, read, update, delete, and search notes. Handle attachments via WebDAV. | +| **Tables** | ⚠️ Row Operations | Read table schemas and perform CRUD operations on table rows. Table management not yet supported. | -### Available Resources +## Available Tools -* `notes://{note_id}`: Access a specific note by its ID. -* `notes://all`: Access all notes. -* `notes://settings`: Access note settings. -* `nc://capabilities`: Access Nextcloud server capabilities. -* `nc://Notes/{note_id}/attachments/{attachment_filename}`: Access attachments for notes. +### Notes Tools + +| Tool | Description | +|------|-------------| +| `nc_get_note` | Get a specific note by ID | +| `nc_notes_create_note` | Create a new note with title, content, and category | +| `nc_notes_update_note` | Update an existing note by ID | +| `nc_notes_append_content` | Append content to an existing note with a clear separator | +| `nc_notes_delete_note` | Delete a note by ID | +| `nc_notes_search_notes` | Search notes by title or content | + +### Tables Tools + +| Tool | Description | +|------|-------------| +| `nc_tables_list_tables` | List all tables available to the user | +| `nc_tables_get_schema` | Get the schema/structure of a specific table including columns and views | +| `nc_tables_read_table` | Read rows from a table with optional pagination | +| `nc_tables_insert_row` | Insert a new row into a table | +| `nc_tables_update_row` | Update an existing row in a table | +| `nc_tables_delete_row` | Delete a row from a table | + +## Available Resources + +| Resource | Description | +|----------|-------------| +| `nc://capabilities` | Access Nextcloud server capabilities | +| `notes://settings` | Access Notes app settings | +| `nc://Notes/{note_id}/attachments/{attachment_filename}` | Access attachments for notes | ### Note Attachments diff --git a/app-hooks/post-installation/install-tables-app.sh b/app-hooks/post-installation/install-tables-app.sh new file mode 100755 index 0000000..53c8583 --- /dev/null +++ b/app-hooks/post-installation/install-tables-app.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +php /var/www/html/occ app:enable tables diff --git a/nextcloud_mcp_server/base_client.py b/nextcloud_mcp_server/base_client.py new file mode 100644 index 0000000..92add59 --- /dev/null +++ b/nextcloud_mcp_server/base_client.py @@ -0,0 +1,41 @@ +"""Base client for Nextcloud operations with shared authentication.""" + +from abc import ABC +from httpx import AsyncClient +import logging + +logger = logging.getLogger(__name__) + + +class BaseNextcloudClient(ABC): + """Base class for all Nextcloud app clients.""" + + def __init__(self, http_client: AsyncClient, username: str): + """Initialize with shared HTTP client and username. + + Args: + http_client: Authenticated AsyncClient instance + username: Nextcloud username for WebDAV operations + """ + self._client = http_client + self.username = username + + def _get_webdav_base_path(self) -> str: + """Helper to get the base WebDAV path for the authenticated user.""" + return f"/remote.php/dav/files/{self.username}" + + async def _make_request(self, method: str, url: str, **kwargs): + """Common request wrapper with logging and error handling. + + Args: + method: HTTP method + url: Request URL + **kwargs: Additional request parameters + + Returns: + Response object + """ + logger.debug(f"Making {method} request to {url}") + response = await self._client.request(method, url, **kwargs) + response.raise_for_status() + return response diff --git a/nextcloud_mcp_server/client.py b/nextcloud_mcp_server/client.py index e4bab4c..907ade7 100644 --- a/nextcloud_mcp_server/client.py +++ b/nextcloud_mcp_server/client.py @@ -1,15 +1,17 @@ import os -import mimetypes from httpx import ( AsyncClient, Auth, BasicAuth, Request, Response, - HTTPStatusError, ) import logging +from .notes_client import NotesClient +from .webdav_client import WebDAVClient +from .tables_client import TablesClient +from .controllers.notes_search import NotesSearchController logger = logging.getLogger(__name__) @@ -30,14 +32,24 @@ def log_response(response: Response): class NextcloudClient: + """Main Nextcloud client that orchestrates all app clients.""" + def __init__(self, base_url: str, username: str, auth: Auth | None = None): - self.username = username # Store username + self.username = username self._client = AsyncClient( base_url=base_url, auth=auth, # event_hooks={"request": [log_request], "response": [log_response]}, ) + # Initialize app clients + self.notes = NotesClient(self._client, username) + self.webdav = WebDAVClient(self._client, username) + self.tables = TablesClient(self._client, username) + + # Initialize controllers + self._notes_search = NotesSearchController() + @classmethod def from_env(cls): logger.info("Creating NC Client using env vars") @@ -57,618 +69,15 @@ class NextcloudClient: return response.json() - async def notes_get_settings(self): - response = await self._client.get("/apps/notes/api/v1/settings") - response.raise_for_status() - return response.json() - - async def notes_get_all(self): - response = await self._client.get("/apps/notes/api/v1/notes") - response.raise_for_status() - return response.json() - - async def notes_get_note(self, *, note_id: int): - response = await self._client.get(f"/apps/notes/api/v1/notes/{note_id}") - response.raise_for_status() - return response.json() - - async def notes_create_note( - self, - *, - title: str | None = None, - content: str | None = None, - category: str | None = None, - ): - body = {} - if title: - body.update({"title": title}) - if content: - body.update({"content": content}) - if category: - body.update({"category": category}) - - response = await self._client.post( - url="/apps/notes/api/v1/notes", - json=body, - ) - response.raise_for_status() - return response.json() - - async def notes_update_note( - self, - *, - note_id: int, - etag: str, - title: str | None = None, - content: str | None = None, - category: str | None = None, - ): - # First, get the current note details to check for category change - old_note = None - try: - if category is not None: # Only fetch if category might change - old_note = await self.notes_get_note(note_id=note_id) - old_category = old_note.get("category", "") - logger.info(f"Current category for note {note_id}: '{old_category}'") - except Exception as e: - logger.warning( - f"Could not fetch current note {note_id} details before update: {e}" - ) - # Continue with update even if we couldn't fetch current details - old_note = None - - # Prepare update body - body = {} - if title: - body.update({"title": title}) - if content: - body.update({"content": content}) - if category: - body.update({"category": category}) - - logger.info( - "Attempting to update note %s with etag %s. Body: %s", - note_id, - etag, - body, - ) - # Ensure conditional PUT using If-Match header is active - response = await self._client.put( - url=f"/apps/notes/api/v1/notes/{note_id}", - json=body, - headers={"If-Match": f'"{etag}"'}, - ) - logger.info( - "Update response for note %s: Status %s, Headers %s", - note_id, - response.status_code, - response.headers, - ) - response.raise_for_status() - updated_note = response.json() - - # Check for category change and clean up old attachment directory if needed - if ( - old_note - and category is not None - and old_note.get("category", "") != category - ): - logger.info( - f"Category changed from '{old_note.get('category', '')}' to '{category}' - cleaning up old attachment directory" - ) - try: - await self._cleanup_old_attachment_directory( - note_id=note_id, old_category=old_note.get("category", "") - ) - except Exception as e: - logger.error( - f"Error cleaning up old attachment directory for note {note_id}: {e}" - ) - # Continue with update even if cleanup failed - - return updated_note - - async def notes_append_content(self, *, note_id: int, content: str): - """Append content to an existing note. - - The content will be separated by a newline and a delimiter `---`, so - one will not be required in the content provided to this tool - """ - logger.info(f"Appending content to note {note_id}") - - # Get current note - current_note = await self.notes_get_note(note_id=note_id) - - # Use fixed separator for consistency - separator = "\n---\n" - - # Combine content - existing_content = current_note.get("content", "") - if existing_content: - new_content = existing_content + separator + content - else: - new_content = content # No separator needed for empty notes - - logger.info( - f"Combining existing content ({len(existing_content)} chars) with new content ({len(content)} chars)" - ) - - # Update with combined content - return await self.notes_update_note( - note_id=note_id, - etag=current_note["etag"], - content=new_content, - title=None, # Keep existing title - category=None, # Keep existing category - ) - async def notes_search_notes(self, *, query: str): - """ - Search notes using token-based matching with relevance ranking. - Returns notes sorted by relevance score. - """ - all_notes = await self.notes_get_all() - search_results = [] - - # Process the query - query_tokens = self.process_query(query) - - # If empty query after processing, return empty results - if not query_tokens: - return [] - - # Process and score each note - for note in all_notes: - title_tokens, content_tokens = self.process_note_content(note) - score = self.calculate_score(query_tokens, title_tokens, content_tokens) - - # Only include notes with a non-zero score - if score >= 0.5: - search_results.append( - { - "id": note.get("id"), - "title": note.get("title"), - "category": note.get("category"), - "modified": note.get("modified"), - "_score": score, # Include score for sorting (optional field) - } - ) - - # Sort by score in descending order - search_results.sort(key=lambda x: x["_score"], reverse=True) - - # Keep score field for debugging - # for result in search_results: - # if "_score" in result: - # del result["_score"] - - return search_results - - def process_query(self, query: str) -> list[str]: - """ - Tokenize and normalize the search query. - """ - # Convert to lowercase and split into tokens - tokens = query.lower().split() - # Filter out very short tokens (optional) - tokens = [token for token in tokens if len(token) > 1] - # Could add stop word removal here - return tokens - - def process_note_content(self, note: dict) -> tuple[list[str], list[str]]: - """ - Tokenize and normalize note title and content. - """ - # Process title - title = note.get("title", "").lower() - title_tokens = title.split() - - # Process content - content = note.get("content", "").lower() - content_tokens = content.split() - - return title_tokens, content_tokens - - def calculate_score( - self, - query_tokens: list[str], - title_tokens: list[str], - content_tokens: list[str], - ) -> float: - """ - Calculate a relevance score for a note based on query tokens. - """ - # Constants for weighting - TITLE_WEIGHT = 3.0 - CONTENT_WEIGHT = 1.0 - - score = 0.0 - - # Count matches in title - title_matches = sum(1 for qt in query_tokens if qt in title_tokens) - if query_tokens: # Avoid division by zero - title_match_ratio = title_matches / len(query_tokens) - score += TITLE_WEIGHT * title_match_ratio - - # Count matches in content - content_matches = sum(1 for qt in query_tokens if qt in content_tokens) - if query_tokens: # Avoid division by zero - content_match_ratio = content_matches / len(query_tokens) - score += CONTENT_WEIGHT * content_match_ratio - - # If no tokens matched at all, return zero - if title_matches == 0 and content_matches == 0: - return 0.0 - - return score - - async def _cleanup_old_attachment_directory( - self, *, note_id: int, old_category: str - ): - """ - Clean up the attachment directory for a note in its old category location. - Called after a category change to prevent orphaned directories. - """ - # Construct path to old attachment directory - old_category_path_part = f"{old_category}/" if old_category else "" - old_attachment_dir_path = ( - f"Notes/{old_category_path_part}.attachments.{note_id}/" - ) - - logger.info(f"Cleaning up old attachment directory: {old_attachment_dir_path}") - try: - delete_result = await self.delete_webdav_resource( - path=old_attachment_dir_path - ) - logger.info(f"Cleanup of old attachment directory result: {delete_result}") - return delete_result - except Exception as e: - logger.error(f"Error during cleanup of old attachment directory: {e}") - raise e - - async def delete_webdav_resource(self, *, path: str): - """Delete a resource (file or directory) via WebDAV DELETE.""" - # Ensure path ends with a slash if it's a directory - if not path.endswith("/"): - # This is a heuristic; a more robust solution would check resource type first - # but for the specific case of deleting the attachment directory, this is acceptable. - path_with_slash = f"{path}/" - else: - path_with_slash = path - - webdav_path = f"{self._get_webdav_base_path()}/{path_with_slash.lstrip('/')}" - logger.info("Deleting WebDAV resource: %s", webdav_path) - - headers = {"OCS-APIRequest": "true"} - try: - # First try a PROPFIND to verify resource exists - propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"} - try: - propfind_resp = await self._client.request( - "PROPFIND", webdav_path, headers=propfind_headers - ) - logger.info( - f"Resource exists check (PROPFIND) status: {propfind_resp.status_code}" - ) - # If we get here with 2xx, the resource exists - except HTTPStatusError as e: - if e.response.status_code == 404: - logger.info( - f"Resource '{webdav_path}' doesn't exist, no deletion needed." - ) - return {"status_code": 404} - # For other errors, continue with deletion attempt - - # Proceed with deletion - response = await self._client.delete(webdav_path, headers=headers) - response.raise_for_status() # Raises for 4xx/5xx status codes - logger.info( - "Successfully deleted WebDAV resource '%s' (Status: %s)", - webdav_path, - response.status_code, - ) - # DELETE typically returns 204 No Content on success - return {"status_code": response.status_code} - - except HTTPStatusError as e: - logger.warning( - "HTTP error deleting WebDAV resource '%s': %s", - webdav_path, - e, - ) - # It's expected to get a 404 if the resource doesn't exist, which is fine. - # We only re-raise if it's not a 404. - if e.response.status_code != 404: - raise e - else: - logger.info("Resource '%s' not found, no deletion needed.", webdav_path) - return {"status_code": 404} # Indicate resource was not found - except Exception as e: - logger.warning( - "Unexpected error deleting WebDAV resource '%s': %s", - webdav_path, - e, - ) - raise e - - async def notes_delete_note(self, *, note_id: int): - """Deletes a note via API and attempts to delete its attachment directory via WebDAV.""" - # Fetch note details first to get the category for path construction - try: - note_details = await self.notes_get_note(note_id=note_id) - category = note_details.get("category", "") - - # Check for other potential categories (if any note was moved between categories) - # We can't reliably detect this without a dedicated tracking mechanism, but we can - # implement a basic check for common category names and empty category - potential_categories = [] - if category: - potential_categories.append(category) # Current category first - - # Add empty category (uncategorized notes) - if category != "": - potential_categories.append("") - - # We could add logic here to check for other common categories if needed - - logger.info( - f"Note {note_id} has category: '{category}', will check attachment directories in: {potential_categories}" - ) - except HTTPStatusError as e: - # If note doesn't exist (404), we can't delete attachments anyway. - # Re-raise other errors. - if e.response.status_code == 404: - logger.warning( - f"Note {note_id} not found when attempting delete. Skipping attachment cleanup." - ) - # Still raise the 404 as the primary delete operation failed - raise e - else: - logger.error( - f"Error fetching note {note_id} details before deleting attachments: {e}" - ) - raise e # Re-raise unexpected errors during fetch - - # Proceed with API note deletion - logger.info(f"Deleting note {note_id} via API.") - response = await self._client.delete(f"/apps/notes/api/v1/notes/{note_id}") - response.raise_for_status() # Raise if API deletion fails - logger.info(f"Note {note_id} deleted successfully via API.") - json_response = response.json() # Usually empty on success - - # Now, attempt to delete the associated attachments directory via WebDAV for each potential category - for cat in potential_categories: - cat_path_part = f"{cat}/" if cat else "" - attachment_dir_path = f"Notes/{cat_path_part}.attachments.{note_id}/" - - logger.info( - f"Attempting to delete attachment directory for note {note_id} in category '{cat}' via WebDAV: {attachment_dir_path}" - ) - try: - # delete_webdav_resource expects path relative to user's files dir - delete_result = await self.delete_webdav_resource( - path=attachment_dir_path - ) - logger.info( - f"WebDAV deletion for category '{cat}' attachment directory: {delete_result}" - ) - except Exception as e: - # Log the error but don't re-raise, as API note deletion itself was successful - # Also, we want to try other potential categories even if one fails - logger.warning( - f"Failed during WebDAV deletion for category '{cat}' attachment directory: {e}" - ) - - return json_response - - # Removed incorrect get_note_attachment method that used Notes API + """Search notes using token-based matching with relevance ranking.""" + all_notes = await self.notes.get_all_notes() + return self._notes_search.search_notes(all_notes, query) def _get_webdav_base_path(self) -> str: """Helper to get the base WebDAV path for the authenticated user.""" - # Use the stored username return f"/remote.php/dav/files/{self.username}" - # Removed _get_note_attachment_webdav_path helper - - async def add_note_attachment( - self, - *, - note_id: int, - filename: str, - content: bytes, - category: str | None = None, - mime_type: str | None = None, - ): - """ - Add/Update an attachment to a note via WebDAV PUT. - Requires the caller to provide the note's category. - """ - # Construct paths based on provided category - webdav_base = self._get_webdav_base_path() - category_path_part = f"{category}/" if category else "" - attachment_dir_segment = f".attachments.{note_id}" - parent_dir_webdav_rel_path = ( - f"Notes/{category_path_part}{attachment_dir_segment}" - ) - parent_dir_path = ( - f"{webdav_base}/{parent_dir_webdav_rel_path}" # Full path for MKCOL - ) - attachment_path = f"{parent_dir_path}/{filename}" # Full path for PUT - - logger.info( - f"Uploading attachment for note {note_id} (category: '{category or ''}') to WebDAV path: {attachment_path}" - ) - - # Log current auth settings to diagnose the issue - logger.info( - "WebDAV auth settings - Username: %s, Auth Type: %s", - self.username, - type(self._client.auth).__name__, - ) - - if not mime_type: - mime_type, _ = mimetypes.guess_type(filename) - if not mime_type: - mime_type = "application/octet-stream" # Default if guessing fails - - headers = {"Content-Type": mime_type, "OCS-APIRequest": "true"} - try: - # First check if we can access WebDAV at all with current credentials - # by checking the Notes directory - notes_dir_path = f"{webdav_base}/Notes" - logger.info("Testing WebDAV access to Notes directory: %s", notes_dir_path) - - # Log details of the auth being used by the client for this specific request - if self._client.auth: - auth_header = ( - self._client.auth.auth_flow( - self._client.build_request("GET", notes_dir_path) - ) - .__next__() - .headers.get("Authorization") - ) - logger.info( - "Authorization header for PROPFIND (Notes dir): %s", - ( - auth_header - if auth_header - else "Not present or generated by auth flow" - ), - ) - else: - logger.info( - "No httpx.Auth object configured on the client for PROPFIND (Notes dir)." - ) - - propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"} - logger.info("Headers for PROPFIND (Notes dir): %s", propfind_headers) - notes_dir_response = await self._client.request( - "PROPFIND", notes_dir_path, headers=propfind_headers - ) - - if notes_dir_response.status_code == 401: - logger.error( - "WebDAV authentication failed for Notes directory. Please verify WebDAV permissions." - ) - raise HTTPStatusError( - f"Authentication error accessing WebDAV Notes directory: {notes_dir_response.status_code}", - request=notes_dir_response.request, - response=notes_dir_response, - ) - elif notes_dir_response.status_code >= 400: - logger.error( - "Error accessing WebDAV Notes directory: %s", - notes_dir_response.status_code, - ) - notes_dir_response.raise_for_status() - else: - logger.info( - "Successfully accessed WebDAV Notes directory (Status: %s)", - notes_dir_response.status_code, - ) - - # Ensure the parent directory exists using MKCOL - # parent_dir_path is now determined by the helper method - logger.info("Ensuring attachments directory exists: %s", parent_dir_path) - mkcol_headers = {"OCS-APIRequest": "true"} - logger.info("Headers for MKCOL (Attachments dir): %s", mkcol_headers) - mkcol_response = await self._client.request( - "MKCOL", parent_dir_path, headers=mkcol_headers - ) - # MKCOL should return 201 Created or 405 Method Not Allowed (if directory already exists) - # We can ignore 405, but raise for other errors - if mkcol_response.status_code not in [201, 405]: - logger.warning( - "Unexpected status code %s when creating attachments directory", - mkcol_response.status_code, - ) - mkcol_response.raise_for_status() - else: - logger.info( - "Created/verified directory: %s (Status: %s)", - parent_dir_path, - mkcol_response.status_code, - ) - - # Proceed with the PUT request - logger.info("Putting attachment file to: %s", attachment_path) - response = await self._client.put( - attachment_path, content=content, headers=headers - ) - response.raise_for_status() # Raises for 4xx/5xx status codes - logger.info( - "Successfully uploaded attachment '%s' to note %s (Status: %s)", - filename, - note_id, - response.status_code, - ) - # PUT typically returns 201 Created or 204 No Content on success - return { - "status_code": response.status_code - } # Return status or relevant info - - except HTTPStatusError as e: - logger.error( - "HTTP error uploading attachment '%s' to note %s: %s", - filename, - note_id, - e, - ) - raise e - except Exception as e: - logger.error( - "Unexpected error uploading attachment '%s' to note %s: %s", - filename, - note_id, - e, - ) - raise e - - async def get_note_attachment( - self, *, note_id: int, filename: str, category: str | None = None - ): - """ - Fetch a specific attachment from a note via WebDAV GET. - Requires the caller to provide the note's category. - """ - # Construct path based on provided category - webdav_base = self._get_webdav_base_path() - category_path_part = f"{category}/" if category else "" - attachment_dir_segment = f".attachments.{note_id}" - attachment_path = f"{webdav_base}/Notes/{category_path_part}{attachment_dir_segment}/{filename}" - - logger.info( - f"Fetching attachment for note {note_id} (category: '{category or ''}') from WebDAV path: {attachment_path}" - ) - - try: - response = await self._client.get(attachment_path) - response.raise_for_status() - - content = response.content - mime_type = response.headers.get("content-type", "application/octet-stream") - - logger.info( - "Successfully fetched attachment '%s' (%s, %d bytes)", - filename, - mime_type, - len(content), - ) - return content, mime_type - - except HTTPStatusError as e: - logger.error( - "HTTP error fetching attachment '%s' for note %s: %s", - filename, - note_id, - e, - ) - raise e - except Exception as e: - logger.error( - "Unexpected error fetching attachment '%s' for note %s: %s", - filename, - note_id, - e, - ) - raise e + async def close(self): + """Close the HTTP client.""" + await self._client.aclose() diff --git a/nextcloud_mcp_server/controllers/__init__.py b/nextcloud_mcp_server/controllers/__init__.py new file mode 100644 index 0000000..8645805 --- /dev/null +++ b/nextcloud_mcp_server/controllers/__init__.py @@ -0,0 +1 @@ +"""Controllers for utility operations.""" diff --git a/nextcloud_mcp_server/controllers/notes_search.py b/nextcloud_mcp_server/controllers/notes_search.py new file mode 100644 index 0000000..7a4e0e8 --- /dev/null +++ b/nextcloud_mcp_server/controllers/notes_search.py @@ -0,0 +1,102 @@ +"""Controller for notes search functionality.""" + +from typing import List, Dict, Any + + +class NotesSearchController: + """Handles notes search logic and scoring.""" + + def search_notes( + self, notes: List[Dict[str, Any]], query: str + ) -> List[Dict[str, Any]]: + """ + Search notes using token-based matching with relevance ranking. + Returns notes sorted by relevance score. + """ + search_results = [] + query_tokens = self._process_query(query) + + # If empty query after processing, return empty results + if not query_tokens: + return [] + + # Process and score each note + for note in notes: + title_tokens, content_tokens = self._process_note_content(note) + score = self._calculate_score(query_tokens, title_tokens, content_tokens) + + # Only include notes with a non-zero score + if score >= 0.5: + search_results.append( + { + "id": note.get("id"), + "title": note.get("title"), + "category": note.get("category"), + "modified": note.get("modified"), + "_score": score, # Include score for sorting + } + ) + + # Sort by score in descending order + search_results.sort(key=lambda x: x["_score"], reverse=True) + + return search_results + + def _process_query(self, query: str) -> List[str]: + """ + Tokenize and normalize the search query. + """ + # Convert to lowercase and split into tokens + tokens = query.lower().split() + # Filter out very short tokens + tokens = [token for token in tokens if len(token) > 1] + return tokens + + def _process_note_content( + self, note: Dict[str, Any] + ) -> tuple[List[str], List[str]]: + """ + Tokenize and normalize note title and content. + """ + # Process title + title = note.get("title", "").lower() + title_tokens = title.split() + + # Process content + content = note.get("content", "").lower() + content_tokens = content.split() + + return title_tokens, content_tokens + + def _calculate_score( + self, + query_tokens: List[str], + title_tokens: List[str], + content_tokens: List[str], + ) -> float: + """ + Calculate a relevance score for a note based on query tokens. + """ + # Constants for weighting + TITLE_WEIGHT = 3.0 + CONTENT_WEIGHT = 1.0 + + score = 0.0 + + # Count matches in title + title_matches = sum(1 for qt in query_tokens if qt in title_tokens) + if query_tokens: # Avoid division by zero + title_match_ratio = title_matches / len(query_tokens) + score += TITLE_WEIGHT * title_match_ratio + + # Count matches in content + content_matches = sum(1 for qt in query_tokens if qt in content_tokens) + if query_tokens: # Avoid division by zero + content_match_ratio = content_matches / len(query_tokens) + score += CONTENT_WEIGHT * content_match_ratio + + # If no tokens matched at all, return zero + if title_matches == 0 and content_matches == 0: + return 0.0 + + return score diff --git a/nextcloud_mcp_server/notes_client.py b/nextcloud_mcp_server/notes_client.py new file mode 100644 index 0000000..4dee62c --- /dev/null +++ b/nextcloud_mcp_server/notes_client.py @@ -0,0 +1,199 @@ +"""Client for Nextcloud Notes app operations.""" + +from typing import Dict, List, Any, Optional +import logging + +from .base_client import BaseNextcloudClient + +logger = logging.getLogger(__name__) + + +class NotesClient(BaseNextcloudClient): + """Client for Nextcloud Notes app operations.""" + + async def get_settings(self) -> Dict[str, Any]: + """Get Notes app settings.""" + response = await self._make_request("GET", "/apps/notes/api/v1/settings") + return response.json() + + async def get_all_notes(self) -> List[Dict[str, Any]]: + """Get all notes.""" + response = await self._make_request("GET", "/apps/notes/api/v1/notes") + return response.json() + + async def get_note(self, note_id: int) -> Dict[str, Any]: + """Get a specific note by ID.""" + response = await self._make_request( + "GET", f"/apps/notes/api/v1/notes/{note_id}" + ) + return response.json() + + async def create_note( + self, + title: Optional[str] = None, + content: Optional[str] = None, + category: Optional[str] = None, + ) -> Dict[str, Any]: + """Create a new note.""" + body = {} + if title: + body["title"] = title + if content: + body["content"] = content + if category: + body["category"] = category + + response = await self._make_request( + "POST", "/apps/notes/api/v1/notes", json=body + ) + return response.json() + + async def update( + self, + note_id: int, + etag: str, + title: Optional[str] = None, + content: Optional[str] = None, + category: Optional[str] = None, + ) -> Dict[str, Any]: + """Update an existing note.""" + # Get current note details to check for category change + old_note = None + try: + if category is not None: + old_note = await self.get_note(note_id) + old_category = old_note.get("category", "") + logger.info(f"Current category for note {note_id}: '{old_category}'") + except Exception as e: + logger.warning( + f"Could not fetch current note {note_id} details before update: {e}" + ) + old_note = None + + # Prepare update body + body = {} + if title: + body["title"] = title + if content: + body["content"] = content + if category: + body["category"] = category + + logger.info( + f"Attempting to update note {note_id} with etag {etag}. Body: {body}" + ) + + response = await self._make_request( + "PUT", + f"/apps/notes/api/v1/notes/{note_id}", + json=body, + headers={"If-Match": f'"{etag}"'}, + ) + + logger.info( + f"Update response for note {note_id}: Status {response.status_code}" + ) + updated_note = response.json() + + # Check for category change and cleanup old attachment directory if needed + if ( + old_note + and category is not None + and old_note.get("category", "") != category + ): + logger.info( + f"Category changed from '{old_note.get('category', '')}' to '{category}' - cleaning up old attachment directory" + ) + try: + # Import here to avoid circular imports + from .webdav_client import WebDAVClient + + webdav_client = WebDAVClient(self._client, self.username) + await webdav_client.cleanup_old_attachment_directory( + note_id=note_id, old_category=old_note.get("category", "") + ) + except Exception as e: + logger.error( + f"Error cleaning up old attachment directory for note {note_id}: {e}" + ) + + return updated_note + + async def delete_note(self, note_id: int) -> Dict[str, Any]: + """Delete a note and its attachments.""" + # Fetch note details first to get category for cleanup + try: + note_details = await self.get_note(note_id) + category = note_details.get("category", "") + + # Determine potential categories for cleanup + potential_categories = [] + if category: + potential_categories.append(category) + if category != "": + potential_categories.append("") # Empty category + + logger.info( + f"Note {note_id} has category: '{category}', will check attachment directories in: {potential_categories}" + ) + except Exception as e: + logger.warning( + f"Could not fetch note {note_id} details before deletion: {e}" + ) + potential_categories = ["", "Unknown"] # Try common categories + + # Delete the note via API + logger.info(f"Deleting note {note_id} via API") + response = await self._make_request( + "DELETE", f"/apps/notes/api/v1/notes/{note_id}" + ) + logger.info(f"Note {note_id} deleted successfully via API") + json_response = response.json() + + # Clean up attachment directories + try: + from .webdav_client import WebDAVClient + + webdav_client = WebDAVClient(self._client, self.username) + + for cat in potential_categories: + try: + await webdav_client.cleanup_note_attachments(note_id, cat) + except Exception as e: + logger.warning( + f"Failed to cleanup attachments for category '{cat}': {e}" + ) + except Exception as e: + logger.warning(f"Error during attachment cleanup: {e}") + + return json_response + + async def append_content(self, note_id: int, content: str) -> Dict[str, Any]: + """Append content to an existing note with a separator.""" + logger.info(f"Appending content to note {note_id}") + + # Get current note + current_note = await self.get_note(note_id) + + # Use fixed separator for consistency + separator = "\n---\n" + + # Combine content + existing_content = current_note.get("content", "") + if existing_content: + new_content = existing_content + separator + content + else: + new_content = content # No separator needed for empty notes + + logger.info( + f"Combining existing content ({len(existing_content)} chars) with new content ({len(content)} chars)" + ) + + # Update with combined content + return await self.update( + note_id=note_id, + etag=current_note["etag"], + content=new_content, + title=None, # Keep existing title + category=None, # Keep existing category + ) diff --git a/nextcloud_mcp_server/server.py b/nextcloud_mcp_server/server.py index 3102ecf..a5edfbd 100644 --- a/nextcloud_mcp_server/server.py +++ b/nextcloud_mcp_server/server.py @@ -26,7 +26,7 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: yield AppContext(client=client) finally: # Cleanup on shutdown - await client._client.aclose() + await client.close() # Create an MCP server @@ -38,7 +38,6 @@ logger = logging.getLogger(__name__) @mcp.resource("nc://capabilities") async def nc_get_capabilities(): """Get the Nextcloud Host capabilities""" - # client = NextcloudClient.from_env() ctx = ( mcp.get_context() ) # https://github.com/modelcontextprotocol/python-sdk/issues/244 @@ -53,21 +52,21 @@ async def notes_get_settings(): mcp.get_context() ) # https://github.com/modelcontextprotocol/python-sdk/issues/244 client: NextcloudClient = ctx.request_context.lifespan_context.client - return await client.notes_get_settings() + return await client.notes.get_settings() @mcp.tool() async def nc_get_note(note_id: int, ctx: Context): """Get user note using note id""" client: NextcloudClient = ctx.request_context.lifespan_context.client - return await client.notes_get_note(note_id=note_id) + return await client.notes.get_note(note_id) @mcp.tool() async def nc_notes_create_note(title: str, content: str, category: str, ctx: Context): """Create a new note""" client: NextcloudClient = ctx.request_context.lifespan_context.client - return await client.notes_create_note( + return await client.notes.create_note( title=title, content=content, category=category, @@ -85,7 +84,7 @@ async def nc_notes_update_note( ): logger.info("Updating note %s", note_id) client: NextcloudClient = ctx.request_context.lifespan_context.client - return await client.notes_update_note( + return await client.notes.update( note_id=note_id, etag=etag, title=title, @@ -99,7 +98,7 @@ async def nc_notes_append_content(note_id: int, content: str, ctx: Context): """Append content to an existing note with a clear separator""" logger.info("Appending content to note %s", note_id) client: NextcloudClient = ctx.request_context.lifespan_context.client - return await client.notes_append_content(note_id=note_id, content=content) + return await client.notes.append_content(note_id=note_id, content=content) @mcp.tool() @@ -113,7 +112,61 @@ async def nc_notes_search_notes(query: str, ctx: Context): async def nc_notes_delete_note(note_id: int, ctx: Context): logger.info("Deleting note %s", note_id) client: NextcloudClient = ctx.request_context.lifespan_context.client - return await client.notes_delete_note(note_id=note_id) + return await client.notes.delete_note(note_id) + + +# Tables tools +@mcp.tool() +async def nc_tables_list_tables(ctx: Context): + """List all tables available to the user""" + client: NextcloudClient = ctx.request_context.lifespan_context.client + return await client.tables.list_tables() + + +@mcp.tool() +async def nc_tables_get_schema(table_id: int, ctx: Context): + """Get the schema/structure of a specific table including columns and views""" + client: NextcloudClient = ctx.request_context.lifespan_context.client + return await client.tables.get_table_schema(table_id) + + +@mcp.tool() +async def nc_tables_read_table( + table_id: int, + limit: int | None = None, + offset: int | None = None, + ctx: Context = None, +): + """Read rows from a table with optional pagination""" + client: NextcloudClient = ctx.request_context.lifespan_context.client + return await client.tables.get_table_rows(table_id, limit, offset) + + +@mcp.tool() +async def nc_tables_insert_row(table_id: int, data: dict, ctx: Context): + """Insert a new row into a table. + + Data should be a dictionary mapping column IDs to values, e.g. {1: "text", 2: 42} + """ + client: NextcloudClient = ctx.request_context.lifespan_context.client + return await client.tables.create_row(table_id, data) + + +@mcp.tool() +async def nc_tables_update_row(row_id: int, data: dict, ctx: Context): + """Update an existing row in a table. + + Data should be a dictionary mapping column IDs to new values, e.g. {1: "new text", 2: 99} + """ + client: NextcloudClient = ctx.request_context.lifespan_context.client + return await client.tables.update_row(row_id, data) + + +@mcp.tool() +async def nc_tables_delete_row(row_id: int, ctx: Context): + """Delete a row from a table""" + client: NextcloudClient = ctx.request_context.lifespan_context.client + return await client.tables.delete_row(row_id) @mcp.resource("nc://Notes/{note_id}/attachments/{attachment_filename}") @@ -123,7 +176,7 @@ async def nc_notes_get_attachment(note_id: int, attachment_filename: str): client: NextcloudClient = ctx.request_context.lifespan_context.client # Assuming a method get_note_attachment exists in the client # This method should return the raw content and determine the mime type - content, mime_type = await client.get_note_attachment( + content, mime_type = await client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename ) return { diff --git a/nextcloud_mcp_server/tables_client.py b/nextcloud_mcp_server/tables_client.py new file mode 100644 index 0000000..d8554cb --- /dev/null +++ b/nextcloud_mcp_server/tables_client.py @@ -0,0 +1,125 @@ +"""Client for Nextcloud Tables app operations.""" + +from typing import Dict, List, Any, Optional +import logging + +from .base_client import BaseNextcloudClient + +logger = logging.getLogger(__name__) + + +class TablesClient(BaseNextcloudClient): + """Client for Nextcloud Tables app operations.""" + + async def list_tables(self) -> List[Dict[str, Any]]: + """List all tables available to the user.""" + response = await self._make_request( + "GET", + "/ocs/v2.php/apps/tables/api/2/tables", + headers={"OCS-APIRequest": "true", "Accept": "application/json"}, + ) + result = response.json() + return result["ocs"]["data"] + + async def get_table_schema(self, table_id: int) -> Dict[str, Any]: + """Get the schema/structure of a specific table including columns and views.""" + # Using v1 API as v2 schema endpoint had issues during testing + response = await self._make_request( + "GET", f"/index.php/apps/tables/api/1/tables/{table_id}/scheme" + ) + return response.json() + + async def get_table_rows( + self, table_id: int, limit: Optional[int] = None, offset: Optional[int] = None + ) -> List[Dict[str, Any]]: + """Read rows from a table with optional pagination.""" + params = {} + if limit is not None: + params["limit"] = limit + if offset is not None: + params["offset"] = offset + + response = await self._make_request( + "GET", f"/index.php/apps/tables/api/1/tables/{table_id}/rows", params=params + ) + return response.json() + + async def create_row(self, table_id: int, data: Dict[str, Any]) -> Dict[str, Any]: + """Insert a new row into a table. + + Args: + table_id: ID of the table to insert into + data: Dictionary mapping column IDs to values, e.g. {1: "text", 2: 42} + """ + # Transform data to API format: {"data": {"1": "text", "2": 42}} + api_data = {str(k): v for k, v in data.items()} + + response = await self._make_request( + "POST", + f"/ocs/v2.php/apps/tables/api/2/tables/{table_id}/rows", + headers={"OCS-APIRequest": "true", "Accept": "application/json"}, + json={"data": api_data}, + ) + result = response.json() + return result["ocs"]["data"] + + async def update_row(self, row_id: int, data: Dict[str, Any]) -> Dict[str, Any]: + """Update an existing row in a table. + + Args: + row_id: ID of the row to update + data: Dictionary mapping column IDs to new values, e.g. {1: "new text", 2: 99} + """ + # Transform data to API format for v1 endpoint + api_data = {str(k): v for k, v in data.items()} + + response = await self._make_request( + "PUT", + f"/index.php/apps/tables/api/1/rows/{row_id}", + json={"data": api_data}, + ) + return response.json() + + async def delete_row(self, row_id: int) -> Dict[str, Any]: + """Delete a row from a table.""" + response = await self._make_request( + "DELETE", f"/index.php/apps/tables/api/1/rows/{row_id}" + ) + return response.json() + + def transform_row_data( + self, rows: List[Dict[str, Any]], columns: List[Dict[str, Any]] + ) -> List[Dict[str, Any]]: + """Transform raw row data into more readable format using column names. + + Args: + rows: Raw row data from the API + columns: Column definitions from table schema + + Returns: + List of rows with column names as keys instead of column IDs + """ + # Create mapping from column ID to column title + column_map = {col["id"]: col["title"] for col in columns} + + transformed_rows = [] + for row in rows: + transformed_row = { + "id": row["id"], + "tableId": row["tableId"], + "createdBy": row["createdBy"], + "createdAt": row["createdAt"], + "lastEditBy": row["lastEditBy"], + "lastEditAt": row["lastEditAt"], + "data": {}, + } + + # Transform data array to column_name: value mapping + for item in row["data"]: + column_id = item["columnId"] + column_name = column_map.get(column_id, f"column_{column_id}") + transformed_row["data"][column_name] = item["value"] + + transformed_rows.append(transformed_row) + + return transformed_rows diff --git a/nextcloud_mcp_server/webdav_client.py b/nextcloud_mcp_server/webdav_client.py new file mode 100644 index 0000000..5dac2d3 --- /dev/null +++ b/nextcloud_mcp_server/webdav_client.py @@ -0,0 +1,244 @@ +"""WebDAV client for Nextcloud file operations.""" + +import mimetypes +from typing import Tuple, Dict, Any, Optional +import logging +from httpx import HTTPStatusError + +from .base_client import BaseNextcloudClient + +logger = logging.getLogger(__name__) + + +class WebDAVClient(BaseNextcloudClient): + """Client for Nextcloud WebDAV operations.""" + + async def delete_resource(self, path: str) -> Dict[str, Any]: + """Delete a resource (file or directory) via WebDAV DELETE.""" + # Ensure path ends with a slash if it's a directory + if not path.endswith("/"): + path_with_slash = f"{path}/" + else: + path_with_slash = path + + webdav_path = f"{self._get_webdav_base_path()}/{path_with_slash.lstrip('/')}" + logger.info(f"Deleting WebDAV resource: {webdav_path}") + + headers = {"OCS-APIRequest": "true"} + try: + # First try a PROPFIND to verify resource exists + propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"} + try: + propfind_resp = await self._client.request( + "PROPFIND", webdav_path, headers=propfind_headers + ) + logger.info( + f"Resource exists check (PROPFIND) status: {propfind_resp.status_code}" + ) + except HTTPStatusError as e: + if e.response.status_code == 404: + logger.info( + f"Resource '{webdav_path}' doesn't exist, no deletion needed." + ) + return {"status_code": 404} + # For other errors, continue with deletion attempt + + # Proceed with deletion + response = await self._client.delete(webdav_path, headers=headers) + response.raise_for_status() + logger.info( + f"Successfully deleted WebDAV resource '{webdav_path}' (Status: {response.status_code})" + ) + return {"status_code": response.status_code} + + except HTTPStatusError as e: + logger.warning(f"HTTP error deleting WebDAV resource '{webdav_path}': {e}") + if e.response.status_code != 404: + raise e + else: + logger.info(f"Resource '{webdav_path}' not found, no deletion needed.") + return {"status_code": 404} + except Exception as e: + logger.warning( + f"Unexpected error deleting WebDAV resource '{webdav_path}': {e}" + ) + raise e + + async def cleanup_old_attachment_directory( + self, note_id: int, old_category: str + ) -> Dict[str, Any]: + """Clean up the attachment directory for a note in its old category location.""" + old_category_path_part = f"{old_category}/" if old_category else "" + old_attachment_dir_path = ( + f"Notes/{old_category_path_part}.attachments.{note_id}/" + ) + + logger.info(f"Cleaning up old attachment directory: {old_attachment_dir_path}") + try: + delete_result = await self.delete_resource(path=old_attachment_dir_path) + logger.info(f"Cleanup of old attachment directory result: {delete_result}") + return delete_result + except Exception as e: + logger.error(f"Error during cleanup of old attachment directory: {e}") + raise e + + async def cleanup_note_attachments( + self, note_id: int, category: str + ) -> Dict[str, Any]: + """Clean up attachment directory for a specific note and category.""" + cat_path_part = f"{category}/" if category else "" + attachment_dir_path = f"Notes/{cat_path_part}.attachments.{note_id}/" + + logger.info( + f"Attempting to delete attachment directory for note {note_id} in category '{category}' via WebDAV: {attachment_dir_path}" + ) + try: + delete_result = await self.delete_resource(path=attachment_dir_path) + logger.info( + f"WebDAV deletion for category '{category}' attachment directory: {delete_result}" + ) + return delete_result + except Exception as e: + logger.warning( + f"Failed during WebDAV deletion for category '{category}' attachment directory: {e}" + ) + raise e + + async def add_note_attachment( + self, + note_id: int, + filename: str, + content: bytes, + category: Optional[str] = None, + mime_type: Optional[str] = None, + ) -> Dict[str, Any]: + """Add/Update an attachment to a note via WebDAV PUT.""" + # Construct paths based on provided category + webdav_base = self._get_webdav_base_path() + category_path_part = f"{category}/" if category else "" + attachment_dir_segment = f".attachments.{note_id}" + parent_dir_webdav_rel_path = ( + f"Notes/{category_path_part}{attachment_dir_segment}" + ) + parent_dir_path = f"{webdav_base}/{parent_dir_webdav_rel_path}" + attachment_path = f"{parent_dir_path}/{filename}" + + logger.info( + f"Uploading attachment for note {note_id} (category: '{category or ''}') to WebDAV path: {attachment_path}" + ) + + # Log current auth settings + logger.info( + f"WebDAV auth settings - Username: {self.username}, Auth Type: {type(self._client.auth).__name__}" + ) + + if not mime_type: + mime_type, _ = mimetypes.guess_type(filename) + if not mime_type: + mime_type = "application/octet-stream" + + headers = {"Content-Type": mime_type, "OCS-APIRequest": "true"} + try: + # First check if we can access WebDAV at all + notes_dir_path = f"{webdav_base}/Notes" + logger.info(f"Testing WebDAV access to Notes directory: {notes_dir_path}") + + propfind_headers = {"Depth": "0", "OCS-APIRequest": "true"} + notes_dir_response = await self._client.request( + "PROPFIND", notes_dir_path, headers=propfind_headers + ) + + if notes_dir_response.status_code == 401: + logger.error( + "WebDAV authentication failed for Notes directory. Please verify WebDAV permissions." + ) + raise HTTPStatusError( + f"Authentication error accessing WebDAV Notes directory: {notes_dir_response.status_code}", + request=notes_dir_response.request, + response=notes_dir_response, + ) + elif notes_dir_response.status_code >= 400: + logger.error( + f"Error accessing WebDAV Notes directory: {notes_dir_response.status_code}" + ) + notes_dir_response.raise_for_status() + else: + logger.info( + f"Successfully accessed WebDAV Notes directory (Status: {notes_dir_response.status_code})" + ) + + # Ensure the parent directory exists using MKCOL + logger.info(f"Ensuring attachments directory exists: {parent_dir_path}") + mkcol_headers = {"OCS-APIRequest": "true"} + mkcol_response = await self._client.request( + "MKCOL", parent_dir_path, headers=mkcol_headers + ) + + # MKCOL should return 201 Created or 405 Method Not Allowed (if directory already exists) + if mkcol_response.status_code not in [201, 405]: + logger.warning( + f"Unexpected status code {mkcol_response.status_code} when creating attachments directory" + ) + mkcol_response.raise_for_status() + else: + logger.info( + f"Created/verified directory: {parent_dir_path} (Status: {mkcol_response.status_code})" + ) + + # Proceed with the PUT request + logger.info(f"Putting attachment file to: {attachment_path}") + response = await self._client.put( + attachment_path, content=content, headers=headers + ) + response.raise_for_status() + logger.info( + f"Successfully uploaded attachment '{filename}' to note {note_id} (Status: {response.status_code})" + ) + return {"status_code": response.status_code} + + except HTTPStatusError as e: + logger.error( + f"HTTP error uploading attachment '{filename}' to note {note_id}: {e}" + ) + raise e + except Exception as e: + logger.error( + f"Unexpected error uploading attachment '{filename}' to note {note_id}: {e}" + ) + raise e + + async def get_note_attachment( + self, note_id: int, filename: str, category: Optional[str] = None + ) -> Tuple[bytes, str]: + """Fetch a specific attachment from a note via WebDAV GET.""" + webdav_base = self._get_webdav_base_path() + category_path_part = f"{category}/" if category else "" + attachment_dir_segment = f".attachments.{note_id}" + attachment_path = f"{webdav_base}/Notes/{category_path_part}{attachment_dir_segment}/{filename}" + + logger.info( + f"Fetching attachment for note {note_id} (category: '{category or ''}') from WebDAV path: {attachment_path}" + ) + + try: + response = await self._client.get(attachment_path) + response.raise_for_status() + + content = response.content + mime_type = response.headers.get("content-type", "application/octet-stream") + + logger.info( + f"Successfully fetched attachment '{filename}' ({mime_type}, {len(content)} bytes)" + ) + return content, mime_type + + except HTTPStatusError as e: + logger.error( + f"HTTP error fetching attachment '{filename}' for note {note_id}: {e}" + ) + raise e + except Exception as e: + logger.error( + f"Unexpected error fetching attachment '{filename}' for note {note_id}: {e}" + ) + raise e diff --git a/tables-openapi.json b/tables-openapi.json new file mode 100644 index 0000000..d436761 --- /dev/null +++ b/tables-openapi.json @@ -0,0 +1,10133 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "tables", + "version": "0.0.1", + "description": "Manage data the way you need it.", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "Capabilities": { + "type": "object", + "required": [ + "tables" + ], + "properties": { + "tables": { + "type": "object", + "required": [ + "enabled", + "version", + "apiVersions", + "features", + "isCirclesEnabled", + "column_types" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "version": { + "type": "string" + }, + "apiVersions": { + "type": "array", + "items": { + "type": "string" + } + }, + "features": { + "type": "array", + "items": { + "type": "string" + } + }, + "isCirclesEnabled": { + "type": "boolean" + }, + "column_types": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "Column": { + "type": "object", + "required": [ + "id", + "title", + "tableId", + "createdBy", + "createdAt", + "lastEditBy", + "lastEditAt", + "type", + "subtype", + "mandatory", + "description", + "orderWeight", + "numberDefault", + "numberMin", + "numberMax", + "numberDecimals", + "numberPrefix", + "numberSuffix", + "textDefault", + "textAllowedPattern", + "textMaxLength", + "selectionOptions", + "selectionDefault", + "datetimeDefault", + "usergroupDefault", + "usergroupMultipleItems", + "usergroupSelectUsers", + "usergroupSelectGroups", + "usergroupSelectTeams", + "showUserStatus" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "tableId": { + "type": "integer", + "format": "int64" + }, + "createdBy": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "lastEditBy": { + "type": "string" + }, + "lastEditAt": { + "type": "string" + }, + "type": { + "type": "string" + }, + "subtype": { + "type": "string" + }, + "mandatory": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "orderWeight": { + "type": "integer", + "format": "int64" + }, + "numberDefault": { + "type": "number", + "format": "double" + }, + "numberMin": { + "type": "number", + "format": "double" + }, + "numberMax": { + "type": "number", + "format": "double" + }, + "numberDecimals": { + "type": "integer", + "format": "int64" + }, + "numberPrefix": { + "type": "string" + }, + "numberSuffix": { + "type": "string" + }, + "textDefault": { + "type": "string" + }, + "textAllowedPattern": { + "type": "string" + }, + "textMaxLength": { + "type": "integer", + "format": "int64" + }, + "selectionOptions": { + "type": "string" + }, + "selectionDefault": { + "type": "string" + }, + "datetimeDefault": { + "type": "string" + }, + "usergroupDefault": { + "type": "string" + }, + "usergroupMultipleItems": { + "type": "boolean" + }, + "usergroupSelectUsers": { + "type": "boolean" + }, + "usergroupSelectGroups": { + "type": "boolean" + }, + "usergroupSelectTeams": { + "type": "boolean" + }, + "showUserStatus": { + "type": "boolean" + } + } + }, + "Context": { + "type": "object", + "required": [ + "id", + "name", + "iconName", + "description", + "owner", + "ownerType" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "iconName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "ownerType": { + "type": "integer", + "format": "int64" + } + } + }, + "ContextNavigation": { + "type": "object", + "required": [ + "id", + "shareId", + "displayMode", + "userId" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "shareId": { + "type": "integer", + "format": "int64" + }, + "displayMode": { + "type": "integer", + "format": "int64" + }, + "userId": { + "type": "string" + } + } + }, + "ImportState": { + "type": "object", + "required": [ + "found_columns_count", + "matching_columns_count", + "created_columns_count", + "inserted_rows_count", + "errors_parsing_count", + "errors_count" + ], + "properties": { + "found_columns_count": { + "type": "integer", + "format": "int64" + }, + "matching_columns_count": { + "type": "integer", + "format": "int64" + }, + "created_columns_count": { + "type": "integer", + "format": "int64" + }, + "inserted_rows_count": { + "type": "integer", + "format": "int64" + }, + "errors_parsing_count": { + "type": "integer", + "format": "int64" + }, + "errors_count": { + "type": "integer", + "format": "int64" + } + } + }, + "Index": { + "type": "object", + "required": [ + "tables", + "views" + ], + "properties": { + "tables": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Table" + } + }, + "views": { + "type": "array", + "items": { + "$ref": "#/components/schemas/View" + } + } + } + }, + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + }, + "Row": { + "type": "object", + "required": [ + "id", + "tableId", + "createdBy", + "createdAt", + "lastEditBy", + "lastEditAt", + "data" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "tableId": { + "type": "integer", + "format": "int64" + }, + "createdBy": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "lastEditBy": { + "type": "string" + }, + "lastEditAt": { + "type": "string" + }, + "data": { + "type": "object", + "nullable": true, + "required": [ + "columnId", + "value" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "value": { + "type": "object" + } + } + } + } + }, + "Share": { + "type": "object", + "required": [ + "id", + "sender", + "receiver", + "receiverDisplayName", + "receiverType", + "nodeId", + "nodeType", + "permissionRead", + "permissionCreate", + "permissionUpdate", + "permissionDelete", + "permissionManage", + "createdAt", + "createdBy" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "sender": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "receiverDisplayName": { + "type": "string" + }, + "receiverType": { + "type": "string" + }, + "nodeId": { + "type": "integer", + "format": "int64" + }, + "nodeType": { + "type": "string" + }, + "permissionRead": { + "type": "boolean" + }, + "permissionCreate": { + "type": "boolean" + }, + "permissionUpdate": { + "type": "boolean" + }, + "permissionDelete": { + "type": "boolean" + }, + "permissionManage": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "createdBy": { + "type": "string" + } + } + }, + "Table": { + "type": "object", + "required": [ + "id", + "title", + "emoji", + "ownership", + "ownerDisplayName", + "createdBy", + "createdAt", + "lastEditBy", + "lastEditAt", + "archived", + "favorite", + "isShared", + "onSharePermissions", + "hasShares", + "rowsCount", + "views", + "columnsCount" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "emoji": { + "type": "string", + "nullable": true + }, + "ownership": { + "type": "string" + }, + "ownerDisplayName": { + "type": "string" + }, + "createdBy": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "lastEditBy": { + "type": "string" + }, + "lastEditAt": { + "type": "string" + }, + "archived": { + "type": "boolean" + }, + "favorite": { + "type": "boolean" + }, + "isShared": { + "type": "boolean" + }, + "onSharePermissions": { + "type": "object", + "nullable": true, + "required": [ + "read", + "create", + "update", + "delete", + "manage" + ], + "properties": { + "read": { + "type": "boolean" + }, + "create": { + "type": "boolean" + }, + "update": { + "type": "boolean" + }, + "delete": { + "type": "boolean" + }, + "manage": { + "type": "boolean" + } + } + }, + "hasShares": { + "type": "boolean" + }, + "rowsCount": { + "type": "integer", + "format": "int64" + }, + "views": { + "type": "array", + "items": { + "$ref": "#/components/schemas/View" + } + }, + "columnsCount": { + "type": "integer", + "format": "int64" + } + } + }, + "View": { + "type": "object", + "required": [ + "id", + "title", + "emoji", + "tableId", + "ownership", + "ownerDisplayName", + "createdBy", + "createdAt", + "lastEditBy", + "lastEditAt", + "description", + "columns", + "columnSettings", + "sort", + "filter", + "isShared", + "favorite", + "onSharePermissions", + "hasShares", + "rowsCount" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "emoji": { + "type": "string", + "nullable": true + }, + "tableId": { + "type": "integer", + "format": "int64" + }, + "ownership": { + "type": "string" + }, + "ownerDisplayName": { + "type": "string", + "nullable": true + }, + "createdBy": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "lastEditBy": { + "type": "string" + }, + "lastEditAt": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "columns": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "columnSettings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "columnId", + "order" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "order": { + "type": "integer", + "format": "int64" + } + } + } + }, + "sort": { + "type": "array", + "items": { + "type": "object", + "required": [ + "columnId", + "mode" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "mode": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + } + } + }, + "filter": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "columnId", + "operator", + "value" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "operator": { + "type": "string", + "enum": [ + "begins-with", + "ends-with", + "contains", + "is-equal", + "is-greater-than", + "is-greater-than-or-equal", + "is-lower-than", + "is-lower-than-or-equal", + "is-empty" + ] + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + } + ] + } + } + } + } + }, + "isShared": { + "type": "boolean" + }, + "favorite": { + "type": "boolean" + }, + "onSharePermissions": { + "type": "object", + "nullable": true, + "required": [ + "read", + "create", + "update", + "delete", + "manage" + ], + "properties": { + "read": { + "type": "boolean" + }, + "create": { + "type": "boolean" + }, + "update": { + "type": "boolean" + }, + "delete": { + "type": "boolean" + }, + "manage": { + "type": "boolean" + } + } + }, + "hasShares": { + "type": "boolean" + }, + "rowsCount": { + "type": "integer", + "format": "int64" + } + } + } + } + }, + "paths": { + "/index.php/apps/tables/api/1/tables": { + "get": { + "operationId": "api1-index", + "summary": "Returns all Tables", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Table" + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api1-create-table", + "summary": "Create a new table and return it", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "description": "Title of the table" + }, + "emoji": { + "type": "string", + "nullable": true, + "description": "Emoji for the table" + }, + "template": { + "type": "string", + "default": "custom", + "description": "Template to use if wanted" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}": { + "put": { + "operationId": "api1-update-table", + "summary": "Update tables properties", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "nullable": true, + "description": "New table title" + }, + "emoji": { + "type": "string", + "nullable": true, + "description": "New table emoji" + }, + "archived": { + "type": "boolean", + "default": false, + "description": "Whether the table is archived" + } + } + } + } + } + }, + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "get": { + "operationId": "api1-get-table", + "summary": "Get a table object", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Table returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api1-delete-table", + "summary": "Delete a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Deleted table returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}/scheme": { + "get": { + "operationId": "api1-show-scheme", + "summary": "returns table scheme", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Table returned", + "headers": { + "Content-Disposition": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Table" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}/views": { + "get": { + "operationId": "api1-index-views", + "summary": "Get all views for a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Views returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/View" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api1-create-view", + "summary": "Create a new view for a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "description": "Title for the view" + }, + "emoji": { + "type": "string", + "nullable": true, + "description": "Emoji for the view" + } + } + } + } + } + }, + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID that will hold the view", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/View" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/views/{viewId}": { + "get": { + "operationId": "api1-get-view", + "summary": "Get a view object", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/View" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "api1-update-view", + "summary": "Update a view via key-value sets", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "description": "key-value pairs", + "anyOf": [ + { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "title", + "emoji", + "description" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "columns" + ] + }, + "value": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + }, + { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "sort" + ] + }, + "value": { + "type": "object", + "required": [ + "columnId", + "mode" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "mode": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + } + } + } + }, + { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "filter" + ] + }, + "value": { + "type": "object", + "required": [ + "columnId", + "operator", + "value" + ], + "properties": { + "columnId": { + "type": "integer", + "format": "int64" + }, + "operator": { + "type": "string", + "enum": [ + "begins-with", + "ends-with", + "contains", + "is-equal", + "is-greater-than", + "is-greater-than-or-equal", + "is-lower-than", + "is-lower-than-or-equal", + "is-empty" + ] + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + } + ] + } + } + } + } + } + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/View" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid data", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api1-delete-view", + "summary": "Delete a view", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/View" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/shares/{shareId}": { + "get": { + "operationId": "api1-get-share", + "summary": "Get a share object", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "shareId", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Share returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Share" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api1-delete-share", + "summary": "Delete a share", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "shareId", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Share" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "api1-update-share-permissions", + "summary": "Update a share permission", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "permissionType", + "permissionValue" + ], + "properties": { + "permissionType": { + "type": "string", + "description": "Permission type that should be changed" + }, + "permissionValue": { + "type": "boolean", + "description": "New permission value" + } + } + } + } + } + }, + "parameters": [ + { + "name": "shareId", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Share" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/views/{viewId}/shares": { + "get": { + "operationId": "api1-index-view-shares", + "summary": "Get all shares for a view Will be empty if view does not exist", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Shares returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Share" + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}/shares": { + "get": { + "operationId": "api1-index-table-shares", + "summary": "Get all shares for a table Will be empty if table does not exist", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Shares returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Share" + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api1-create-table-share", + "summary": "Create a share for a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "receiver", + "receiverType", + "permissionRead", + "permissionCreate", + "permissionUpdate", + "permissionDelete", + "permissionManage" + ], + "properties": { + "receiver": { + "type": "string", + "description": "Receiver ID" + }, + "receiverType": { + "type": "string", + "enum": [ + "user", + "group" + ], + "description": "Receiver type" + }, + "permissionRead": { + "type": "boolean", + "description": "Permission if receiver can read data" + }, + "permissionCreate": { + "type": "boolean", + "description": "Permission if receiver can create data" + }, + "permissionUpdate": { + "type": "boolean", + "description": "Permission if receiver can update data" + }, + "permissionDelete": { + "type": "boolean", + "description": "Permission if receiver can delete data" + }, + "permissionManage": { + "type": "boolean", + "description": "Permission if receiver can manage table" + } + } + } + } + } + }, + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Share" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/shares": { + "post": { + "operationId": "api1-create-share", + "summary": "Create a new share", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "nodeId", + "nodeType", + "receiver", + "receiverType" + ], + "properties": { + "nodeId": { + "type": "integer", + "format": "int64", + "description": "Node ID" + }, + "nodeType": { + "type": "string", + "enum": [ + "table", + "view", + "context" + ], + "description": "Node type" + }, + "receiver": { + "type": "string", + "description": "Receiver ID" + }, + "receiverType": { + "type": "string", + "enum": [ + "user", + "group" + ], + "description": "Receiver type" + }, + "permissionRead": { + "type": "boolean", + "default": false, + "description": "Permission if receiver can read data" + }, + "permissionCreate": { + "type": "boolean", + "default": false, + "description": "Permission if receiver can create data" + }, + "permissionUpdate": { + "type": "boolean", + "default": false, + "description": "Permission if receiver can update data" + }, + "permissionDelete": { + "type": "boolean", + "default": false, + "description": "Permission if receiver can delete data" + }, + "permissionManage": { + "type": "boolean", + "default": false, + "description": "Permission if receiver can manage node" + }, + "displayMode": { + "type": "integer", + "format": "int64", + "default": 2, + "description": "context shares only, whether it should appear in nav bar. 0: no, 1: recipients, 2: all (default). Cf. Application::NAV_ENTRY_MODE_*.", + "minimum": 0, + "maximum": 2 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Share returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Share" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/shares/{shareId}/display-mode": { + "put": { + "operationId": "api1-update-share-display-mode", + "summary": "Updates the display mode of a context share", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "displayMode" + ], + "properties": { + "displayMode": { + "type": "integer", + "format": "int64", + "description": "The new value for the display mode of the nav bar icon. 0: hidden, 1: visible for recipients, 2: visible for all", + "minimum": 0, + "maximum": 2 + }, + "target": { + "type": "string", + "default": "default", + "enum": [ + "default", + "self" + ], + "description": "\"default\" to set the default, \"self\" to set an override for the authenticated user" + } + } + } + } + } + }, + "parameters": [ + { + "name": "shareId", + "in": "path", + "description": "Share ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Display mode updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContextNavigation" + } + } + } + }, + "400": { + "description": "Invalid parameter", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Share not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}/columns": { + "get": { + "operationId": "api1-index-table-columns", + "summary": "Get all columns for a table or a underlying view Return an empty array if no columns were found", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "viewId", + "in": "query", + "description": "View ID", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Column" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api1-create-table-column", + "summary": "Create a new column for a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title", + "type", + "mandatory" + ], + "properties": { + "title": { + "type": "string", + "description": "Title" + }, + "type": { + "type": "string", + "enum": [ + "text", + "number", + "datetime", + "select", + "usergroup" + ], + "description": "Column main type" + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "Column sub type" + }, + "mandatory": { + "type": "boolean", + "description": "Is the column mandatory" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "numberPrefix": { + "type": "string", + "nullable": true, + "description": "Prefix if the column is a number field" + }, + "numberSuffix": { + "type": "string", + "nullable": true, + "description": "Suffix if the column is a number field" + }, + "numberDefault": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Default number, if column is a number" + }, + "numberMin": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Min value, if column is a number" + }, + "numberMax": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Max number, if column is a number" + }, + "numberDecimals": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Number of decimals, if column is a number" + }, + "textDefault": { + "type": "string", + "nullable": true, + "description": "Default text, if column is a text" + }, + "textAllowedPattern": { + "type": "string", + "nullable": true, + "description": "Allowed pattern (regex) for text columns (not yet implemented)" + }, + "textMaxLength": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Max length, if column is a text" + }, + "selectionOptions": { + "type": "string", + "nullable": true, + "default": "", + "description": "Options for a selection (json array{id: int, label: string})" + }, + "selectionDefault": { + "type": "string", + "nullable": true, + "default": "", + "description": "Default option IDs for a selection (json int[])" + }, + "datetimeDefault": { + "type": "string", + "nullable": true, + "default": "", + "description": "Default value, if column is datetime" + }, + "usergroupDefault": { + "type": "string", + "nullable": true, + "default": "", + "description": "Default value, if column is usergroup" + }, + "usergroupMultipleItems": { + "type": "boolean", + "nullable": true, + "description": "Can select multiple users or/and groups, if column is usergroup" + }, + "usergroupSelectUsers": { + "type": "boolean", + "nullable": true, + "description": "Can select users, if column type is usergroup" + }, + "usergroupSelectGroups": { + "type": "boolean", + "nullable": true, + "description": "Can select groups, if column type is usergroup" + }, + "usergroupSelectTeams": { + "type": "boolean", + "nullable": true, + "description": "Can select teams, if column type is usergroup" + }, + "usergroupShowUserStatus": { + "type": "boolean", + "nullable": true, + "description": "Whether to show the user's status, if column type is usergroup" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this column should be added to be presented", + "items": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Column" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/views/{viewId}/columns": { + "get": { + "operationId": "api1-index-view-columns", + "summary": "Get all columns for a view Return an empty array if no columns were found", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Column" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/columns": { + "post": { + "operationId": "api1-create-column", + "summary": "Create a column", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title", + "type", + "mandatory" + ], + "properties": { + "tableId": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Table ID" + }, + "viewId": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "View ID" + }, + "title": { + "type": "string", + "description": "Title" + }, + "type": { + "type": "string", + "enum": [ + "text", + "number", + "datetime", + "select", + "usergroup" + ], + "description": "Column main type" + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "Column sub type" + }, + "mandatory": { + "type": "boolean", + "description": "Is the column mandatory" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "numberPrefix": { + "type": "string", + "nullable": true, + "description": "Prefix if the column is a number field" + }, + "numberSuffix": { + "type": "string", + "nullable": true, + "description": "Suffix if the column is a number field" + }, + "numberDefault": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Default number, if column is a number" + }, + "numberMin": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Min value, if column is a number" + }, + "numberMax": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Max number, if column is a number" + }, + "numberDecimals": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Number of decimals, if column is a number" + }, + "textDefault": { + "type": "string", + "nullable": true, + "description": "Default text, if column is a text" + }, + "textAllowedPattern": { + "type": "string", + "nullable": true, + "description": "Allowed pattern (regex) for text columns (not yet implemented)" + }, + "textMaxLength": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Max length, if column is a text" + }, + "selectionOptions": { + "type": "string", + "nullable": true, + "default": "", + "description": "Options for a selection (json array{id: int, label: string})" + }, + "selectionDefault": { + "type": "string", + "nullable": true, + "default": "", + "description": "Default option IDs for a selection (json int[])" + }, + "datetimeDefault": { + "type": "string", + "nullable": true, + "default": "", + "description": "Default value, if column is datetime" + }, + "usergroupDefault": { + "type": "string", + "nullable": true, + "default": "", + "description": "Default value, if column is usergroup (json array{id: string, type: int})" + }, + "usergroupMultipleItems": { + "type": "boolean", + "nullable": true, + "description": "Can select multiple users or/and groups, if column is usergroup" + }, + "usergroupSelectUsers": { + "type": "boolean", + "nullable": true, + "description": "Can select users, if column type is usergroup" + }, + "usergroupSelectGroups": { + "type": "boolean", + "nullable": true, + "description": "Can select groups, if column type is usergroup" + }, + "usergroupSelectTeams": { + "type": "boolean", + "nullable": true, + "description": "Can select teams, if column type is usergroup" + }, + "usergroupShowUserStatus": { + "type": "boolean", + "nullable": true, + "description": "Whether to show the user's status, if column type is usergroup" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this column should be added to be presented", + "items": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Column" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/columns/{columnId}": { + "put": { + "operationId": "api1-update-column", + "summary": "Update a column", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "mandatory" + ], + "properties": { + "title": { + "type": "string", + "nullable": true, + "description": "Title" + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "Column sub type" + }, + "mandatory": { + "type": "boolean", + "description": "Is the column mandatory" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "numberPrefix": { + "type": "string", + "nullable": true, + "description": "Prefix if the column is a number field" + }, + "numberSuffix": { + "type": "string", + "nullable": true, + "description": "Suffix if the column is a number field" + }, + "numberDefault": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Default number, if column is a number" + }, + "numberMin": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Min value, if column is a number" + }, + "numberMax": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Max number, if column is a number" + }, + "numberDecimals": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Number of decimals, if column is a number" + }, + "textDefault": { + "type": "string", + "nullable": true, + "description": "Default text, if column is a text" + }, + "textAllowedPattern": { + "type": "string", + "nullable": true, + "description": "Allowed pattern (regex) for text columns (not yet implemented)" + }, + "textMaxLength": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Max length, if column is a text" + }, + "selectionOptions": { + "type": "string", + "nullable": true, + "description": "Options for a selection (json array{id: int, label: string})" + }, + "selectionDefault": { + "type": "string", + "nullable": true, + "description": "Default option IDs for a selection (json int[])" + }, + "datetimeDefault": { + "type": "string", + "nullable": true, + "description": "Default value, if column is datetime" + }, + "usergroupDefault": { + "type": "string", + "nullable": true, + "description": "Default value, if column is usergroup" + }, + "usergroupMultipleItems": { + "type": "boolean", + "nullable": true, + "description": "Can select multiple users or/and groups, if column is usergroup" + }, + "usergroupSelectUsers": { + "type": "boolean", + "nullable": true, + "description": "Can select users, if column type is usergroup" + }, + "usergroupSelectGroups": { + "type": "boolean", + "nullable": true, + "description": "Can select groups, if column type is usergroup" + }, + "usergroupSelectTeams": { + "type": "boolean", + "nullable": true, + "description": "Can select teams, if column type is usergroup" + }, + "usergroupShowUserStatus": { + "type": "boolean", + "nullable": true, + "description": "Whether to show the user's status, if column type is usergroup" + } + } + } + } + } + }, + "parameters": [ + { + "name": "columnId", + "in": "path", + "description": "Column ID that will be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Updated column", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Column" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "get": { + "operationId": "api1-get-column", + "summary": "Returns a column object", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "columnId", + "in": "path", + "description": "Wanted Column ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Column returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Column" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api1-delete-column", + "summary": "Delete a column", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "columnId", + "in": "path", + "description": "Wanted Column ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Deleted column returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Column" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}/rows/simple": { + "get": { + "operationId": "api1-index-table-rows-simple", + "summary": "List all rows values for a table, first row are the column titles", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "limit", + "in": "query", + "description": "Limit", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "offset", + "in": "query", + "description": "Offset", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Row values returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/tables/{tableId}/rows": { + "get": { + "operationId": "api1-index-table-rows", + "summary": "List all rows for a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "limit", + "in": "query", + "description": "Limit", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "offset", + "in": "query", + "description": "Offset", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Rows returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Row" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api1-create-row-in-table", + "summary": "Create a row within a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "description": "Data as key - value store", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Row returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Row" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/views/{viewId}/rows": { + "get": { + "operationId": "api1-index-view-rows", + "summary": "List all rows for a view", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "limit", + "in": "query", + "description": "Limit", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "offset", + "in": "query", + "description": "Offset", + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Rows returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Row" + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api1-create-row-in-view", + "summary": "Create a row within a view", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "description": "Data as key - value store", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Row returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Row" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/rows/{rowId}": { + "get": { + "operationId": "api1-get-row", + "summary": "Get a row", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "rowId", + "in": "path", + "description": "Row ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Row returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Row" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "api1-update-row", + "summary": "Update a row", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "viewId": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "View ID" + }, + "data": { + "description": "Data as key - value store", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "rowId", + "in": "path", + "description": "Row ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Updated row returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Row" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api1-delete-row", + "summary": "Delete a row", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "rowId", + "in": "path", + "description": "Row ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Deleted row returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Row" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/views/{viewId}/rows/{rowId}": { + "delete": { + "operationId": "api1-delete-row-by-view", + "summary": "Delete a row within a view", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "rowId", + "in": "path", + "description": "Row ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Deleted row returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Row" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/import/table/{tableId}": { + "post": { + "operationId": "api1-import-in-table", + "summary": "Import from file in to a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "description": "Path to file" + }, + "createMissingColumns": { + "type": "boolean", + "default": true, + "description": "Create missing columns" + } + } + } + } + } + }, + "parameters": [ + { + "name": "tableId", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Import status returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportState" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/index.php/apps/tables/api/1/import/views/{viewId}": { + "post": { + "operationId": "api1-import-in-view", + "summary": "Import from file in to a table", + "tags": [ + "api1" + ], + "security": [ + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "description": "Path to file" + }, + "createMissingColumns": { + "type": "boolean", + "default": true, + "description": "Create missing columns" + } + } + } + } + } + }, + "parameters": [ + { + "name": "viewId", + "in": "path", + "description": "View ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Import status returned", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportState" + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/init": { + "get": { + "operationId": "api_general-index", + "summary": "[api v2] Returns all main resources", + "description": "Tables and views incl. shares", + "tags": [ + "api_general" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Index returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Index" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/tables": { + "get": { + "operationId": "api_tables-index", + "summary": "[api v2] Returns all Tables", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "api_tables-create", + "summary": "[api v2] Create a new table and return it", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "description": "Title of the table" + }, + "emoji": { + "type": "string", + "nullable": true, + "description": "Emoji for the table" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description for the table" + }, + "template": { + "type": "string", + "default": "custom", + "description": "Template to use if wanted" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/tables/{id}": { + "get": { + "operationId": "api_tables-show", + "summary": "[api v2] Get a table object", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Table returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "put": { + "operationId": "api_tables-update", + "summary": "[api v2] Update tables properties", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "nullable": true, + "description": "New table title" + }, + "emoji": { + "type": "string", + "nullable": true, + "description": "New table emoji" + }, + "description": { + "type": "string", + "description": "the tables description" + }, + "archived": { + "type": "boolean", + "description": "whether the table is archived" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api_tables-destroy", + "summary": "[api v2] Delete a table", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Deleted table returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/tables/scheme/{id}": { + "get": { + "operationId": "api_tables-show-scheme", + "summary": "[api v2] Get a table Scheme", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Scheme returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/tables/scheme": { + "post": { + "operationId": "api_tables-create-from-scheme", + "summary": "creates table from scheme", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title", + "emoji", + "description", + "columns", + "views" + ], + "properties": { + "title": { + "type": "string", + "description": "title of new table" + }, + "emoji": { + "type": "string", + "description": "emoji" + }, + "description": { + "type": "string", + "description": "description" + }, + "columns": { + "type": "array", + "description": "columns", + "items": { + "$ref": "#/components/schemas/Column" + } + }, + "views": { + "type": "array", + "description": "views", + "items": { + "$ref": "#/components/schemas/View" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/tables/{id}/transfer": { + "put": { + "operationId": "api_tables-transfer", + "summary": "[api v2] Transfer table", + "description": "Transfer table from one user to another", + "tags": [ + "api_tables" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "newOwnerUserId" + ], + "properties": { + "newOwnerUserId": { + "type": "string", + "description": "New user ID" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Table ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Ownership changed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Table" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/{nodeType}/{nodeId}": { + "get": { + "operationId": "api_columns-index", + "summary": "[api v2] Get all columns for a table or a view", + "description": "Return an empty array if no columns were found", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "nodeType", + "in": "path", + "description": "Node type", + "required": true, + "schema": { + "type": "string", + "enum": [ + "table", + "view" + ] + } + }, + { + "name": "nodeId", + "in": "path", + "description": "Node ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "View deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/{id}": { + "get": { + "operationId": "api_columns-show", + "summary": "[api v2] Get a column object", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Column ID", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Column returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/number": { + "post": { + "operationId": "api_columns-create-number-column", + "summary": "[api v2] Create new numbered column", + "description": "Specify a subtype to use any special numbered column", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "baseNodeId", + "title" + ], + "properties": { + "baseNodeId": { + "type": "integer", + "format": "int64", + "description": "Context of the column creation" + }, + "title": { + "type": "string", + "description": "Title" + }, + "numberDefault": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Default value for new rows" + }, + "numberDecimals": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Decimals" + }, + "numberPrefix": { + "type": "string", + "nullable": true, + "description": "Prefix" + }, + "numberSuffix": { + "type": "string", + "nullable": true, + "description": "Suffix" + }, + "numberMin": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Min" + }, + "numberMax": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Max" + }, + "subtype": { + "type": "string", + "nullable": true, + "enum": [ + "progress", + "stars" + ], + "description": "Subtype for the new column" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this columns should be added", + "items": { + "type": "integer", + "format": "int64" + } + }, + "mandatory": { + "type": "boolean", + "default": false, + "description": "Is mandatory" + }, + "baseNodeType": { + "type": "string", + "default": "table", + "enum": [ + "table", + "view" + ], + "description": "Context type of the column creation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/text": { + "post": { + "operationId": "api_columns-create-text-column", + "summary": "[api v2] Create new text column", + "description": "Specify a subtype to use any special text column", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "baseNodeId", + "title" + ], + "properties": { + "baseNodeId": { + "type": "integer", + "format": "int64", + "description": "Context of the column creation" + }, + "title": { + "type": "string", + "description": "Title" + }, + "textDefault": { + "type": "string", + "nullable": true, + "description": "Default" + }, + "textAllowedPattern": { + "type": "string", + "nullable": true, + "description": "Allowed regex pattern" + }, + "textMaxLength": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Max raw text length" + }, + "subtype": { + "type": "string", + "nullable": true, + "enum": [ + "progress", + "stars" + ], + "description": "Subtype for the new column" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this columns should be added", + "items": { + "type": "integer", + "format": "int64" + } + }, + "mandatory": { + "type": "boolean", + "default": false, + "description": "Is mandatory" + }, + "baseNodeType": { + "type": "string", + "default": "table", + "enum": [ + "table", + "view" + ], + "description": "Context type of the column creation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/selection": { + "post": { + "operationId": "api_columns-create-selection-column", + "summary": "[api v2] Create new selection column", + "description": "Specify a subtype to use any special selection column", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "baseNodeId", + "title", + "selectionOptions" + ], + "properties": { + "baseNodeId": { + "type": "integer", + "format": "int64", + "description": "Context of the column creation" + }, + "title": { + "type": "string", + "description": "Title" + }, + "selectionOptions": { + "type": "string", + "description": "Json array{id: int, label: string} with options that can be selected, eg [{\"id\": 1, \"label\": \"first\"},{\"id\": 2, \"label\": \"second\"}]" + }, + "selectionDefault": { + "type": "string", + "nullable": true, + "description": "Json int|int[] for default selected option(s), eg 5 or [\"1\", \"8\"]" + }, + "subtype": { + "type": "string", + "nullable": true, + "enum": [ + "progress", + "stars" + ], + "description": "Subtype for the new column" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this columns should be added", + "items": { + "type": "integer", + "format": "int64" + } + }, + "mandatory": { + "type": "boolean", + "default": false, + "description": "Is mandatory" + }, + "baseNodeType": { + "type": "string", + "default": "table", + "enum": [ + "table", + "view" + ], + "description": "Context type of the column creation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/datetime": { + "post": { + "operationId": "api_columns-create-datetime-column", + "summary": "[api v2] Create new datetime column", + "description": "Specify a subtype to use any special datetime column", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "baseNodeId", + "title" + ], + "properties": { + "baseNodeId": { + "type": "integer", + "format": "int64", + "description": "Context of the column creation" + }, + "title": { + "type": "string", + "description": "Title" + }, + "datetimeDefault": { + "type": "string", + "nullable": true, + "enum": [ + "today", + "now" + ], + "description": "For a subtype 'date' you can set 'today'. For a main type or subtype 'time' you can set to 'now'." + }, + "subtype": { + "type": "string", + "nullable": true, + "enum": [ + "progress", + "stars" + ], + "description": "Subtype for the new column" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this columns should be added", + "items": { + "type": "integer", + "format": "int64" + } + }, + "mandatory": { + "type": "boolean", + "default": false, + "description": "Is mandatory" + }, + "baseNodeType": { + "type": "string", + "default": "table", + "enum": [ + "table", + "view" + ], + "description": "Context type of the column creation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/columns/usergroup": { + "post": { + "operationId": "api_columns-create-usergroup-column", + "summary": "[api v2] Create new usergroup column", + "tags": [ + "api_columns" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "baseNodeId", + "title" + ], + "properties": { + "baseNodeId": { + "type": "integer", + "format": "int64", + "description": "Context of the column creation" + }, + "title": { + "type": "string", + "description": "Title" + }, + "usergroupDefault": { + "type": "string", + "nullable": true, + "description": "Json array{id: string, type: int}, eg [{\"id\": \"admin\", \"type\": 0}, {\"id\": \"user1\", \"type\": 0}]" + }, + "usergroupMultipleItems": { + "type": "boolean", + "description": "Whether you can select multiple users or/and groups" + }, + "usergroupSelectUsers": { + "type": "boolean", + "description": "Whether you can select users" + }, + "usergroupSelectGroups": { + "type": "boolean", + "description": "Whether you can select groups" + }, + "usergroupSelectTeams": { + "type": "boolean", + "description": "Whether you can select teams" + }, + "showUserStatus": { + "type": "boolean", + "description": "Whether to show the user's status" + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description" + }, + "selectedViewIds": { + "type": "array", + "nullable": true, + "default": [], + "description": "View IDs where this columns should be added", + "items": { + "type": "integer", + "format": "int64" + } + }, + "mandatory": { + "type": "boolean", + "default": false, + "description": "Is mandatory" + }, + "baseNodeType": { + "type": "string", + "default": "table", + "enum": [ + "table", + "view" + ], + "description": "Context type of the column creation" + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Column created", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Column" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permission", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/favorites/{nodeType}/{nodeId}": { + "post": { + "operationId": "api_favorite-create", + "summary": "[api v2] Add a node (table or view) to user favorites", + "tags": [ + "api_favorite" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "nodeType", + "in": "path", + "description": "any Application::NODE_TYPE_* constant", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "nodeId", + "in": "path", + "description": "identifier of the node", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Tables returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "api_favorite-destroy", + "summary": "[api v2] Remove a node (table or view) to from favorites", + "tags": [ + "api_favorite" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "nodeType", + "in": "path", + "description": "any Application::NODE_TYPE_* constant", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "nodeId", + "in": "path", + "description": "identifier of the node", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Deleted table returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/contexts": { + "get": { + "operationId": "context-index", + "summary": "[api v2] Get all contexts available to the requesting person", + "description": "Return an empty array if no contexts were found", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "reporting in available contexts", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "post": { + "operationId": "context-create", + "summary": "[api v2] Create a new context and return it", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "iconName" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the context" + }, + "iconName": { + "type": "string", + "description": "Material design icon name of the context" + }, + "description": { + "type": "string", + "default": "", + "description": "Descriptive text of the context" + }, + "nodes": { + "type": "array", + "default": [], + "description": "optional nodes to be connected to this context", + "items": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "type": { + "type": "integer", + "format": "int64" + }, + "permissions": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "returning the full context information", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "invalid parameters were supplied", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "403": { + "description": "lacking permissions on a resource", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}": { + "get": { + "operationId": "context-show", + "summary": "[api v2] Get information about the requests context", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "returning the full context information", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "context not found or not available anymore", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "put": { + "operationId": "context-update", + "summary": "[api v2] Update an existing context and return it", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "provide this parameter to set a new name" + }, + "iconName": { + "type": "string", + "nullable": true, + "description": "provide this parameter to set a new icon" + }, + "description": { + "type": "string", + "nullable": true, + "description": "provide this parameter to set a new description" + }, + "nodes": { + "type": "object", + "nullable": true, + "description": "provide this parameter to set a new list of nodes.", + "required": [ + "id", + "type", + "permissions", + "order" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "type": { + "type": "integer", + "format": "int64" + }, + "permissions": { + "type": "integer", + "format": "int64" + }, + "order": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "returning the full context information", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "context-destroy", + "summary": "[api v2] Delete an existing context and return it", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "returning the full context information", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/transfer": { + "put": { + "operationId": "context-transfer", + "summary": "[api v2] Transfer the ownership of a context and return it", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "newOwnerId" + ], + "properties": { + "newOwnerId": { + "type": "string", + "description": "ID of the new owner" + }, + "newOwnerType": { + "type": "integer", + "format": "int64", + "default": 0, + "description": "any Application::OWNER_TYPE_* constant", + "minimum": 0, + "maximum": 0 + } + } + } + } + } + }, + "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Ownership transferred", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/contexts/{contextId}/pages/{pageId}": { + "put": { + "operationId": "context-update-content-order", + "summary": "[api v2] Update the order on a page of a context", + "tags": [ + "context" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "object", + "description": "content items with it and order values", + "required": [ + "id", + "order" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "order": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "contextId", + "in": "path", + "description": "ID of the context", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "pageId", + "in": "path", + "description": "ID of the page", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "content updated successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Context" + } + } + } + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/tables/api/2/{nodeCollection}/{nodeId}/rows": { + "post": { + "operationId": "rowocs-create-row", + "summary": "[api v2] Create a new row in a table or a view", + "tags": [ + "rowocs" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "description": "An array containing the column identifiers and their values", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "nodeCollection", + "in": "path", + "description": "Indicates whether to create a row on a table or view", + "required": true, + "schema": { + "type": "string", + "enum": [ + "tables", + "views" + ], + "pattern": "^(tables|views)$" + } + }, + { + "name": "nodeId", + "in": "path", + "description": "The identifier of the targeted table or view", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Row returned", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Row" + } + } + } + } + } + } + } + }, + "403": { + "description": "No permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid request parameters", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "Internal error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [ + { + "name": "navigation", + "description": "This is a workaround until https://github.com/nextcloud/server/pull/49904 is settled in all covered NC versions; expected >= 31." + } + ] +} diff --git a/tests/conftest.py b/tests/conftest.py index 621e7cc..3664720 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,8 @@ import pytest import os import logging import uuid -from nextcloud_mcp_server.client import NextcloudClient, HTTPStatusError +from nextcloud_mcp_server.client import NextcloudClient +from httpx import HTTPStatusError import asyncio logger = logging.getLogger(__name__) @@ -51,7 +52,7 @@ async def temporary_note(nc_client: NextcloudClient): logger.info(f"Creating temporary note: {note_title}") try: - created_note_data = await nc_client.notes_create_note( + created_note_data = await nc_client.notes.create_note( title=note_title, content=note_content, category=note_category ) note_id = created_note_data.get("id") @@ -65,7 +66,7 @@ async def temporary_note(nc_client: NextcloudClient): if note_id: logger.info(f"Cleaning up temporary note ID: {note_id}") try: - await nc_client.notes_delete_note(note_id=note_id) + await nc_client.notes.delete_note(note_id=note_id) logger.info(f"Successfully deleted temporary note ID: {note_id}") except HTTPStatusError as e: # Ignore 404 if note was already deleted by the test itself @@ -101,7 +102,7 @@ async def temporary_note_with_attachment( ) try: # Pass the category to add_note_attachment - upload_response = await nc_client.add_note_attachment( + upload_response = await nc_client.webdav.add_note_attachment( note_id=note_id, filename=attachment_filename, content=attachment_content, diff --git a/tests/integration/test_attachments.py b/tests/integration/test_attachments.py index 870a23f..a489583 100644 --- a/tests/integration/test_attachments.py +++ b/tests/integration/test_attachments.py @@ -29,7 +29,7 @@ async def test_attachments_add_and_get( f"Attempting to retrieve attachment '{attachment_filename}' added by fixture for note ID: {note_id}" ) # Pass category to get_note_attachment - retrieved_content, retrieved_mime = await nc_client.get_note_attachment( + retrieved_content, retrieved_mime = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=note_category ) logger.info( @@ -67,7 +67,7 @@ async def test_attachments_add_to_note_with_category( f"Attempting to add attachment '{attachment_filename}' to note ID: {note_id}" ) # Pass category to add_note_attachment - upload_response = await nc_client.add_note_attachment( + upload_response = await nc_client.webdav.add_note_attachment( note_id=note_id, filename=attachment_filename, content=attachment_content, @@ -86,7 +86,7 @@ async def test_attachments_add_to_note_with_category( f"Attempting to retrieve attachment '{attachment_filename}' from note ID: {note_id}" ) # Pass category to get_note_attachment - retrieved_content, retrieved_mime = await nc_client.get_note_attachment( + retrieved_content, retrieved_mime = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=note_category, # Pass the note's category @@ -127,13 +127,13 @@ async def test_attachments_cleanup_on_note_delete( # Manually delete the note logger.info(f"Manually deleting note ID: {note_id} within the test.") - await nc_client.notes_delete_note(note_id=note_id) + await nc_client.notes.delete_note(note_id=note_id) logger.info(f"Note ID: {note_id} deleted successfully.") time.sleep(1) # Verify Note Is Deleted with pytest.raises(HTTPStatusError) as excinfo_note: - await nc_client.notes_get_note(note_id=note_id) + await nc_client.notes.get_note(note_id=note_id) assert excinfo_note.value.response.status_code == 404 logger.info(f"Verified note {note_id} deletion (404 received).") @@ -145,7 +145,7 @@ async def test_attachments_cleanup_on_note_delete( # Pass category to get_note_attachment - although it should fail anyway # because the note (and thus details) are gone. # The client method will raise 404 from the initial notes_get_note call. - await nc_client.get_note_attachment( + await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=note_category, # Pass category, though note fetch should fail first @@ -205,7 +205,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient): try: # 1. Create note with initial category logger.info(f"Creating note '{note_title}' in category '{initial_category}'") - created_note = await nc_client.notes_create_note( + created_note = await nc_client.notes.create_note( title=note_title, content="Initial content", category=initial_category ) note_id = created_note["id"] @@ -217,7 +217,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient): logger.info( f"Adding attachment '{attachment_filename}' to note {note_id} (in {initial_category})" ) - upload_response = await nc_client.add_note_attachment( + upload_response = await nc_client.webdav.add_note_attachment( note_id=note_id, filename=attachment_filename, content=attachment_content, @@ -232,7 +232,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient): logger.info( f"Verifying attachment retrieval from initial category '{initial_category}'" ) - retrieved_content1, _ = await nc_client.get_note_attachment( + retrieved_content1, _ = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=initial_category ) assert retrieved_content1 == attachment_content @@ -243,9 +243,9 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient): f"Updating note {note_id} category from '{initial_category}' to '{new_category}'" ) # Need to fetch the latest etag after attachment add (WebDAV ops don't update note etag) - current_note_data = await nc_client.notes_get_note(note_id=note_id) + current_note_data = await nc_client.notes.get_note(note_id=note_id) current_etag = current_note_data["etag"] - updated_note = await nc_client.notes_update_note( + updated_note = await nc_client.notes.update( note_id=note_id, etag=current_etag, category=new_category, @@ -261,7 +261,7 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient): logger.info( f"Verifying attachment retrieval from new category '{new_category}'" ) - retrieved_content2, _ = await nc_client.get_note_attachment( + retrieved_content2, _ = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=new_category ) assert retrieved_content2 == attachment_content @@ -326,18 +326,18 @@ async def test_attachments_category_change_handling(nc_client: NextcloudClient): f"Cleaning up note ID: {note_id} (last known category: '{new_category}')" ) try: - await nc_client.notes_delete_note(note_id=note_id) + await nc_client.notes.delete_note(note_id=note_id) logger.info(f"Note {note_id} deleted.") time.sleep(1) # Verify note deletion with pytest.raises(HTTPStatusError) as excinfo_note_del: - await nc_client.notes_get_note(note_id=note_id) + await nc_client.notes.get_note(note_id=note_id) assert excinfo_note_del.value.response.status_code == 404 logger.info("Verified note deleted (404).") # Verify attachment deletion (should fail with 404 on the initial note fetch) with pytest.raises(HTTPStatusError) as excinfo_attach_del: # Pass the *last known* category, although the note fetch should fail first - await nc_client.get_note_attachment( + await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=new_category, diff --git a/tests/integration/test_embedded_images.py b/tests/integration/test_embedded_images.py index fc90f38..6ab1fa0 100644 --- a/tests/integration/test_embedded_images.py +++ b/tests/integration/test_embedded_images.py @@ -62,7 +62,7 @@ async def test_note_with_embedded_image( logger.info( f"Uploading image attachment '{attachment_filename}' to note {note_id} (category: '{note_category or ''}')..." ) - upload_response = await nc_client.add_note_attachment( + upload_response = await nc_client.webdav.add_note_attachment( note_id=note_id, filename=attachment_filename, content=image_content, @@ -115,7 +115,7 @@ async def test_note_with_embedded_image( Test Image HTML """ logger.info("Updating note content with image references...") - updated_note = await nc_client.notes_update_note( + updated_note = await nc_client.notes.update( note_id=note_id, etag=note_etag, # Use etag from the created note content=updated_content, @@ -128,7 +128,7 @@ async def test_note_with_embedded_image( time.sleep(1) # 3. Verify the updated note content - retrieved_note = await nc_client.notes_get_note(note_id=note_id) + retrieved_note = await nc_client.notes.get_note(note_id=note_id) assert f".attachments.{note_id}/{attachment_filename}" in retrieved_note["content"] logger.info("Verified image reference exists in updated note content.") @@ -137,7 +137,7 @@ async def test_note_with_embedded_image( f"Retrieving image attachment '{attachment_filename}' (category: '{note_category or ''}')..." ) # Pass category to get_note_attachment - retrieved_img_content, mime_type = await nc_client.get_note_attachment( + retrieved_img_content, mime_type = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=note_category ) assert retrieved_img_content == image_content @@ -150,13 +150,13 @@ async def test_note_with_embedded_image( logger.info( f"Manually deleting note ID: {note_id} to verify proper attachment cleanup" ) - await nc_client.notes_delete_note(note_id=note_id) + await nc_client.notes.delete_note(note_id=note_id) logger.info(f"Note ID: {note_id} deleted successfully.") time.sleep(1) # 6. Verify note is deleted with pytest.raises(HTTPStatusError) as excinfo_note: - await nc_client.notes_get_note(note_id=note_id) + await nc_client.notes.get_note(note_id=note_id) assert excinfo_note.value.response.status_code == 404 logger.info(f"Verified note {note_id} deletion (404 received).") diff --git a/tests/integration/test_notes_api.py b/tests/integration/test_notes_api.py index 5263e6e..7ffd9e9 100644 --- a/tests/integration/test_notes_api.py +++ b/tests/integration/test_notes_api.py @@ -24,7 +24,7 @@ async def test_notes_api_create_and_read( note_id = created_note_data["id"] logger.info(f"Reading note created by fixture, ID: {note_id}") - read_note = await nc_client.notes_get_note(note_id=note_id) + read_note = await nc_client.notes.get_note(note_id=note_id) assert read_note["id"] == note_id assert read_note["title"] == created_note_data["title"] @@ -46,7 +46,7 @@ async def test_notes_api_update(nc_client: NextcloudClient, temporary_note: dict update_content = f"Updated Content {uuid.uuid4().hex[:8]}" logger.info(f"Attempting to update note ID: {note_id} with etag: {original_etag}") - updated_note = await nc_client.notes_update_note( + updated_note = await nc_client.notes.update( note_id=note_id, etag=original_etag, title=update_title, @@ -66,7 +66,7 @@ async def test_notes_api_update(nc_client: NextcloudClient, temporary_note: dict # Optional: Verify update by reading again await asyncio.sleep(1) # Allow potential propagation delay - read_updated_note = await nc_client.notes_get_note(note_id=note_id) + read_updated_note = await nc_client.notes.get_note(note_id=note_id) assert read_updated_note["title"] == update_title assert read_updated_note["content"] == update_content logger.info(f"Successfully updated and verified note ID: {note_id}") @@ -85,7 +85,7 @@ async def test_notes_api_update_conflict( # Perform a first update to change the etag first_update_title = f"First Update {uuid.uuid4().hex[:8]}" logger.info(f"Performing first update on note ID: {note_id} to change etag.") - first_updated_note = await nc_client.notes_update_note( + first_updated_note = await nc_client.notes.update( note_id=note_id, etag=original_etag, title=first_update_title, @@ -102,7 +102,7 @@ async def test_notes_api_update_conflict( f"Attempting second update on note ID: {note_id} with OLD etag: {original_etag}" ) with pytest.raises(HTTPStatusError) as excinfo: - await nc_client.notes_update_note( + await nc_client.notes.update( note_id=note_id, etag=original_etag, # Use the stale etag title="This update should fail due to conflict", @@ -119,7 +119,7 @@ async def test_notes_api_delete_nonexistent(nc_client: NextcloudClient): non_existent_id = 999999999 # Use an ID highly unlikely to exist logger.info(f"\nAttempting to delete non-existent note ID: {non_existent_id}") with pytest.raises(HTTPStatusError) as excinfo: - await nc_client.notes_delete_note(note_id=non_existent_id) + await nc_client.notes.delete_note(note_id=non_existent_id) assert excinfo.value.response.status_code == 404 logger.info( f"Deleting non-existent note ID: {non_existent_id} correctly failed with 404." @@ -139,7 +139,7 @@ async def test_notes_api_append_content_to_existing_note( append_text = f"Appended content {uuid.uuid4().hex[:8]}" logger.info(f"Appending content to note ID: {note_id}") - updated_note = await nc_client.notes_append_content( + updated_note = await nc_client.notes.append_content( note_id=note_id, content=append_text ) logger.info(f"Note after append: {updated_note}") @@ -155,7 +155,7 @@ async def test_notes_api_append_content_to_existing_note( # Verify by reading the note again await asyncio.sleep(1) # Allow potential propagation delay - read_note = await nc_client.notes_get_note(note_id=note_id) + read_note = await nc_client.notes.get_note(note_id=note_id) assert read_note["content"] == expected_content logger.info(f"Successfully appended content to note ID: {note_id}") @@ -169,7 +169,7 @@ async def test_notes_api_append_content_to_empty_note(nc_client: NextcloudClient test_category = "Test" logger.info("Creating empty note for append test") - empty_note = await nc_client.notes_create_note( + empty_note = await nc_client.notes.create_note( title=test_title, content="", category=test_category, # Empty content @@ -180,7 +180,7 @@ async def test_notes_api_append_content_to_empty_note(nc_client: NextcloudClient append_text = f"First content {uuid.uuid4().hex[:8]}" logger.info(f"Appending content to empty note ID: {note_id}") - updated_note = await nc_client.notes_append_content( + updated_note = await nc_client.notes.append_content( note_id=note_id, content=append_text ) @@ -189,14 +189,14 @@ async def test_notes_api_append_content_to_empty_note(nc_client: NextcloudClient # Verify by reading the note again await asyncio.sleep(1) - read_note = await nc_client.notes_get_note(note_id=note_id) + read_note = await nc_client.notes.get_note(note_id=note_id) assert read_note["content"] == append_text logger.info(f"Successfully appended content to empty note ID: {note_id}") finally: # Clean up the test note try: - await nc_client.notes_delete_note(note_id=note_id) + await nc_client.notes.delete_note(note_id=note_id) logger.info(f"Cleaned up test note ID: {note_id}") except Exception as e: logger.warning(f"Failed to clean up test note ID: {note_id}: {e}") @@ -218,7 +218,7 @@ async def test_notes_api_append_content_multiple_times( logger.info(f"Performing multiple appends to note ID: {note_id}") # First append - updated_note = await nc_client.notes_append_content( + updated_note = await nc_client.notes.append_content( note_id=note_id, content=first_append ) @@ -226,7 +226,7 @@ async def test_notes_api_append_content_multiple_times( assert updated_note["content"] == expected_content_after_first # Second append - updated_note = await nc_client.notes_append_content( + updated_note = await nc_client.notes.append_content( note_id=note_id, content=second_append ) @@ -237,7 +237,7 @@ async def test_notes_api_append_content_multiple_times( # Verify by reading the note again await asyncio.sleep(1) - read_note = await nc_client.notes_get_note(note_id=note_id) + read_note = await nc_client.notes.get_note(note_id=note_id) assert read_note["content"] == expected_content_after_second logger.info(f"Successfully performed multiple appends to note ID: {note_id}") @@ -250,13 +250,10 @@ async def test_notes_api_append_content_nonexistent_note(nc_client: NextcloudCli logger.info(f"Attempting to append to non-existent note ID: {non_existent_id}") with pytest.raises(HTTPStatusError) as excinfo: - await nc_client.notes_append_content( + await nc_client.notes.append_content( note_id=non_existent_id, content="This should fail" ) assert excinfo.value.response.status_code == 404 logger.info( f"Appending to non-existent note ID: {non_existent_id} correctly failed with 404." ) - - -# --- Attachment tests moved to test_attachments.py --- diff --git a/tests/integration/test_tables_api.py b/tests/integration/test_tables_api.py new file mode 100644 index 0000000..e03ad68 --- /dev/null +++ b/tests/integration/test_tables_api.py @@ -0,0 +1,534 @@ +import pytest +import logging +import asyncio +import uuid +from httpx import HTTPStatusError +from typing import Dict, Any + +from nextcloud_mcp_server.client import NextcloudClient + +logger = logging.getLogger(__name__) + +# Mark all tests in this module as integration tests +pytestmark = pytest.mark.integration + + +@pytest.fixture(scope="session") +async def sample_table_info(nc_client: NextcloudClient) -> Dict[str, Any]: + """ + Fixture to get information about the sample table that comes with Nextcloud Tables. + This assumes that the sample table exists in the Nextcloud instance. + """ + logger.info("Looking for sample table in Nextcloud Tables app") + + # Get all tables + tables = await nc_client.tables.list_tables() + + # Look for a sample table (usually created by default) + sample_table = None + for table in tables: + # Common names for sample tables + if any( + keyword in table.get("title", "").lower() + for keyword in ["sample", "demo", "example", "test"] + ): + sample_table = table + break + + if not sample_table and tables: + # If no sample table found, use the first available table + sample_table = tables[0] + logger.info( + f"No sample table found, using first available table: {sample_table.get('title')}" + ) + + if not sample_table: + pytest.skip( + "No tables found in Nextcloud Tables app. Please ensure Tables app is installed and has at least one table." + ) + + # Get the schema for the sample table + table_id = sample_table["id"] + schema = await nc_client.tables.get_table_schema(table_id) + + logger.info(f"Using sample table: {sample_table.get('title')} (ID: {table_id})") + + return { + "table": sample_table, + "schema": schema, + "table_id": table_id, + "columns": schema.get("columns", []), + } + + +@pytest.fixture +async def temporary_table_row( + nc_client: NextcloudClient, sample_table_info: Dict[str, Any] +): + """ + Fixture to create a temporary row in the sample table for testing. + Yields the created row data and cleans up afterward. + """ + table_id = sample_table_info["table_id"] + columns = sample_table_info["columns"] + + # Create test data based on the table schema + test_data = {} + unique_suffix = uuid.uuid4().hex[:8] + + for column in columns: + column_id = column["id"] + column_type = column.get("type", "text") + column_title = column.get("title", f"column_{column_id}") + + # Generate test data based on column type + if column_type == "text": + test_data[column_id] = f"Test {column_title} {unique_suffix}" + elif column_type == "number": + test_data[column_id] = 42 + elif column_type == "datetime": + test_data[column_id] = "2024-01-01T12:00:00Z" + elif column_type == "select": + # For select columns, use the first option if available + options = column.get("selectOptions", []) + if options: + test_data[column_id] = options[0].get("label", "Option 1") + else: + test_data[column_id] = "Test Option" + else: + # Default to text for unknown types + test_data[column_id] = f"Test {column_title} {unique_suffix}" + + logger.info(f"Creating temporary row in table {table_id} with data: {test_data}") + + created_row = None + try: + created_row = await nc_client.tables.create_row(table_id, test_data) + row_id = created_row.get("id") + + if not row_id: + pytest.fail("Failed to get ID from created temporary row.") + + logger.info(f"Temporary row created with ID: {row_id}") + yield created_row + + finally: + if created_row and created_row.get("id"): + row_id = created_row["id"] + logger.info(f"Cleaning up temporary row ID: {row_id}") + try: + await nc_client.tables.delete_row(row_id) + logger.info(f"Successfully deleted temporary row ID: {row_id}") + except HTTPStatusError as e: + # Ignore 404 if row was already deleted by the test itself + if e.response.status_code != 404: + logger.error(f"HTTP error deleting temporary row {row_id}: {e}") + else: + logger.warning(f"Temporary row {row_id} already deleted (404).") + except Exception as e: + logger.error(f"Unexpected error deleting temporary row {row_id}: {e}") + + +async def test_tables_list_tables(nc_client: NextcloudClient): + """ + Test listing all tables available to the user. + """ + logger.info("Testing list_tables functionality") + + tables = await nc_client.tables.list_tables() + + assert isinstance(tables, list) + assert len(tables) > 0, "Expected at least one table to be available" + + # Check that each table has required fields + for table in tables: + assert "id" in table + assert "title" in table + assert isinstance(table["id"], int) + assert isinstance(table["title"], str) + + logger.info(f"Successfully listed {len(tables)} tables") + + +async def test_tables_get_schema( + nc_client: NextcloudClient, sample_table_info: Dict[str, Any] +): + """ + Test getting the schema/structure of a specific table. + """ + table_id = sample_table_info["table_id"] + + logger.info(f"Testing get_table_schema for table ID: {table_id}") + + schema = await nc_client.tables.get_table_schema(table_id) + + assert isinstance(schema, dict) + assert "columns" in schema + assert isinstance(schema["columns"], list) + assert len(schema["columns"]) > 0, "Expected at least one column in the table" + + # Check that each column has required fields + for column in schema["columns"]: + assert "id" in column + assert "title" in column + assert "type" in column + assert isinstance(column["id"], int) + assert isinstance(column["title"], str) + assert isinstance(column["type"], str) + + logger.info(f"Successfully retrieved schema with {len(schema['columns'])} columns") + + +async def test_tables_read_table( + nc_client: NextcloudClient, sample_table_info: Dict[str, Any] +): + """ + Test reading rows from a table. + """ + table_id = sample_table_info["table_id"] + + logger.info(f"Testing get_table_rows for table ID: {table_id}") + + # Test without pagination + rows = await nc_client.tables.get_table_rows(table_id) + + assert isinstance(rows, list) + # Note: The table might be empty, so we don't assert len > 0 + + # Test with pagination + rows_limited = await nc_client.tables.get_table_rows(table_id, limit=5, offset=0) + + assert isinstance(rows_limited, list) + assert len(rows_limited) <= 5 + + # If there are rows, check their structure + if rows: + row = rows[0] + assert "id" in row + assert "tableId" in row + assert "data" in row + assert isinstance(row["id"], int) + assert isinstance(row["tableId"], int) + assert isinstance(row["data"], list) + + logger.info(f"Successfully read {len(rows)} rows from table") + + +async def test_tables_create_row( + nc_client: NextcloudClient, sample_table_info: Dict[str, Any] +): + """ + Test creating a new row in a table. + """ + table_id = sample_table_info["table_id"] + columns = sample_table_info["columns"] + + # Create test data based on the table schema + test_data = {} + unique_suffix = uuid.uuid4().hex[:8] + + for column in columns: + column_id = column["id"] + column_type = column.get("type", "text") + column_title = column.get("title", f"column_{column_id}") + + # Generate test data based on column type + if column_type == "text": + test_data[column_id] = f"Test Create {column_title} {unique_suffix}" + elif column_type == "number": + test_data[column_id] = 123 + elif column_type == "datetime": + test_data[column_id] = "2024-01-01T12:00:00Z" + elif column_type == "select": + # For select columns, use the first option if available + options = column.get("selectOptions", []) + if options: + test_data[column_id] = options[0].get("label", "Option 1") + else: + test_data[column_id] = "Test Option" + else: + # Default to text for unknown types + test_data[column_id] = f"Test Create {column_title} {unique_suffix}" + + logger.info(f"Testing create_row for table ID: {table_id} with data: {test_data}") + + created_row = None + try: + created_row = await nc_client.tables.create_row(table_id, test_data) + + assert isinstance(created_row, dict) + assert "id" in created_row + assert "tableId" in created_row + assert isinstance(created_row["id"], int) + assert created_row["tableId"] == table_id + + # Verify the row was created by reading it back + await asyncio.sleep(1) # Allow potential propagation delay + rows = await nc_client.tables.get_table_rows(table_id) + created_row_id = created_row["id"] + + # Find the created row in the results + found_row = None + for row in rows: + if row["id"] == created_row_id: + found_row = row + break + + assert found_row is not None, ( + f"Created row with ID {created_row_id} not found in table" + ) + + logger.info(f"Successfully created row with ID: {created_row_id}") + + finally: + # Clean up the created row + if created_row and created_row.get("id"): + try: + await nc_client.tables.delete_row(created_row["id"]) + logger.info(f"Cleaned up created row ID: {created_row['id']}") + except Exception as e: + logger.warning(f"Failed to clean up created row: {e}") + + +async def test_tables_update_row( + nc_client: NextcloudClient, + temporary_table_row: Dict[str, Any], + sample_table_info: Dict[str, Any], +): + """ + Test updating an existing row in a table. + """ + row_id = temporary_table_row["id"] + columns = sample_table_info["columns"] + + # Create updated data + update_data = {} + unique_suffix = uuid.uuid4().hex[:8] + + for column in columns: + column_id = column["id"] + column_type = column.get("type", "text") + column_title = column.get("title", f"column_{column_id}") + + # Generate updated test data based on column type + if column_type == "text": + update_data[column_id] = f"Updated {column_title} {unique_suffix}" + elif column_type == "number": + update_data[column_id] = 456 + elif column_type == "datetime": + update_data[column_id] = "2024-12-31T23:59:59Z" + elif column_type == "select": + # For select columns, use the first option if available + options = column.get("selectOptions", []) + if options: + update_data[column_id] = options[0].get("label", "Option 1") + else: + update_data[column_id] = "Updated Option" + else: + # Default to text for unknown types + update_data[column_id] = f"Updated {column_title} {unique_suffix}" + + logger.info(f"Testing update_row for row ID: {row_id} with data: {update_data}") + + updated_row = await nc_client.tables.update_row(row_id, update_data) + + assert isinstance(updated_row, dict) + assert "id" in updated_row + assert updated_row["id"] == row_id + + # Verify the row was updated by reading it back + await asyncio.sleep(1) # Allow potential propagation delay + table_id = sample_table_info["table_id"] + rows = await nc_client.tables.get_table_rows(table_id) + + # Find the updated row in the results + found_row = None + for row in rows: + if row["id"] == row_id: + found_row = row + break + + assert found_row is not None, f"Updated row with ID {row_id} not found in table" + + logger.info(f"Successfully updated row with ID: {row_id}") + + +async def test_tables_delete_row( + nc_client: NextcloudClient, sample_table_info: Dict[str, Any] +): + """ + Test deleting a row from a table. + """ + table_id = sample_table_info["table_id"] + columns = sample_table_info["columns"] + + # First create a row to delete + test_data = {} + unique_suffix = uuid.uuid4().hex[:8] + + for column in columns: + column_id = column["id"] + column_type = column.get("type", "text") + column_title = column.get("title", f"column_{column_id}") + + if column_type == "text": + test_data[column_id] = f"Test Delete {column_title} {unique_suffix}" + elif column_type == "number": + test_data[column_id] = 789 + elif column_type == "datetime": + test_data[column_id] = "2024-06-15T10:30:00Z" + elif column_type == "select": + options = column.get("selectOptions", []) + if options: + test_data[column_id] = options[0].get("label", "Option 1") + else: + test_data[column_id] = "Delete Option" + else: + test_data[column_id] = f"Test Delete {column_title} {unique_suffix}" + + logger.info(f"Creating row for delete test in table ID: {table_id}") + + created_row = await nc_client.tables.create_row(table_id, test_data) + row_id = created_row["id"] + + logger.info(f"Testing delete_row for row ID: {row_id}") + + # Delete the row + delete_result = await nc_client.tables.delete_row(row_id) + + assert isinstance(delete_result, dict) + # The delete response might vary, but it should be successful + + # Verify the row was deleted by trying to find it + await asyncio.sleep(1) # Allow potential propagation delay + rows = await nc_client.tables.get_table_rows(table_id) + + # Ensure the deleted row is not in the results + found_row = None + for row in rows: + if row["id"] == row_id: + found_row = row + break + + assert found_row is None, f"Deleted row with ID {row_id} still found in table" + + logger.info(f"Successfully deleted row with ID: {row_id}") + + +async def test_tables_delete_nonexistent_row(nc_client: NextcloudClient): + """ + Test that deleting a non-existent row fails appropriately. + """ + non_existent_id = 999999999 # Use an ID highly unlikely to exist + + logger.info(f"Testing delete_row for non-existent row ID: {non_existent_id}") + + with pytest.raises(HTTPStatusError) as excinfo: + await nc_client.tables.delete_row(non_existent_id) + + # Accept both 404 and 500 as valid error responses for non-existent rows + # The API behavior may vary between Nextcloud versions + assert excinfo.value.response.status_code in [404, 500] + logger.info( + f"Deleting non-existent row ID: {non_existent_id} correctly failed with {excinfo.value.response.status_code}." + ) + + +async def test_tables_transform_row_data( + nc_client: NextcloudClient, sample_table_info: Dict[str, Any] +): + """ + Test the transform_row_data utility method. + """ + table_id = sample_table_info["table_id"] + columns = sample_table_info["columns"] + + logger.info(f"Testing transform_row_data for table ID: {table_id}") + + # Get some rows to transform + rows = await nc_client.tables.get_table_rows(table_id, limit=5) + + if not rows: + logger.info("No rows to transform, skipping transform_row_data test") + return + + # Transform the rows + transformed_rows = nc_client.tables.transform_row_data(rows, columns) + + assert isinstance(transformed_rows, list) + assert len(transformed_rows) == len(rows) + + # Check the structure of transformed rows + for i, transformed_row in enumerate(transformed_rows): + original_row = rows[i] + + assert "id" in transformed_row + assert "tableId" in transformed_row + assert "data" in transformed_row + assert transformed_row["id"] == original_row["id"] + assert transformed_row["tableId"] == original_row["tableId"] + assert isinstance(transformed_row["data"], dict) + + # Check that column IDs were transformed to column names + for column in columns: + column_title = column["title"] + # The transformed data should have column names as keys + # (though the column might not have data in this row) + if any(item["columnId"] == column["id"] for item in original_row["data"]): + assert column_title in transformed_row["data"] + + logger.info(f"Successfully transformed {len(transformed_rows)} rows") + + +async def test_tables_get_nonexistent_table_schema(nc_client: NextcloudClient): + """ + Test that getting schema for a non-existent table fails appropriately. + """ + non_existent_id = 999999999 # Use an ID highly unlikely to exist + + logger.info( + f"Testing get_table_schema for non-existent table ID: {non_existent_id}" + ) + + with pytest.raises(HTTPStatusError) as excinfo: + await nc_client.tables.get_table_schema(non_existent_id) + + assert excinfo.value.response.status_code == 404 + logger.info( + f"Getting schema for non-existent table ID: {non_existent_id} correctly failed with 404." + ) + + +async def test_tables_read_nonexistent_table(nc_client: NextcloudClient): + """ + Test that reading from a non-existent table fails appropriately. + """ + non_existent_id = 999999999 # Use an ID highly unlikely to exist + + logger.info(f"Testing get_table_rows for non-existent table ID: {non_existent_id}") + + with pytest.raises(HTTPStatusError) as excinfo: + await nc_client.tables.get_table_rows(non_existent_id) + + assert excinfo.value.response.status_code == 404 + logger.info( + f"Reading from non-existent table ID: {non_existent_id} correctly failed with 404." + ) + + +async def test_tables_create_row_invalid_table(nc_client: NextcloudClient): + """ + Test that creating a row in a non-existent table fails appropriately. + """ + non_existent_id = 999999999 # Use an ID highly unlikely to exist + test_data = {1: "test value"} + + logger.info(f"Testing create_row for non-existent table ID: {non_existent_id}") + + with pytest.raises(HTTPStatusError) as excinfo: + await nc_client.tables.create_row(non_existent_id, test_data) + + assert excinfo.value.response.status_code == 404 + logger.info( + f"Creating row in non-existent table ID: {non_existent_id} correctly failed with 404." + ) diff --git a/tests/integration/test_webdav_cleanup.py b/tests/integration/test_webdav_cleanup.py index be6c2f0..3d197d8 100644 --- a/tests/integration/test_webdav_cleanup.py +++ b/tests/integration/test_webdav_cleanup.py @@ -29,7 +29,7 @@ async def test_category_change_cleans_up_old_attachments_directory( try: # 1. Create note with initial category logger.info(f"Creating note '{note_title}' in category '{initial_category}'") - created_note = await nc_client.notes_create_note( + created_note = await nc_client.notes.create_note( title=note_title, content="Initial content", category=initial_category ) note_id = created_note["id"] @@ -41,7 +41,7 @@ async def test_category_change_cleans_up_old_attachments_directory( logger.info( f"Adding attachment '{attachment_filename}' to note {note_id} (in {initial_category})" ) - upload_response = await nc_client.add_note_attachment( + upload_response = await nc_client.webdav.add_note_attachment( note_id=note_id, filename=attachment_filename, content=attachment_content, @@ -56,7 +56,7 @@ async def test_category_change_cleans_up_old_attachments_directory( logger.info( f"Verifying attachment retrieval from initial category '{initial_category}'" ) - retrieved_content1, _ = await nc_client.get_note_attachment( + retrieved_content1, _ = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=initial_category ) assert retrieved_content1 == attachment_content @@ -72,9 +72,9 @@ async def test_category_change_cleans_up_old_attachments_directory( logger.info( f"Updating note {note_id} category from '{initial_category}' to '{new_category}'" ) - current_note_data = await nc_client.notes_get_note(note_id=note_id) + current_note_data = await nc_client.notes.get_note(note_id=note_id) current_etag = current_note_data["etag"] - updated_note = await nc_client.notes_update_note( + updated_note = await nc_client.notes.update( note_id=note_id, etag=current_etag, category=new_category, @@ -90,7 +90,7 @@ async def test_category_change_cleans_up_old_attachments_directory( logger.info( f"Verifying attachment retrieval from new category '{new_category}'" ) - retrieved_content2, _ = await nc_client.get_note_attachment( + retrieved_content2, _ = await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=new_category ) assert retrieved_content2 == attachment_content @@ -101,7 +101,7 @@ async def test_category_change_cleans_up_old_attachments_directory( f"Trying to retrieve attachment from old category '{initial_category}' - should fail" ) try: - await nc_client.get_note_attachment( + await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=initial_category ) # If we get here, it means the old directory still exists (a problem) @@ -165,14 +165,14 @@ async def test_category_change_cleans_up_old_attachments_directory( if note_id: logger.info(f"Cleaning up note ID: {note_id}") try: - await nc_client.notes_delete_note(note_id=note_id) + await nc_client.notes.delete_note(note_id=note_id) logger.info(f"Note {note_id} deleted.") time.sleep(1) # 9. Verify both old and new attachment paths are gone logger.info("Verifying all attachment paths are gone") with pytest.raises(HTTPStatusError) as excinfo_new: - await nc_client.get_note_attachment( + await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=new_category, @@ -180,7 +180,7 @@ async def test_category_change_cleans_up_old_attachments_directory( assert excinfo_new.value.response.status_code == 404 with pytest.raises(HTTPStatusError) as excinfo_old: - await nc_client.get_note_attachment( + await nc_client.webdav.get_note_attachment( note_id=note_id, filename=attachment_filename, category=initial_category,