mirror of
				https://github.com/dgtlmoon/changedetection.io.git
				synced 2025-11-04 00:27:48 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			0.50.37
			...
			OpenAPI-va
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3acf9fa60d | ||
| 
						 | 
					001d294654 | ||
| 
						 | 
					994d17fc7a | 
							
								
								
									
										4
									
								
								.github/workflows/test-only.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/test-only.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,6 +15,10 @@ jobs:
 | 
			
		||||
          ruff check . --select E9,F63,F7,F82
 | 
			
		||||
          # Complete check with errors treated as warnings
 | 
			
		||||
          ruff check . --exit-zero
 | 
			
		||||
      - name: Validate OpenAPI spec
 | 
			
		||||
        run: |
 | 
			
		||||
          pip install openapi-spec-validator
 | 
			
		||||
          python3 -c "from openapi_spec_validator import validate_spec; import yaml; validate_spec(yaml.safe_load(open('docs/api-spec.yaml')))"
 | 
			
		||||
 | 
			
		||||
  test-application-3-10:
 | 
			
		||||
    needs: lint-code
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ from changedetectionio.strtobool import strtobool
 | 
			
		||||
from flask_restful import abort, Resource
 | 
			
		||||
from flask import request
 | 
			
		||||
import validators
 | 
			
		||||
from . import auth
 | 
			
		||||
from . import auth, validate_openapi_request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Import(Resource):
 | 
			
		||||
@@ -12,6 +12,7 @@ class Import(Resource):
 | 
			
		||||
        self.datastore = kwargs['datastore']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('importWatches')
 | 
			
		||||
    def post(self):
 | 
			
		||||
        """Import a list of watched URLs."""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
from flask_expects_json import expects_json
 | 
			
		||||
from flask_restful import Resource
 | 
			
		||||
from . import auth
 | 
			
		||||
from flask_restful import abort, Resource
 | 
			
		||||
from flask_restful import Resource, abort
 | 
			
		||||
from flask import request
 | 
			
		||||
from . import auth
 | 
			
		||||
from . import auth, validate_openapi_request
 | 
			
		||||
from . import schema_create_notification_urls, schema_delete_notification_urls
 | 
			
		||||
 | 
			
		||||
class Notifications(Resource):
 | 
			
		||||
@@ -12,6 +10,7 @@ class Notifications(Resource):
 | 
			
		||||
        self.datastore = kwargs['datastore']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getNotifications')
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """Return Notification URL List."""
 | 
			
		||||
 | 
			
		||||
@@ -22,6 +21,7 @@ class Notifications(Resource):
 | 
			
		||||
               }, 200
 | 
			
		||||
    
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('addNotifications')
 | 
			
		||||
    @expects_json(schema_create_notification_urls)
 | 
			
		||||
    def post(self):
 | 
			
		||||
        """Create Notification URLs."""
 | 
			
		||||
@@ -49,6 +49,7 @@ class Notifications(Resource):
 | 
			
		||||
        return {'notification_urls': added_urls}, 201
 | 
			
		||||
    
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('replaceNotifications')
 | 
			
		||||
    @expects_json(schema_create_notification_urls)
 | 
			
		||||
    def put(self):
 | 
			
		||||
        """Replace Notification URLs."""
 | 
			
		||||
@@ -71,6 +72,7 @@ class Notifications(Resource):
 | 
			
		||||
        return {'notification_urls': clean_urls}, 200
 | 
			
		||||
        
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('deleteNotifications')
 | 
			
		||||
    @expects_json(schema_delete_notification_urls)
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """Delete Notification URLs."""
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
from flask_restful import Resource, abort
 | 
			
		||||
from flask import request
 | 
			
		||||
from . import auth
 | 
			
		||||
from . import auth, validate_openapi_request
 | 
			
		||||
 | 
			
		||||
class Search(Resource):
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
@@ -8,6 +8,7 @@ class Search(Resource):
 | 
			
		||||
        self.datastore = kwargs['datastore']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('searchWatches')
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """Search for watches by URL or title text."""
 | 
			
		||||
        query = request.args.get('q', '').strip()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
