2024-06-05 23:22:58 +03:00
|
|
|
|
import falcon
|
|
|
|
|
import json
|
2024-06-06 00:08:10 +03:00
|
|
|
|
import os
|
2024-06-29 00:08:56 +03:00
|
|
|
|
from typing import Optional
|
2024-06-06 00:08:10 +03:00
|
|
|
|
|
|
|
|
|
# https://falcon.readthedocs.io/en/stable/user/quickstart.html#learning-by-example
|
|
|
|
|
|
|
|
|
|
class AuthMiddleware:
|
|
|
|
|
def process_request(self, req, resp):
|
|
|
|
|
token = req.get_header('Authorization')
|
|
|
|
|
account_id = req.get_header('Account-ID')
|
|
|
|
|
|
|
|
|
|
challenges = ['Token type="Fernet"']
|
|
|
|
|
|
|
|
|
|
if token is None:
|
|
|
|
|
description = 'Please provide an auth token as part of the request.'
|
|
|
|
|
|
|
|
|
|
raise falcon.HTTPUnauthorized(
|
|
|
|
|
title='Auth token required',
|
|
|
|
|
description=description,
|
|
|
|
|
challenges=challenges,
|
|
|
|
|
href='http://docs.example.com/auth',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not self._token_is_valid(token, account_id):
|
|
|
|
|
description = (
|
|
|
|
|
'The provided auth token is not valid. '
|
|
|
|
|
'Please request a new token and try again.'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
raise falcon.HTTPUnauthorized(
|
|
|
|
|
title='Authentication required',
|
|
|
|
|
description=description,
|
|
|
|
|
challenges=challenges,
|
|
|
|
|
href='http://docs.example.com/auth',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _token_is_valid(self, token, account_id):
|
|
|
|
|
return os.environ["TOKEN"] # Suuuuuure it's valid...
|
2024-06-05 23:22:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RequireJSON:
|
|
|
|
|
def process_request(self, req, resp):
|
|
|
|
|
if not req.client_accepts_json:
|
|
|
|
|
raise falcon.HTTPNotAcceptable(
|
|
|
|
|
description='This API only supports responses encoded as JSON.',
|
|
|
|
|
href='http://docs.examples.com/api/json',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if req.method in ('POST', 'PUT'):
|
2024-10-11 23:08:51 +03:00
|
|
|
|
if req.content_type is None or 'application/json' not in req.content_type:
|
2024-06-05 23:22:58 +03:00
|
|
|
|
raise falcon.HTTPUnsupportedMediaType(
|
|
|
|
|
title='This API only supports requests encoded as JSON.',
|
|
|
|
|
href='http://docs.examples.com/api/json',
|
|
|
|
|
)
|
|
|
|
|
|
2024-06-06 00:08:10 +03:00
|
|
|
|
|
2024-06-05 23:22:58 +03:00
|
|
|
|
class JSONTranslator:
|
|
|
|
|
# NOTE: Normally you would simply use req.media and resp.media for
|
|
|
|
|
# this particular use case; this example serves only to illustrate
|
|
|
|
|
# what is possible.
|
|
|
|
|
|
|
|
|
|
def process_request(self, req, resp):
|
|
|
|
|
# req.stream corresponds to the WSGI wsgi.input environ variable,
|
|
|
|
|
# and allows you to read bytes from the request body.
|
|
|
|
|
#
|
|
|
|
|
# See also: PEP 3333
|
|
|
|
|
if req.content_length in (None, 0):
|
|
|
|
|
# Nothing to do
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
body = req.stream.read()
|
|
|
|
|
if not body:
|
|
|
|
|
raise falcon.HTTPBadRequest(
|
|
|
|
|
title='Empty request body',
|
|
|
|
|
description='A valid JSON document is required.',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
req.context.doc = json.loads(body.decode('utf-8'))
|
|
|
|
|
|
|
|
|
|
except (ValueError, UnicodeDecodeError):
|
|
|
|
|
description = (
|
|
|
|
|
'Could not decode the request body. The '
|
|
|
|
|
'JSON was incorrect or not encoded as '
|
|
|
|
|
'UTF-8.'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
raise falcon.HTTPBadRequest(title='Malformed JSON', description=description)
|
|
|
|
|
|
|
|
|
|
def process_response(self, req, resp, resource, req_succeeded):
|
|
|
|
|
if not hasattr(resp.context, 'result'):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
resp.text = json.dumps(resp.context.result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def max_body(limit):
|
|
|
|
|
def hook(req, resp, resource, params):
|
|
|
|
|
length = req.content_length
|
|
|
|
|
if length is not None and length > limit:
|
|
|
|
|
msg = (
|
|
|
|
|
'The size of the request is too large. The body must not '
|
|
|
|
|
'exceed ' + str(limit) + ' bytes in length.'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
raise falcon.HTTPPayloadTooLarge(
|
|
|
|
|
title='Request body is too large', description=msg
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return hook
|
|
|
|
|
|
2024-06-06 00:08:10 +03:00
|
|
|
|
########################################################################################################
|
|
|
|
|
|
2024-06-05 23:22:58 +03:00
|
|
|
|
class CreateTaskResource:
|
2024-06-06 00:08:10 +03:00
|
|
|
|
|
|
|
|
|
TODO_HEADER = '# ToDo'
|
|
|
|
|
|
2024-06-29 00:08:56 +03:00
|
|
|
|
def __init__(self, config_file: str, todos_path: str) -> None:
|
|
|
|
|
self.__todos_path = todos_path
|
|
|
|
|
self.__config = self.__read_config_file(config_file)
|
|
|
|
|
|
2024-06-29 00:27:18 +03:00
|
|
|
|
def on_post(self, req, resp):
|
|
|
|
|
params = req.params
|
2024-06-29 00:08:56 +03:00
|
|
|
|
list_id = 'default'
|
2024-06-29 00:30:13 +03:00
|
|
|
|
if 'list' in params:
|
|
|
|
|
list_id = params['list']
|
2024-06-29 00:08:56 +03:00
|
|
|
|
file_path = self.__detect_todo_file_path(list_id)
|
|
|
|
|
if file_path == None:
|
|
|
|
|
quote = {
|
|
|
|
|
'title': 'Ошибка настройки',
|
|
|
|
|
'description': 'В URL не указан параметр "list"'
|
|
|
|
|
}
|
|
|
|
|
resp.media = quote
|
|
|
|
|
resp.status = falcon.HTTP_500
|
|
|
|
|
else:
|
|
|
|
|
task = req.context.doc['task']
|
|
|
|
|
self.__create_task(task, file_path)
|
|
|
|
|
quote = {
|
|
|
|
|
'title': 'Получена задача',
|
|
|
|
|
'description': task
|
|
|
|
|
}
|
|
|
|
|
resp.media = quote
|
|
|
|
|
resp.status = falcon.HTTP_201
|
|
|
|
|
|
|
|
|
|
def __read_config_file(self, config_file: str) -> None:
|
|
|
|
|
with open(config_file, 'r') as f:
|
|
|
|
|
return json.load(f)
|
|
|
|
|
|
|
|
|
|
def __detect_todo_file_path(self, todo_list_id) -> Optional[str]:
|
|
|
|
|
if not 'lists' in self.__config or not todo_list_id in self.__config['lists']:
|
|
|
|
|
return None
|
|
|
|
|
return "{0}/{1}".format(self.__todos_path, self.__config['lists'][todo_list_id])
|
|
|
|
|
|
|
|
|
|
def __create_task(self, text: str, file_path: str) -> None:
|
2024-06-06 00:08:10 +03:00
|
|
|
|
content = []
|
|
|
|
|
section_found = False
|
|
|
|
|
is_waiting_empty_line = False
|
2024-06-29 00:08:56 +03:00
|
|
|
|
with open(file_path, 'r', encoding='UTF-8') as file:
|
2024-06-06 00:08:10 +03:00
|
|
|
|
while line := file.readline():
|
2024-10-11 23:08:51 +03:00
|
|
|
|
if not section_found:
|
2024-06-06 00:08:10 +03:00
|
|
|
|
if line.strip() == CreateTaskResource.TODO_HEADER:
|
|
|
|
|
section_found = True
|
|
|
|
|
is_waiting_empty_line = True
|
|
|
|
|
content.append(line)
|
|
|
|
|
if section_found and is_waiting_empty_line == True and line.strip() == '':
|
|
|
|
|
content.append("- [ ] {0}\n".format(text))
|
|
|
|
|
is_waiting_empty_line = False
|
2024-06-29 00:32:42 +03:00
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as file:
|
2024-06-06 00:08:10 +03:00
|
|
|
|
file.writelines(content)
|
|
|
|
|
|
|
|
|
|
########################################################################################################
|
2024-06-05 23:22:58 +03:00
|
|
|
|
|
|
|
|
|
app = falcon.App(
|
|
|
|
|
middleware=[
|
2024-06-06 00:08:10 +03:00
|
|
|
|
AuthMiddleware(),
|
2024-06-05 23:22:58 +03:00
|
|
|
|
RequireJSON(),
|
|
|
|
|
JSONTranslator()
|
|
|
|
|
]
|
|
|
|
|
)
|
2024-06-29 00:08:56 +03:00
|
|
|
|
app.add_route('/create-task', CreateTaskResource(config_file=os.environ['CONFIG_FILE'],
|
|
|
|
|
todos_path=os.environ['TODOS_PATH']))
|