mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-09 02:57:23 +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
|
ruff check . --select E9,F63,F7,F82
|
||||||
# Complete check with errors treated as warnings
|
# Complete check with errors treated as warnings
|
||||||
ruff check . --exit-zero
|
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:
|
test-application-3-10:
|
||||||
needs: lint-code
|
needs: lint-code
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from changedetectionio.strtobool import strtobool
|
|||||||
from flask_restful import abort, Resource
|
from flask_restful import abort, Resource
|
||||||
from flask import request
|
from flask import request
|
||||||
import validators
|
import validators
|
||||||
from . import auth
|
from . import auth, validate_openapi_request
|
||||||
|
|
||||||
|
|
||||||
class Import(Resource):
|
class Import(Resource):
|
||||||
@@ -12,6 +12,7 @@ class Import(Resource):
|
|||||||
self.datastore = kwargs['datastore']
|
self.datastore = kwargs['datastore']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('importWatches')
|
||||||
def post(self):
|
def post(self):
|
||||||
"""Import a list of watched URLs."""
|
"""Import a list of watched URLs."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from flask_expects_json import expects_json
|
from flask_expects_json import expects_json
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource, abort
|
||||||
from . import auth
|
|
||||||
from flask_restful import abort, Resource
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from . import auth
|
from . import auth, validate_openapi_request
|
||||||
from . import schema_create_notification_urls, schema_delete_notification_urls
|
from . import schema_create_notification_urls, schema_delete_notification_urls
|
||||||
|
|
||||||
class Notifications(Resource):
|
class Notifications(Resource):
|
||||||
@@ -12,6 +10,7 @@ class Notifications(Resource):
|
|||||||
self.datastore = kwargs['datastore']
|
self.datastore = kwargs['datastore']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getNotifications')
|
||||||
def get(self):
|
def get(self):
|
||||||
"""Return Notification URL List."""
|
"""Return Notification URL List."""
|
||||||
|
|
||||||
@@ -22,6 +21,7 @@ class Notifications(Resource):
|
|||||||
}, 200
|
}, 200
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('addNotifications')
|
||||||
@expects_json(schema_create_notification_urls)
|
@expects_json(schema_create_notification_urls)
|
||||||
def post(self):
|
def post(self):
|
||||||
"""Create Notification URLs."""
|
"""Create Notification URLs."""
|
||||||
@@ -49,6 +49,7 @@ class Notifications(Resource):
|
|||||||
return {'notification_urls': added_urls}, 201
|
return {'notification_urls': added_urls}, 201
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('replaceNotifications')
|
||||||
@expects_json(schema_create_notification_urls)
|
@expects_json(schema_create_notification_urls)
|
||||||
def put(self):
|
def put(self):
|
||||||
"""Replace Notification URLs."""
|
"""Replace Notification URLs."""
|
||||||
@@ -71,6 +72,7 @@ class Notifications(Resource):
|
|||||||
return {'notification_urls': clean_urls}, 200
|
return {'notification_urls': clean_urls}, 200
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('deleteNotifications')
|
||||||
@expects_json(schema_delete_notification_urls)
|
@expects_json(schema_delete_notification_urls)
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""Delete Notification URLs."""
|
"""Delete Notification URLs."""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from flask_restful import Resource, abort
|
from flask_restful import Resource, abort
|
||||||
from flask import request
|
from flask import request
|
||||||
from . import auth
|
from . import auth, validate_openapi_request
|
||||||
|
|
||||||
class Search(Resource):
|
class Search(Resource):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -8,6 +8,7 @@ class Search(Resource):
|
|||||||
self.datastore = kwargs['datastore']
|
self.datastore = kwargs['datastore']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('searchWatches')
|
||||||
def get(self):
|
def get(self):
|
||||||
"""Search for watches by URL or title text."""
|
"""Search for watches by URL or title text."""
|
||||||
query = request.args.get('q', '').strip()
|
query = request.args.get('q', '').strip()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from . import auth
|
from . import auth, validate_openapi_request
|
||||||
|
|
||||||
|
|
||||||
class SystemInfo(Resource):
|
class SystemInfo(Resource):
|
||||||
@@ -9,6 +9,7 @@ class SystemInfo(Resource):
|
|||||||
self.update_q = kwargs['update_q']
|
self.update_q = kwargs['update_q']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getSystemInfo')
|
||||||
def get(self):
|
def get(self):
|
||||||
"""Return system info."""
|
"""Return system info."""
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from flask import request
|
|||||||
from . import auth
|
from . import auth
|
||||||
|
|
||||||
# Import schemas from __init__.py
|
# 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):
|
class Tag(Resource):
|
||||||
@@ -19,6 +19,7 @@ class Tag(Resource):
|
|||||||
# Get information about a single tag
|
# Get information about a single tag
|
||||||
# curl http://localhost:5000/api/v1/tag/<string:uuid>
|
# curl http://localhost:5000/api/v1/tag/<string:uuid>
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getTag')
|
||||||
def get(self, uuid):
|
def get(self, uuid):
|
||||||
"""Get data for a single tag/group, toggle notification muting, or recheck all."""
|
"""Get data for a single tag/group, toggle notification muting, or recheck all."""
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@@ -50,6 +51,7 @@ class Tag(Resource):
|
|||||||
return tag
|
return tag
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('deleteTag')
|
||||||
def delete(self, uuid):
|
def delete(self, uuid):
|
||||||
"""Delete a tag/group and remove it from all watches."""
|
"""Delete a tag/group and remove it from all watches."""
|
||||||
if not self.datastore.data['settings']['application']['tags'].get(uuid):
|
if not self.datastore.data['settings']['application']['tags'].get(uuid):
|
||||||
@@ -66,6 +68,7 @@ class Tag(Resource):
|
|||||||
return 'OK', 204
|
return 'OK', 204
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('updateTag')
|
||||||
@expects_json(schema_update_tag)
|
@expects_json(schema_update_tag)
|
||||||
def put(self, uuid):
|
def put(self, uuid):
|
||||||
"""Update tag information."""
|
"""Update tag information."""
|
||||||
@@ -80,6 +83,7 @@ class Tag(Resource):
|
|||||||
|
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('createTag')
|
||||||
# Only cares for {'title': 'xxxx'}
|
# Only cares for {'title': 'xxxx'}
|
||||||
def post(self):
|
def post(self):
|
||||||
"""Create a single tag/group."""
|
"""Create a single tag/group."""
|
||||||
@@ -100,6 +104,7 @@ class Tags(Resource):
|
|||||||
self.datastore = kwargs['datastore']
|
self.datastore = kwargs['datastore']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('listTags')
|
||||||
def get(self):
|
def get(self):
|
||||||
"""List tags/groups."""
|
"""List tags/groups."""
|
||||||
result = {}
|
result = {}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from . import auth
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
# Import schemas from __init__.py
|
# 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):
|
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"
|
# @todo - version2 - ?muted and ?paused should be able to be called together, return the watch struct not "OK"
|
||||||
# ?recheck=true
|
# ?recheck=true
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getWatch')
|
||||||
def get(self, uuid):
|
def get(self, uuid):
|
||||||
"""Get information about a single watch, recheck, pause, or mute."""
|
"""Get information about a single watch, recheck, pause, or mute."""
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@@ -57,6 +58,7 @@ class Watch(Resource):
|
|||||||
return watch
|
return watch
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('deleteWatch')
|
||||||
def delete(self, uuid):
|
def delete(self, uuid):
|
||||||
"""Delete a watch and related history."""
|
"""Delete a watch and related history."""
|
||||||
if not self.datastore.data['watching'].get(uuid):
|
if not self.datastore.data['watching'].get(uuid):
|
||||||
@@ -66,6 +68,7 @@ class Watch(Resource):
|
|||||||
return 'OK', 204
|
return 'OK', 204
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('updateWatch')
|
||||||
@expects_json(schema_update_watch)
|
@expects_json(schema_update_watch)
|
||||||
def put(self, uuid):
|
def put(self, uuid):
|
||||||
"""Update watch information."""
|
"""Update watch information."""
|
||||||
@@ -91,6 +94,7 @@ class WatchHistory(Resource):
|
|||||||
# Get a list of available history for a watch by UUID
|
# Get a list of available history for a watch by UUID
|
||||||
# curl http://localhost:5000/api/v1/watch/<string:uuid>/history
|
# curl http://localhost:5000/api/v1/watch/<string:uuid>/history
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getWatchHistory')
|
||||||
def get(self, uuid):
|
def get(self, uuid):
|
||||||
"""Get a list of all historical snapshots available for a watch."""
|
"""Get a list of all historical snapshots available for a watch."""
|
||||||
watch = self.datastore.data['watching'].get(uuid)
|
watch = self.datastore.data['watching'].get(uuid)
|
||||||
@@ -105,6 +109,7 @@ class WatchSingleHistory(Resource):
|
|||||||
self.datastore = kwargs['datastore']
|
self.datastore = kwargs['datastore']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getWatchSnapshot')
|
||||||
def get(self, uuid, timestamp):
|
def get(self, uuid, timestamp):
|
||||||
"""Get single snapshot from watch."""
|
"""Get single snapshot from watch."""
|
||||||
watch = self.datastore.data['watching'].get(uuid)
|
watch = self.datastore.data['watching'].get(uuid)
|
||||||
@@ -138,6 +143,7 @@ class WatchFavicon(Resource):
|
|||||||
self.datastore = kwargs['datastore']
|
self.datastore = kwargs['datastore']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('getWatchFavicon')
|
||||||
def get(self, uuid):
|
def get(self, uuid):
|
||||||
"""Get favicon for a watch."""
|
"""Get favicon for a watch."""
|
||||||
watch = self.datastore.data['watching'].get(uuid)
|
watch = self.datastore.data['watching'].get(uuid)
|
||||||
@@ -172,6 +178,7 @@ class CreateWatch(Resource):
|
|||||||
self.update_q = kwargs['update_q']
|
self.update_q = kwargs['update_q']
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('createWatch')
|
||||||
@expects_json(schema_create_watch)
|
@expects_json(schema_create_watch)
|
||||||
def post(self):
|
def post(self):
|
||||||
"""Create a single watch."""
|
"""Create a single watch."""
|
||||||
@@ -207,6 +214,7 @@ class CreateWatch(Resource):
|
|||||||
return "Invalid or unsupported URL", 400
|
return "Invalid or unsupported URL", 400
|
||||||
|
|
||||||
@auth.check_token
|
@auth.check_token
|
||||||
|
@validate_openapi_request('listWatches')
|
||||||
def get(self):
|
def get(self):
|
||||||
"""List watches."""
|
"""List watches."""
|
||||||
list = {}
|
list = {}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import copy
|
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 . import api_schema
|
||||||
from ..model import watch_base
|
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 = copy.deepcopy(schema_notification_urls)
|
||||||
schema_delete_notification_urls['required'] = ['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
|
# Import all API resources
|
||||||
from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch, WatchFavicon
|
from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch, WatchFavicon
|
||||||
from .Tags import Tags, Tag
|
from .Tags import Tags, Tag
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ components:
|
|||||||
paths:
|
paths:
|
||||||
/watch:
|
/watch:
|
||||||
get:
|
get:
|
||||||
|
operationId: listWatches
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: List all watches
|
summary: List all watches
|
||||||
description: Return concise list of available web page change monitors (watches) and basic info
|
description: Return concise list of available web page change monitors (watches) and basic info
|
||||||
@@ -390,6 +391,7 @@ paths:
|
|||||||
last_checked: 1640998800
|
last_checked: 1640998800
|
||||||
last_changed: 1640995200
|
last_changed: 1640995200
|
||||||
post:
|
post:
|
||||||
|
operationId: createWatch
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: Create a new watch
|
summary: Create a new watch
|
||||||
description: Create a single web page change monitor (watch). Requires at least 'url' to be set.
|
description: Create a single web page change monitor (watch). Requires at least 'url' to be set.
|
||||||
@@ -453,7 +455,7 @@ paths:
|
|||||||
|
|
||||||
/watch/{uuid}:
|
/watch/{uuid}:
|
||||||
get:
|
get:
|
||||||
operationId: getSingleWatch
|
operationId: getWatch
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: Get single watch
|
summary: Get single watch
|
||||||
description: Retrieve web page change monitor (watch) information and set muted/paused status. Returns the FULL Watch JSON.
|
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
|
operationId: updateWatch
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: Update watch
|
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:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -573,6 +575,7 @@ paths:
|
|||||||
description: Server error
|
description: Server error
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
|
operationId: deleteWatch
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: Delete watch
|
summary: Delete watch
|
||||||
description: Delete a web page change monitor (watch) and all related history
|
description: Delete a web page change monitor (watch) and all related history
|
||||||
@@ -608,6 +611,7 @@ paths:
|
|||||||
|
|
||||||
/watch/{uuid}/history:
|
/watch/{uuid}/history:
|
||||||
get:
|
get:
|
||||||
|
operationId: getWatchHistory
|
||||||
tags: [Watch History]
|
tags: [Watch History]
|
||||||
summary: Get watch history
|
summary: Get watch history
|
||||||
description: Get a list of all historical snapshots available for a web page change monitor (watch)
|
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}:
|
/watch/{uuid}/history/{timestamp}:
|
||||||
get:
|
get:
|
||||||
|
operationId: getWatchSnapshot
|
||||||
tags: [Snapshots]
|
tags: [Snapshots]
|
||||||
summary: Get single snapshot
|
summary: Get single snapshot
|
||||||
description: Get single snapshot from web page change monitor (watch). Use 'latest' for the most recent 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:
|
/watch/{uuid}/favicon:
|
||||||
get:
|
get:
|
||||||
|
operationId: getWatchFavicon
|
||||||
tags: [Favicon]
|
tags: [Favicon]
|
||||||
summary: Get watch favicon
|
summary: Get watch favicon
|
||||||
description: Get the favicon for a web page change monitor (watch) as displayed in the watch overview list.
|
description: Get the favicon for a web page change monitor (watch) as displayed in the watch overview list.
|
||||||
@@ -738,6 +744,7 @@ paths:
|
|||||||
|
|
||||||
/tags:
|
/tags:
|
||||||
get:
|
get:
|
||||||
|
operationId: listTags
|
||||||
tags: [Group / Tag Management]
|
tags: [Group / Tag Management]
|
||||||
summary: List all tags
|
summary: List all tags
|
||||||
description: Return list of available tags/groups
|
description: Return list of available tags/groups
|
||||||
@@ -774,8 +781,59 @@ paths:
|
|||||||
notification_urls: ["discord://webhook_id/webhook_token"]
|
notification_urls: ["discord://webhook_id/webhook_token"]
|
||||||
notification_muted: false
|
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}:
|
/tag/{uuid}:
|
||||||
get:
|
get:
|
||||||
|
operationId: getTag
|
||||||
tags: [Group / Tag Management]
|
tags: [Group / Tag Management]
|
||||||
summary: Get single tag
|
summary: Get single tag
|
||||||
description: Retrieve tag information, set notification_muted status, recheck all web page change monitors (watches) in 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
|
description: Tag not found
|
||||||
|
|
||||||
put:
|
put:
|
||||||
|
operationId: updateTag
|
||||||
tags: [Group / Tag Management]
|
tags: [Group / Tag Management]
|
||||||
summary: Update tag
|
summary: Update tag
|
||||||
description: Update an existing tag using JSON
|
description: Update an existing tag using JSON
|
||||||
@@ -877,6 +936,7 @@ paths:
|
|||||||
description: Server error
|
description: Server error
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
|
operationId: deleteTag
|
||||||
tags: [Group / Tag Management]
|
tags: [Group / Tag Management]
|
||||||
summary: Delete tag
|
summary: Delete tag
|
||||||
description: Delete a tag/group and remove it from all web page change monitors (watches)
|
description: Delete a tag/group and remove it from all web page change monitors (watches)
|
||||||
@@ -905,48 +965,10 @@ paths:
|
|||||||
'200':
|
'200':
|
||||||
description: Tag deleted successfully
|
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:
|
/notifications:
|
||||||
get:
|
get:
|
||||||
|
operationId: getNotifications
|
||||||
tags: [Notifications]
|
tags: [Notifications]
|
||||||
summary: Get notification URLs
|
summary: Get notification URLs
|
||||||
description: Return the notification URL list from the configuration
|
description: Return the notification URL list from the configuration
|
||||||
@@ -971,6 +993,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/NotificationUrls'
|
$ref: '#/components/schemas/NotificationUrls'
|
||||||
|
|
||||||
post:
|
post:
|
||||||
|
operationId: addNotifications
|
||||||
tags: [Notifications]
|
tags: [Notifications]
|
||||||
summary: Add notification URLs
|
summary: Add notification URLs
|
||||||
description: Add one or more notification URLs to the configuration
|
description: Add one or more notification URLs to the configuration
|
||||||
@@ -1024,6 +1047,7 @@ paths:
|
|||||||
description: Invalid input
|
description: Invalid input
|
||||||
|
|
||||||
put:
|
put:
|
||||||
|
operationId: replaceNotifications
|
||||||
tags: [Notifications]
|
tags: [Notifications]
|
||||||
summary: Replace notification URLs
|
summary: Replace notification URLs
|
||||||
description: Replace all notification URLs with the provided list (can be empty)
|
description: Replace all notification URLs with the provided list (can be empty)
|
||||||
@@ -1071,6 +1095,7 @@ paths:
|
|||||||
description: Invalid input
|
description: Invalid input
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
|
operationId: deleteNotifications
|
||||||
tags: [Notifications]
|
tags: [Notifications]
|
||||||
summary: Delete notification URLs
|
summary: Delete notification URLs
|
||||||
description: Delete one or more notification URLs from the configuration
|
description: Delete one or more notification URLs from the configuration
|
||||||
@@ -1115,6 +1140,7 @@ paths:
|
|||||||
|
|
||||||
/search:
|
/search:
|
||||||
get:
|
get:
|
||||||
|
operationId: searchWatches
|
||||||
tags: [Search]
|
tags: [Search]
|
||||||
summary: Search watches
|
summary: Search watches
|
||||||
description: Search web page change monitors (watches) by URL or title text
|
description: Search web page change monitors (watches) by URL or title text
|
||||||
@@ -1169,6 +1195,7 @@ paths:
|
|||||||
|
|
||||||
/import:
|
/import:
|
||||||
post:
|
post:
|
||||||
|
operationId: importWatches
|
||||||
tags: [Import]
|
tags: [Import]
|
||||||
summary: Import watch URLs
|
summary: Import watch URLs
|
||||||
description: Import a list of URLs to monitor. Accepts line-separated URLs in request body.
|
description: Import a list of URLs to monitor. Accepts line-separated URLs in request body.
|
||||||
@@ -1239,6 +1266,7 @@ paths:
|
|||||||
|
|
||||||
/systeminfo:
|
/systeminfo:
|
||||||
get:
|
get:
|
||||||
|
operationId: getSystemInfo
|
||||||
tags: [System Information]
|
tags: [System Information]
|
||||||
summary: Get system information
|
summary: Get system information
|
||||||
description: Return information about the current system state
|
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
|
# Anything 4.0 and up but not 5.0
|
||||||
jsonschema ~= 4.0
|
jsonschema ~= 4.0
|
||||||
|
|
||||||
|
# OpenAPI validation support
|
||||||
|
openapi-core[flask] >= 0.19.0
|
||||||
|
|
||||||
|
|
||||||
loguru
|
loguru
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user