from flask_restful import Resource
 | 
			
		||||
from . import auth
 | 
			
		||||
from . import auth, validate_openapi_request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SystemInfo(Resource):
 | 
			
		||||
@@ -9,6 +9,7 @@ class SystemInfo(Resource):
 | 
			
		||||
        self.update_q = kwargs['update_q']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getSystemInfo')
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """Return system info."""
 | 
			
		||||
        import time
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ from flask import request
 | 
			
		||||
from . import auth
 | 
			
		||||
 | 
			
		||||
# Import schemas from __init__.py
 | 
			
		||||
from . import schema_tag, schema_create_tag, schema_update_tag
 | 
			
		||||
from . import schema_tag, schema_create_tag, schema_update_tag, validate_openapi_request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tag(Resource):
 | 
			
		||||
@@ -19,6 +19,7 @@ class Tag(Resource):
 | 
			
		||||
    # Get information about a single tag
 | 
			
		||||
    # curl http://localhost:5000/api/v1/tag/<string:uuid>
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getTag')
 | 
			
		||||
    def get(self, uuid):
 | 
			
		||||
        """Get data for a single tag/group, toggle notification muting, or recheck all."""
 | 
			
		||||
        from copy import deepcopy
 | 
			
		||||
@@ -50,6 +51,7 @@ class Tag(Resource):
 | 
			
		||||
        return tag
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('deleteTag')
 | 
			
		||||
    def delete(self, uuid):
 | 
			
		||||
        """Delete a tag/group and remove it from all watches."""
 | 
			
		||||
        if not self.datastore.data['settings']['application']['tags'].get(uuid):
 | 
			
		||||
@@ -66,6 +68,7 @@ class Tag(Resource):
 | 
			
		||||
        return 'OK', 204
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('updateTag')
 | 
			
		||||
    @expects_json(schema_update_tag)
 | 
			
		||||
    def put(self, uuid):
 | 
			
		||||
        """Update tag information."""
 | 
			
		||||
@@ -80,6 +83,7 @@ class Tag(Resource):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('createTag')
 | 
			
		||||
    # Only cares for {'title': 'xxxx'}
 | 
			
		||||
    def post(self):
 | 
			
		||||
        """Create a single tag/group."""
 | 
			
		||||
@@ -100,6 +104,7 @@ class Tags(Resource):
 | 
			
		||||
        self.datastore = kwargs['datastore']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('listTags')
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """List tags/groups."""
 | 
			
		||||
        result = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ from . import auth
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
# Import schemas from __init__.py
 | 
			
		||||
from . import schema, schema_create_watch, schema_update_watch
 | 
			
		||||
from . import schema, schema_create_watch, schema_update_watch, validate_openapi_request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Watch(Resource):
 | 
			
		||||
@@ -25,6 +25,7 @@ class Watch(Resource):
 | 
			
		||||
    # @todo - version2 - ?muted and ?paused should be able to be called together, return the watch struct not "OK"
 | 
			
		||||
    # ?recheck=true
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getWatch')
 | 
			
		||||
    def get(self, uuid):
 | 
			
		||||
        """Get information about a single watch, recheck, pause, or mute."""
 | 
			
		||||
        from copy import deepcopy
 | 
			
		||||
@@ -57,6 +58,7 @@ class Watch(Resource):
 | 
			
		||||
        return watch
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('deleteWatch')
 | 
			
		||||
    def delete(self, uuid):
 | 
			
		||||
        """Delete a watch and related history."""
 | 
			
		||||
        if not self.datastore.data['watching'].get(uuid):
 | 
			
		||||
@@ -66,6 +68,7 @@ class Watch(Resource):
 | 
			
		||||
        return 'OK', 204
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('updateWatch')
 | 
			
		||||
    @expects_json(schema_update_watch)
 | 
			
		||||
    def put(self, uuid):
 | 
			
		||||
        """Update watch information."""
 | 
			
		||||
