Add HTTP error codes;
Correct count for filtering by tag.
This commit is contained in:
parent
a4c4bccc4b
commit
21e3aa34aa
2 changed files with 95 additions and 53 deletions
145
blasta.py
145
blasta.py
|
@ -11,6 +11,7 @@ TODO
|
|||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
from asyncio import Lock
|
||||
from datetime import datetime
|
||||
|
@ -397,6 +398,14 @@ class HttpInstance:
|
|||
# httponly=False, # True
|
||||
# samesite='lax')
|
||||
|
||||
@self.app.exception_handler(403)
|
||||
def not_found_exception_handler(request: Request, exc: HTTPException):
|
||||
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
|
||||
message = 'Blasta system message » Access denied.'
|
||||
description = 'Access denied (403)'
|
||||
path = 'error'
|
||||
return result_post(request, jabber_id, description, message, path)
|
||||
|
||||
@self.app.exception_handler(404)
|
||||
def not_found_exception_handler(request: Request, exc: HTTPException):
|
||||
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
|
||||
|
@ -421,6 +430,22 @@ class HttpInstance:
|
|||
path = 'error'
|
||||
return result_post(request, jabber_id, description, message, path)
|
||||
|
||||
# TODO
|
||||
@self.app.get('/admin')
|
||||
def admin_get(request: Request):
|
||||
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
|
||||
authorized = None
|
||||
if authorized:
|
||||
template_file = 'connect.xhtml'
|
||||
template_dict = {
|
||||
'request' : request,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
else:
|
||||
raise HTTPException(status_code=403, detail='Access denied')
|
||||
|
||||
@self.app.get('/connect')
|
||||
def connect_get(request: Request):
|
||||
jabber_id = Utilities.is_jid_matches_to_session(accounts, sessions, request)
|
||||
|
@ -432,7 +457,7 @@ class HttpInstance:
|
|||
'request' : request,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/contact')
|
||||
|
@ -450,7 +475,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/disconnect')
|
||||
|
@ -478,7 +503,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about')
|
||||
|
@ -490,7 +515,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/folksonomy')
|
||||
|
@ -502,7 +527,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/ideas')
|
||||
|
@ -518,7 +543,7 @@ class HttpInstance:
|
|||
'journal' : journal,
|
||||
'origin' : origin}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/philosophy')
|
||||
|
@ -530,7 +555,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/projects')
|
||||
|
@ -542,7 +567,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/software')
|
||||
|
@ -554,7 +579,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/thanks')
|
||||
|
@ -566,7 +591,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/xmpp')
|
||||
|
@ -578,7 +603,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/xmpp/atomsub')
|
||||
|
@ -590,7 +615,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/xmpp/libervia')
|
||||
|
@ -602,7 +627,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/xmpp/movim')
|
||||
|
@ -614,7 +639,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/about/xmpp/pubsub')
|
||||
|
@ -630,7 +655,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/feeds')
|
||||
|
@ -642,7 +667,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/questions')
|
||||
|
@ -654,7 +679,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/syndication')
|
||||
|
@ -671,7 +696,7 @@ class HttpInstance:
|
|||
'origin' : origin,
|
||||
'pubsub_jid' : jabber_id_pubsub}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/help/utilities')
|
||||
|
@ -689,7 +714,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/jid', response_class=HTMLResponse)
|
||||
|
@ -864,10 +889,11 @@ class HttpInstance:
|
|||
for tag, instances in SQLite.get_30_tags_by_jid(db_file, jid, index_first):
|
||||
tags_dict[tag] = instances
|
||||
if not entries_database:
|
||||
message = 'Blasta system message » Error: No entries were found.'
|
||||
description = 'No results'
|
||||
path = 'error'
|
||||
return result_post(request, jabber_id, description, message, path)
|
||||
#message = 'Blasta system message » Error: No entries were found.'
|
||||
#description = 'No results'
|
||||
#path = 'error'
|
||||
#return result_post(request, jabber_id, description, message, path)
|
||||
raise HTTPException(status_code=404, detail='No entries were found')
|
||||
if entries_count:
|
||||
entries = []
|
||||
for entry in entries_database:
|
||||
|
@ -928,7 +954,7 @@ class HttpInstance:
|
|||
item_ids_selection = []
|
||||
for item_id in item_ids_all[index_first:index_last]:
|
||||
item_ids_selection.append(item_id)
|
||||
|
||||
|
||||
iq = await XmppPubsub.get_node_items(xmpp_instance, jid, node_id_public, item_ids_selection)
|
||||
entries = Data.extract_iq_items_extra(iq, jid)
|
||||
if entries:
|
||||
|
@ -991,13 +1017,14 @@ class HttpInstance:
|
|||
'origin': origin,
|
||||
'path': path}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
else:
|
||||
description = 'An XMPP account is required'
|
||||
message = 'Blasta system message » Please connect with your XMPP account to view this directory.'
|
||||
path = 'error'
|
||||
return result_post(request, jabber_id, description, message, path)
|
||||
if not entries: raise HTTPException(status_code=404, detail='No entries were found')
|
||||
template_dict = {
|
||||
'request': request,
|
||||
'description': description,
|
||||
|
@ -1025,7 +1052,7 @@ class HttpInstance:
|
|||
else:
|
||||
template_file = 'browse.xhtml'
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/blasta.svg')
|
||||
|
@ -1118,10 +1145,11 @@ class HttpInstance:
|
|||
tags_of_entries = SQLite.get_30_tags_by_entries_recent(db_file, index_first)
|
||||
entries_count = SQLite.get_entries_count(db_file)
|
||||
if not entries_database:
|
||||
message = 'Blasta system message » Error: No entries were found.'
|
||||
description = 'No results'
|
||||
path = 'error'
|
||||
return result_post(request, jabber_id, description, message, path)
|
||||
#message = 'Blasta system message » Error: No entries were found.'
|
||||
#description = 'No results'
|
||||
#path = 'error'
|
||||
#return result_post(request, jabber_id, description, message, path)
|
||||
raise HTTPException(status_code=404, detail='No entries were found')
|
||||
tags_dict = {}
|
||||
#for tag, instances in SQLite.get_tags_30(db_file):
|
||||
for tag, instances in tags_of_entries:
|
||||
|
@ -1196,7 +1224,7 @@ class HttpInstance:
|
|||
else:
|
||||
template_file = 'browse.xhtml'
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
"""
|
||||
|
@ -1349,7 +1377,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/private', response_class=HTMLResponse)
|
||||
|
@ -1405,7 +1433,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
message = 'Blasta system message » Error: No active session.'
|
||||
description = 'You are not connected'
|
||||
|
@ -1442,7 +1470,7 @@ class HttpInstance:
|
|||
'request' : request,
|
||||
'routine' : routine}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
message = 'Blasta system message » Error: No active session.'
|
||||
description = 'You are not connected'
|
||||
|
@ -1606,7 +1634,7 @@ class HttpInstance:
|
|||
'title' : param_title,
|
||||
'url' : param_url}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
message = 'Blasta system message » Error: No active session.'
|
||||
description = 'You are not connected'
|
||||
|
@ -1651,7 +1679,7 @@ class HttpInstance:
|
|||
'url' : url,
|
||||
'url_hash' : url_hash}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
else:
|
||||
message = 'Blasta system message » Error: No active session.'
|
||||
|
@ -1693,7 +1721,7 @@ class HttpInstance:
|
|||
'path' : path,
|
||||
'request' : request}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/register')
|
||||
|
@ -1705,7 +1733,7 @@ class HttpInstance:
|
|||
'jabber_id' : jabber_id,
|
||||
'journal' : journal}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/rss')
|
||||
|
@ -1744,7 +1772,7 @@ class HttpInstance:
|
|||
'message' : message,
|
||||
'path' : path}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/search/jid/{jid}')
|
||||
|
@ -1780,7 +1808,7 @@ class HttpInstance:
|
|||
'message' : message,
|
||||
'path' : path}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
response = RedirectResponse(url='/search/all')
|
||||
return response
|
||||
|
@ -1812,7 +1840,7 @@ class HttpInstance:
|
|||
'message' : message,
|
||||
'path' : path}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/tag')
|
||||
|
@ -1831,7 +1859,7 @@ class HttpInstance:
|
|||
'message' : message,
|
||||
'tag_list' : tag_list}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/tag/{jid}')
|
||||
|
@ -1854,7 +1882,7 @@ class HttpInstance:
|
|||
'message' : message,
|
||||
'tag_list' : tag_list}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
|
||||
@self.app.get('/url')
|
||||
|
@ -2016,7 +2044,7 @@ class HttpInstance:
|
|||
'syndicate' : syndicate,
|
||||
'tags' : tags_list}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
message = 'Blasta system message » Error: MD5 message-digest algorithm.'
|
||||
description = 'The argument for URL does not appear to be a valid MD5 Checksum'
|
||||
|
@ -2176,7 +2204,7 @@ class HttpInstance:
|
|||
'syndicate': syndicate,
|
||||
'tags' : tags_new}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
return response
|
||||
else:
|
||||
message = 'Blasta system message » Error: No active session.'
|
||||
|
@ -2242,7 +2270,7 @@ class HttpInstance:
|
|||
'pubsub_jid' : jabber_id_pubsub,
|
||||
'syndicate' : syndicate}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
response = RedirectResponse(url='/jid/' + jabber_id)
|
||||
else:
|
||||
|
@ -2337,7 +2365,7 @@ class HttpInstance:
|
|||
'restore' : True,
|
||||
'syndicate' : syndicate}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
response = RedirectResponse(url='/jid/' + jabber_id)
|
||||
else:
|
||||
|
@ -2439,7 +2467,7 @@ class HttpInstance:
|
|||
'url' : entry['link'],
|
||||
'url_hash' : url_hash}
|
||||
response = templates.TemplateResponse(template_file, template_dict)
|
||||
response.headers["Content-Type"] = "application/xhtml+xml"
|
||||
response.headers['Content-Type'] = 'application/xhtml+xml'
|
||||
else:
|
||||
message = 'Blasta system message » Error: No active session.'
|
||||
description = 'You are not connected'
|
||||
|
@ -3985,7 +4013,7 @@ class SQLite:
|
|||
# .format(function_name, db_file, tag))
|
||||
sql = (
|
||||
"""
|
||||
SELECT COUNT(entries.id)
|
||||
SELECT COUNT(DISTINCT entries.id)
|
||||
FROM main_entries AS entries
|
||||
INNER JOIN combination_entries_tags_jids AS co ON entries.id = co.entry_id
|
||||
INNER JOIN main_tags AS tags ON tags.id = co.tag_id
|
||||
|
@ -5320,11 +5348,22 @@ def main():
|
|||
return http_instance.app
|
||||
|
||||
app = main()
|
||||
webbrowser.open('http://localhost:8000/help/about')
|
||||
# TODO Check first time
|
||||
webbrowser.open_new_tab('http://localhost:8000')
|
||||
|
||||
# FIXME
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(app, host='localhost', port=8000, reload=True)
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='blasta',
|
||||
description='Blasta - A collaborative annotation system.',
|
||||
usage='%(prog)s [OPTION]...')
|
||||
parser.add_argument('-v', '--version', help='print version',
|
||||
action='version', version='0.1')
|
||||
parser.add_argument('-p', '--port', help='port number', dest='port')
|
||||
parser.add_argument('-o', '--open', help='open an html browser', action='store_const', const=True, dest='open')
|
||||
args = parser.parse_args()
|
||||
port = args.port if args.port else 8000
|
||||
uvicorn.run(app, host='localhost', port=port, reload=True)
|
||||
if args.open:
|
||||
# TODO Check first time
|
||||
webbrowser.open('http://localhost:{}/help/about'.format(port))
|
||||
webbrowser.open_new_tab('http://localhost:{}'.format(port))
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import webview
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
Loading…
Reference in a new issue