@@ -91,6 +94,7 @@ class WatchHistory(Resource):
 | 
			
		||||
    # Get a list of available history for a watch by UUID
 | 
			
		||||
    # curl http://localhost:5000/api/v1/watch/<string:uuid>/history
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getWatchHistory')
 | 
			
		||||
    def get(self, uuid):
 | 
			
		||||
        """Get a list of all historical snapshots available for a watch."""
 | 
			
		||||
        watch = self.datastore.data['watching'].get(uuid)
 | 
			
		||||
@@ -105,6 +109,7 @@ class WatchSingleHistory(Resource):
 | 
			
		||||
        self.datastore = kwargs['datastore']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getWatchSnapshot')
 | 
			
		||||
    def get(self, uuid, timestamp):
 | 
			
		||||
        """Get single snapshot from watch."""
 | 
			
		||||
        watch = self.datastore.data['watching'].get(uuid)
 | 
			
		||||
@@ -138,6 +143,7 @@ class WatchFavicon(Resource):
 | 
			
		||||
        self.datastore = kwargs['datastore']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('getWatchFavicon')
 | 
			
		||||
    def get(self, uuid):
 | 
			
		||||
        """Get favicon for a watch."""
 | 
			
		||||
        watch = self.datastore.data['watching'].get(uuid)
 | 
			
		||||
@@ -172,6 +178,7 @@ class CreateWatch(Resource):
 | 
			
		||||
        self.update_q = kwargs['update_q']
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('createWatch')
 | 
			
		||||
    @expects_json(schema_create_watch)
 | 
			
		||||
    def post(self):
 | 
			
		||||
        """Create a single watch."""
 | 
			
		||||
@@ -207,6 +214,7 @@ class CreateWatch(Resource):
 | 
			
		||||
            return "Invalid or unsupported URL", 400
 | 
			
		||||
 | 
			
		||||
    @auth.check_token
 | 
			
		||||
    @validate_openapi_request('listWatches')
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """List watches."""
 | 
			
		||||
        list = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,9 @@
 | 
			
		||||
import copy
 | 
			
		||||
import yaml
 | 
			
		||||
import functools
 | 
			
		||||
from flask import request, abort
 | 
			
		||||
from openapi_core import OpenAPI
 | 
			
		||||
from openapi_core.contrib.flask import FlaskOpenAPIRequest
 | 
			
		||||
from . import api_schema
 | 
			
		||||
from ..model import watch_base
 | 
			
		||||
 | 
			
		||||
@@ -25,6 +30,38 @@ schema_create_notification_urls['required'] = ['notification_urls']
 | 
			
		||||
schema_delete_notification_urls = copy.deepcopy(schema_notification_urls)
 | 
			
		||||
schema_delete_notification_urls['required'] = ['notification_urls']
 | 
			
		||||
 | 
			
		||||
# Load OpenAPI spec for validation
 | 
			
		||||
_openapi_spec = None
 | 
			
		||||
 | 
			
		||||
def get_openapi_spec():
 | 
			
		||||
    global _openapi_spec
 | 
			
		||||
    if _openapi_spec is None:
 | 
			
		||||
        import os
 | 
			
		||||
        spec_path = os.path.join(os.path.dirname(__file__), '../../docs/api-spec.yaml')
 | 
			
		||||
        with open(spec_path, 'r') as f:
 | 
			
		||||
            spec_dict = yaml.safe_load(f)
 | 
			
		||||
        _openapi_spec = OpenAPI.from_dict(spec_dict)
 | 
			
		||||
    return _openapi_spec
 | 
			
		||||
 | 
			
		||||
def validate_openapi_request(operation_id):
 | 
			
		||||
    """Decorator to validate incoming requests against OpenAPI spec."""
 | 
			
		||||
    def decorator(f):
 | 
			
		||||
        @functools.wraps(f)
 | 
			
		||||
        def wrapper(*args, **kwargs):
 | 
			
		||||
            try:
 | 
			
		||||
                spec = get_openapi_spec()
 | 
			
		||||
                openapi_request = FlaskOpenAPIRequest(request)
 | 
			
		||||
                result = spec.unmarshal_request(openapi_request, operation_id)
 | 
			
		||||
                if result.errors:
 | 
			
		||||
                    abort(400, message=f"OpenAPI validation failed: {result.errors}")
 | 
			
		||||
                return f(*args, **kwargs)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                # If OpenAPI validation fails, log but don't break existing functionality
 | 
			
		||||
                print(f"OpenAPI validation warning for {operation_id}: {e}")
 | 
			
		||||
                return f(*args, **kwargs)
 | 
			
		||||
        return wrapper
 | 
			
		||||
    return decorator
 | 
			
		||||
 | 
			
		||||
# Import all API resources
 | 
			
		||||
from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch, WatchFavicon
 | 
			
		||||
from .Tags import Tags, Tag
 | 
			
		||||
 
 | 
			
		||||
@@ -328,6 +328,7 @@ components:
 | 
			
		||||
paths:
 | 
			
		||||
  /watch:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: listWatches
 | 
			
		||||
      tags: [Watch Management]
 | 
			
		||||
      summary: List all watches
 | 
			
		||||
      description: Return concise list of available web page change monitors (watches) and basic info
 | 
			
		||||
@@ -390,6 +391,7 @@ paths:
 | 
			
		||||
                  last_checked: 1640998800
 | 
			
		||||
                  last_changed: 1640995200
 | 
			
		||||
    post:
 | 
			
		||||
      operationId: createWatch
 | 
			
		||||
      tags: [Watch Management]
 | 
			
		||||
      summary: Create a new watch
 | 
			
		||||
      description: Create a single web page change monitor (watch). Requires at least 'url' to be set.
 | 
			
		||||
@@ -453,7 +455,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /watch/{uuid}:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getSingleWatch
 | 
			
		||||
      operationId: getWatch
 | 
			
		||||
      tags: [Watch Management]
 | 
			
		||||
      summary: Get single watch
 | 
			
		||||
      description: Retrieve web page change monitor (watch) information and set muted/paused status. Returns the FULL Watch JSON.
 | 
			
		||||
@@ -518,7 +520,7 @@ paths:
 | 
			
		||||
      operationId: updateWatch
 | 
			
		||||
      tags: [Watch Management]
 | 
			
		||||
      summary: Update watch
 | 
			
		||||
      description: Update an existing web page change monitor (watch) using JSON. Accepts the same structure as returned in [get single watch information](#operation/getSingleWatch).
 | 
			
		||||
      description: Update an existing web page change monitor (watch) using JSON. Accepts the same structure as returned in [get single watch information](#operation/getWatch).
 | 
			
		||||
      x-code-samples:
 | 
			
		||||
        - lang: 'curl'
 | 
			
		||||
          source: |
 | 
			
		||||
@@ -573,6 +575,7 @@ paths:
 | 
			
		||||
          description: Server error
 | 
			
		||||
 | 
			
		||||
    delete:
 | 
			
		||||
      operationId: deleteWatch
 | 
			
		||||
      tags: [Watch Management]  
 | 
			
		||||
      summary: Delete watch
 | 
			
		||||
      description: Delete a web page change monitor (watch) and all related history
 | 
			
		||||
@@ -608,6 +611,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /watch/{uuid}/history:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getWatchHistory
 | 
			
		||||
      tags: [Watch History]
 | 
			
		||||
      summary: Get watch history
 | 
			
		||||
      description: Get a list of all historical snapshots available for a web page change monitor (watch)
 | 
			
		||||
@@ -647,6 +651,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /watch/{uuid}/history/{timestamp}:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getWatchSnapshot
 | 
			
		||||
      tags: [Snapshots]
 | 
			
		||||
      summary: Get single snapshot
 | 
			
		||||
      description: Get single snapshot from web page change monitor (watch). Use 'latest' for the most recent snapshot.
 | 
			
		||||
@@ -699,6 +704,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /watch/{uuid}/favicon:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getWatchFavicon
 | 
			
		||||
      tags: [Favicon]
 | 
			
		||||
      summary: Get watch favicon
 | 
			
		||||
      description: Get the favicon for a web page change monitor (watch) as displayed in the watch overview list.
 | 
			
		||||
@@ -738,6 +744,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /tags:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: listTags
 | 
			
		||||
      tags: [Group / Tag Management]
 | 
			
		||||
      summary: List all tags
 | 
			
		||||
      description: Return list of available tags/groups
 | 
			
		||||
@@ -774,8 +781,59 @@ paths:
 | 
			
		||||
                  notification_urls: ["discord://webhook_id/webhook_token"]
 | 
			
		||||
                  notification_muted: false
 | 
			
		||||
 | 
			
		||||
  /tag:
 | 
			
		||||
    post:
 | 
			
		||||
      operationId: createTag
 | 
			
		||||
      tags: [Group / Tag Management]
 | 
			
		||||
      summary: Create tag
 | 
			
		||||
      description: Create a single tag/group
 | 
			
		||||
      x-code-samples:
 | 
			
		||||
        - lang: 'curl'
 | 
			
		||||
          source: |
 | 
			
		||||
            curl -X POST "http://localhost:5000/api/v1/tag" \
 | 
			
		||||
              -H "x-api-key: YOUR_API_KEY" \
 | 
			
		||||
              -H "Content-Type: application/json" \
 | 
			
		||||
              -d '{
 | 
			
		||||
                "title": "Important Sites"
 | 
			
		||||
              }'
 | 
			
		||||
        - lang: 'Python'
 | 
			
		||||
          source: |
 | 
			
		||||
            import requests
 | 
			
		||||
            
 | 
			
		||||
            headers = {
 | 
			
		||||
                'x-api-key': 'YOUR_API_KEY',
 | 
			
		||||
                'Content-Type': 'application/json'
 | 
			
		||||
            }
 | 
			
		||||
            data = {'title': 'Important Sites'}
 | 
			
		||||
            response = requests.post('http://localhost:5000/api/v1/tag', 
 | 
			
		||||
                                   headers=headers, json=data)
 | 
			
		||||
            print(response.json())
 | 
			
		||||
      requestBody:
 | 
			
		||||
        required: true
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/Tag'
 | 
			
		||||
            example:
 | 
			
		||||
              title: "Important Sites"
 | 
			
		||||
      responses:
 | 
			
		||||
        '201':
 | 
			
		||||
          description: Tag created successfully
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  uuid:
 | 
			
		||||
                    type: string
 | 
			
		||||
                    format: uuid
 | 
			
		||||
                    description: UUID of the created tag
 | 
			
		||||
        '400':
 | 
			
		||||
          description: Invalid or unsupported tag
 | 
			
		||||
 | 
			
		||||
  /tag/{uuid}:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getTag
 | 
			
		||||
      tags: [Group / Tag Management]
 | 
			
		||||
      summary: Get single tag
 | 
			
		||||
      description: Retrieve tag information, set notification_muted status, recheck all web page change monitors (watches) in tag.
 | 
			
		||||
@@ -827,6 +885,7 @@ paths:
 | 
			
		||||
          description: Tag not found
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      operationId: updateTag
 | 
			
		||||
      tags: [Group / Tag Management]
 | 
			
		||||
      summary: Update tag
 | 
			
		||||
      description: Update an existing tag using JSON
 | 
			
		||||
@@ -877,6 +936,7 @@ paths:
 | 
			
		||||
          description: Server error
 | 
			
		||||
 | 
			
		||||
    delete:
 | 
			
		||||
      operationId: deleteTag
 | 
			
		||||
      tags: [Group / Tag Management]
 | 
			
		||||
      summary: Delete tag
 | 
			
		||||
      description: Delete a tag/group and remove it from all web page change monitors (watches)
 | 
			
		||||
@@ -905,48 +965,10 @@ paths:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: Tag deleted successfully
 | 
			
		||||
 | 
			
		||||
    post:
 | 
			
		||||
      tags: [Group / Tag Management]
 | 
			
		||||
      summary: Create tag
 | 
			
		||||
      description: Create a single tag/group
 | 
			
		||||
      x-code-samples:
 | 
			
		||||
        - lang: 'curl'
 | 
			
		||||
          source: |
 | 
			
		||||
            curl -X POST "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \
 | 
			
		||||
              -H "x-api-key: YOUR_API_KEY" \
 | 
			
		||||
              -H "Content-Type: application/json" \
 | 
			
		||||
              -d '{
 | 
			
		||||
                "title": "Important Sites"
 | 
			
		||||
              }'
 | 
			
		||||
        - lang: 'Python'
 | 
			
		||||
          source: |
 | 
			
		||||
            import requests
 | 
			
		||||
            
 | 
			
		||||
            headers = {
 | 
			
		||||
                'x-api-key': 'YOUR_API_KEY',
 | 
			
		||||
                'Content-Type': 'application/json'
 | 
			
		||||
            }
 | 
			
		||||
            tag_uuid = '550e8400-e29b-41d4-a716-446655440000'
 | 
			
		||||
            data = {'title': 'Important Sites'}
 | 
			
		||||
            response = requests.post(f'http://localhost:5000/api/v1/tag/{tag_uuid}', 
 | 
			
		||||
                                   headers=headers, json=data)
 | 
			
		||||
            print(response.text)
 | 
			
		||||
      requestBody:
 | 
			
		||||
        required: true
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/Tag'
 | 
			
		||||
            example:
 | 
			
		||||
              title: "Important Sites"
 | 
			
		||||
      responses:
 | 
			
		||||
        '200':
 | 
			
		||||
          description: Tag created successfully
 | 
			
		||||
        '500':
 | 
			
		||||
          description: Server error
 | 
			
		||||
 | 
			
		||||
  /notifications:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getNotifications
 | 
			
		||||
      tags: [Notifications]
 | 
			
		||||
      summary: Get notification URLs
 | 
			
		||||
      description: Return the notification URL list from the configuration
 | 
			
		||||
@@ -971,6 +993,7 @@ paths:
 | 
			
		||||
                $ref: '#/components/schemas/NotificationUrls'
 | 
			
		||||
 | 
			
		||||
    post:
 | 
			
		||||
      operationId: addNotifications
 | 
			
		||||
      tags: [Notifications]
 | 
			
		||||
      summary: Add notification URLs
 | 
			
		||||
      description: Add one or more notification URLs to the configuration
 | 
			
		||||
@@ -1024,6 +1047,7 @@ paths:
 | 
			
		||||
          description: Invalid input
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      operationId: replaceNotifications
 | 
			
		||||
      tags: [Notifications]
 | 
			
		||||
      summary: Replace notification URLs
 | 
			
		||||
      description: Replace all notification URLs with the provided list (can be empty)
 | 
			
		||||
@@ -1071,6 +1095,7 @@ paths:
 | 
			
		||||
          description: Invalid input
 | 
			
		||||
 | 
			
		||||
    delete:
 | 
			
		||||
      operationId: deleteNotifications
 | 
			
		||||
      tags: [Notifications]
 | 
			
		||||
      summary: Delete notification URLs
 | 
			
		||||
      description: Delete one or more notification URLs from the configuration
 | 
			
		||||
@@ -1115,6 +1140,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /search:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: searchWatches
 | 
			
		||||
      tags: [Search]
 | 
			
		||||
      summary: Search watches
 | 
			
		||||
      description: Search web page change monitors (watches) by URL or title text
 | 
			
		||||
@@ -1169,6 +1195,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /import:
 | 
			
		||||
    post:
 | 
			
		||||
      operationId: importWatches
 | 
			
		||||
      tags: [Import]
 | 
			
		||||
      summary: Import watch URLs
 | 
			
		||||
      description: Import a list of URLs to monitor. Accepts line-separated URLs in request body.
 | 
			
		||||
@@ -1239,6 +1266,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
  /systeminfo:
 | 
			
		||||
    get:
 | 
			
		||||
      operationId: getSystemInfo
 | 
			
		||||
      tags: [System Information]
 | 
			
		||||
      summary: Get system information
 | 
			
		||||
      description: Return information about the current system state
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -89,6 +89,9 @@ pytest-flask ~=1.2
 | 
			
		||||
# Anything 4.0 and up but not 5.0
 | 
			
		||||
jsonschema ~= 4.0
 | 
			
		||||
 | 
			
		||||
# OpenAPI validation support
 | 
			
		||||
openapi-core[flask] >= 0.19.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
loguru
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user