Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
f8f18c40e7 | |||
231d11a5fe | |||
8d65d3787c | |||
a50d5b177f | |||
316cf7e581 | |||
fa282b96e0 | |||
cf66a0d0d0 | |||
710277abae | |||
05ef983dc2 | |||
701cbf94e1 | |||
24d3f50e29 | |||
83d6388e35 | |||
f5680564d2 | |||
8eef627c2e | |||
96023662d3 | |||
d89715b8fd | |||
f715fb11dc | |||
ab6ab52f24 | |||
be03d196b7 | |||
8f7af381e6 | |||
f04db259c6 | |||
a716df4beb | |||
bffa816ef1 | |||
4fd9c7330e | |||
f311a5788e | |||
7d91b69e81 | |||
ee163fe9a1 | |||
cb2de342c2 | |||
de454fe5c1 | |||
57265c4e7c | |||
abb1434b8d | |||
72b13b7e98 | |||
9d9e213607 | |||
e8341cb899 | |||
7f87191f2a | |||
3d3d3df19f | |||
27ab87a7a2 | |||
92d495dfd5 | |||
b2e2cda3ea | |||
30a9e6400a | |||
b4e04c5e46 | |||
d37db9fb86 | |||
d289a41e4d | |||
1165606a37 | |||
d1389abaf2 |
@ -4,6 +4,7 @@ After=apache2.service
|
||||
Wants=apache2.service
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/usr/bin/install -m 777 -d /run/apache_restart
|
||||
ExecStart=/usr/bin/env python3 /path/apache_restarter.py /run/apache_restart/apache_restart
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
0
app/api/__init__.py
Normal file
0
app/api/__init__.py
Normal file
29
app/api/apache_restart.py
Normal file
29
app/api/apache_restart.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os
|
||||
|
||||
from flask_restful import Resource, abort
|
||||
|
||||
from app.glob import get_config
|
||||
|
||||
apache_restart_flagfile = 'restart_apache'
|
||||
|
||||
|
||||
class ApacheRestartFlag(Resource):
|
||||
def get(self):
|
||||
cfg = get_config()
|
||||
flagfile = cfg.get('apache_restart_flagfile', apache_restart_flagfile)
|
||||
if not os.path.isfile(flagfile):
|
||||
abort(404, message='not found')
|
||||
return {
|
||||
'message': 'found'
|
||||
}
|
||||
|
||||
def put(self):
|
||||
cfg = get_config()
|
||||
flagfile = cfg.get('apache_restart_flagfile', apache_restart_flagfile)
|
||||
if os.path.isfile(flagfile):
|
||||
return '', 304
|
||||
with open(flagfile, 'a'):
|
||||
pass
|
||||
return {
|
||||
'message': 'success'
|
||||
}
|
14
app/api/config.py
Normal file
14
app/api/config.py
Normal file
@ -0,0 +1,14 @@
|
||||
from flask_restful import Resource
|
||||
|
||||
from app.glob import get_config, get_manager
|
||||
|
||||
|
||||
class Config(Resource):
|
||||
def get(self):
|
||||
return get_config()
|
||||
|
||||
|
||||
class ConfigTest(Resource):
|
||||
def get(self):
|
||||
manager = get_manager()
|
||||
return manager.check()
|
27
app/api/index.py
Normal file
27
app/api/index.py
Normal file
@ -0,0 +1,27 @@
|
||||
from flask_restful import Resource, Api
|
||||
|
||||
from app.api.apache_restart import ApacheRestartFlag
|
||||
from app.api.config import Config, ConfigTest
|
||||
from app.api.infobases import InfobasesAvailable, InfobasesAll
|
||||
from app.api.module import EnterpriseModule
|
||||
from app.api.publications import Publications, Publication, PublicationURL
|
||||
|
||||
|
||||
class APIIndex(Resource):
|
||||
def get(self):
|
||||
return ['infobases-available', 'infobases-all', 'publications',
|
||||
'module', 'config', 'config-test', 'apache-restart']
|
||||
|
||||
|
||||
def add_api_resources(api: Api) -> None:
|
||||
api.add_resource(InfobasesAvailable, '/infobases-available')
|
||||
api.add_resource(InfobasesAll, '/infobases-all')
|
||||
api.add_resource(Publications, '/publications', '/publications/')
|
||||
api.add_resource(Publication, '/publications/<string:name>')
|
||||
api.add_resource(PublicationURL, '/publications/<string:name>/url')
|
||||
api.add_resource(EnterpriseModule, '/module')
|
||||
api.add_resource(Config, '/config')
|
||||
api.add_resource(ConfigTest, '/config-test')
|
||||
api.add_resource(ApacheRestartFlag, '/apache-restart')
|
||||
api.add_resource(APIIndex, '/')
|
||||
|
39
app/api/infobases.py
Normal file
39
app/api/infobases.py
Normal file
@ -0,0 +1,39 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from flask_restful import Resource
|
||||
|
||||
from app.glob import get_config, get_manager
|
||||
from app.manager import infobase_data_blank
|
||||
from app.brackets import get_infobases as br_get_infobases
|
||||
|
||||
|
||||
def load_infobases(config) -> List[str]:
|
||||
result = []
|
||||
if 'infobases' in config and 'server_file' in config['infobases']:
|
||||
result = br_get_infobases(config['infobases']['server_file'])
|
||||
return result
|
||||
|
||||
|
||||
class InfobasesAvailable(Resource):
|
||||
def get(self) -> List[str]:
|
||||
cfg = get_config()
|
||||
bases = load_infobases(cfg)
|
||||
return bases
|
||||
|
||||
|
||||
class InfobasesAll(Resource):
|
||||
def get(self) -> List[Dict[str, str]]:
|
||||
cfg = get_config()
|
||||
manager = get_manager()
|
||||
infobase_names = load_infobases(cfg)
|
||||
pubs = manager.publications()
|
||||
result: List[Dict[str, str]] = []
|
||||
for name in infobase_names:
|
||||
publicated = False
|
||||
for pub in filter(lambda x: x["name"] == name, pubs):
|
||||
publicated = True
|
||||
result.append(pub)
|
||||
break
|
||||
if not publicated:
|
||||
result.append(infobase_data_blank(name))
|
||||
return result
|
22
app/api/module.py
Normal file
22
app/api/module.py
Normal file
@ -0,0 +1,22 @@
|
||||
from flask_restful import Resource, abort
|
||||
|
||||
from app.glob import get_manager
|
||||
|
||||
|
||||
class EnterpriseModule(Resource):
|
||||
def get(self):
|
||||
manager = get_manager()
|
||||
if not manager.has_module():
|
||||
abort(404, message='not found')
|
||||
return {
|
||||
'message': 'found'
|
||||
}
|
||||
|
||||
def put(self):
|
||||
manager = get_manager()
|
||||
if manager.has_module():
|
||||
return '', 304
|
||||
manager.add_module()
|
||||
return {
|
||||
'message': 'success'
|
||||
}
|
98
app/api/publications.py
Normal file
98
app/api/publications.py
Normal file
@ -0,0 +1,98 @@
|
||||
import traceback
|
||||
from typing import List, Dict
|
||||
|
||||
from flask_restful import Resource, abort, reqparse
|
||||
|
||||
from app.glob import get_manager
|
||||
from app.utils import validate_url
|
||||
|
||||
|
||||
pub_parser = reqparse.RequestParser()
|
||||
pub_parser.add_argument('url', type=str)
|
||||
pub_parser.add_argument('file', type=str, default='')
|
||||
pub_parser.add_argument('force', type=bool, default=False)
|
||||
|
||||
pub_parser_with_name = pub_parser.copy()
|
||||
pub_parser_with_name.add_argument('name', required=True, type=str, help='name required')
|
||||
|
||||
url_req_parser = reqparse.RequestParser()
|
||||
url_req_parser.add_argument('url', type=str, required=True)
|
||||
|
||||
remove_parser = reqparse.RequestParser()
|
||||
remove_parser.add_argument('force', type=bool, default=False)
|
||||
|
||||
|
||||
class Publications(Resource):
|
||||
def get(self) -> List[Dict[str, str]]:
|
||||
manager = get_manager()
|
||||
return manager.publications()
|
||||
|
||||
def put(self):
|
||||
args = pub_parser_with_name.parse_args()
|
||||
validate_url(args.url)
|
||||
manager = get_manager()
|
||||
url = args.url
|
||||
if not args.force and args.name in manager.list():
|
||||
abort(409, message=f'publication exists: {args.name}')
|
||||
try:
|
||||
url = manager.add(args.name, url, args.file, args.force)
|
||||
except Exception as e:
|
||||
abort(422, message=f'publication failed: {args.name}', traceback=traceback.format_exc())
|
||||
return {'message': 'created', 'name': args.name, 'url': url}, 201
|
||||
|
||||
|
||||
class Publication(Resource):
|
||||
def get(self, name: str):
|
||||
manager = get_manager()
|
||||
if name not in manager.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
return {
|
||||
name: manager.get(name)
|
||||
}
|
||||
|
||||
def put(self, name: str):
|
||||
args = pub_parser.parse_args()
|
||||
validate_url(args.url)
|
||||
manager = get_manager()
|
||||
url = args.url
|
||||
if not args.force and name in manager.list():
|
||||
abort(409, message=f'publication exists: {name}')
|
||||
try:
|
||||
url = manager.add(name, url, args.file, args.force)
|
||||
except Exception as e:
|
||||
abort(422, message=f'publication failed: {name}', traceback=traceback.format_exc())
|
||||
return {'message': 'created', 'name': name, 'url': url}, 201
|
||||
|
||||
def delete(self, name: str):
|
||||
args = remove_parser.parse_args()
|
||||
manager = get_manager()
|
||||
if name not in manager.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
try:
|
||||
manager.remove(name, args.force)
|
||||
except Exception as e:
|
||||
abort(422, message='delete failed', traceback=traceback.format_exc())
|
||||
return {'message': 'deleted', 'name': name}
|
||||
|
||||
|
||||
class PublicationURL(Resource):
|
||||
def get(self, name: str):
|
||||
manager = get_manager()
|
||||
if name not in manager.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
return {
|
||||
'url': manager.get(name)['url']
|
||||
}
|
||||
|
||||
def post(self, name: str):
|
||||
args = url_req_parser.parse_args()
|
||||
validate_url(args.url)
|
||||
manager = get_manager()
|
||||
url = args.url
|
||||
if name not in manager.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
try:
|
||||
manager.set_url(name, url)
|
||||
except Exception as e:
|
||||
abort(422, message=f'set url failed', traceback=traceback.format_exc())
|
||||
return {'message': 'success', 'name': name, 'url': url}
|
29
app/app.py
Normal file
29
app/app.py
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
from flask import Flask, render_template
|
||||
from flask_restful import Api
|
||||
from flask_cors import CORS
|
||||
|
||||
from app.api.index import add_api_resources
|
||||
from app.glob import get_config, get_manager
|
||||
|
||||
frontend_dir = '../frontend/dist'
|
||||
|
||||
|
||||
def create_app() -> Flask:
|
||||
app = Flask(__name__, static_url_path='/', static_folder=frontend_dir, template_folder=frontend_dir)
|
||||
api = Api(app, '/api/v1/')
|
||||
add_api_resources(api)
|
||||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
index_content: str = ""
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return index_content
|
||||
|
||||
with app.app_context():
|
||||
get_config()
|
||||
get_manager()
|
||||
index_content = render_template("index.html")
|
||||
|
||||
return app
|
12
app/config.py
Normal file
12
app/config.py
Normal file
@ -0,0 +1,12 @@
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def load_config(filename: str):
|
||||
with open(filename, 'r', encoding='utf-8') as cfg_file:
|
||||
return yaml.safe_load(cfg_file)
|
||||
|
||||
|
||||
def config_location() -> str:
|
||||
return os.getenv('WEBPUB1C_CONFIG', 'config.yml')
|
18
app/glob.py
Normal file
18
app/glob.py
Normal file
@ -0,0 +1,18 @@
|
||||
from flask import g
|
||||
|
||||
from app.config import load_config, config_location
|
||||
from app.manager import PublicationManager
|
||||
|
||||
|
||||
def get_config():
|
||||
if 'config' not in g:
|
||||
g.config = load_config(config_location())
|
||||
|
||||
return g.config
|
||||
|
||||
|
||||
def get_manager():
|
||||
if 'manager' not in g:
|
||||
g.manager = PublicationManager(get_config())
|
||||
|
||||
return g.manager
|
136
app/manager.py
Normal file
136
app/manager.py
Normal file
@ -0,0 +1,136 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from pathvalidate import is_valid_filepath
|
||||
|
||||
from webpub1c.webpub.apache_config import ApacheConfig
|
||||
from webpub1c.webpub.common import VRDConfig, urlpath_join
|
||||
from webpub1c.webpub.webpublication import WebPublication
|
||||
|
||||
|
||||
def infobase_data_blank(name: str):
|
||||
return {
|
||||
'name': name,
|
||||
'publicated': False,
|
||||
'url': '',
|
||||
'directory': '',
|
||||
'vrd_filename': '',
|
||||
'infobase_filepath': '',
|
||||
'is_file_infobase': False,
|
||||
}
|
||||
|
||||
|
||||
def publication_data(publication: WebPublication):
|
||||
return {
|
||||
'name': publication.name,
|
||||
'publicated': True,
|
||||
'url': publication.url_path,
|
||||
'directory': publication.directory,
|
||||
'vrd_filename': publication.vrd_filename,
|
||||
'infobase_filepath': publication.infobase_filepath,
|
||||
'is_file_infobase': publication.is_file_infobase(),
|
||||
}
|
||||
|
||||
|
||||
class PublicationManager:
|
||||
def __init__(self, config, verbose: bool = False):
|
||||
level = logging.INFO if verbose else logging.WARNING
|
||||
logging.basicConfig(level=level)
|
||||
self._log = logging.getLogger("manager")
|
||||
self._log.setLevel(level)
|
||||
|
||||
self._config = config
|
||||
|
||||
vrd_params: Optional[VRDConfig] = self._config.get('vrd_params', None)
|
||||
apache_config: str = self._config.get('apache_config', '')
|
||||
self._vrd_path: str = self._config.get('vrd_path', '')
|
||||
self._dir_path: str = self._config.get('dir_path', '')
|
||||
self._url_base: str = self._config.get('url_base', '')
|
||||
|
||||
self._apache_cfg = ApacheConfig(apache_config, self._vrd_path,
|
||||
self._dir_path, self._url_base,
|
||||
vrd_params)
|
||||
|
||||
def _is_vrd_path_valid(self) -> bool:
|
||||
return os.path.isdir(self._vrd_path)
|
||||
|
||||
def _is_dir_path_valid(self) -> bool:
|
||||
return os.path.isdir(self._dir_path)
|
||||
|
||||
def _is_url_base_valid(self) -> bool:
|
||||
return is_valid_filepath(self._url_base, platform='posix') and self._url_base.startswith('/')
|
||||
|
||||
def _is_module_valid(self) -> bool:
|
||||
if 'platform_path' not in self._config:
|
||||
return False
|
||||
if 'ws_module' not in self._config:
|
||||
return False
|
||||
return os.path.isfile(os.path.join(self._config['platform_path'], self._config['ws_module']))
|
||||
|
||||
def check(self):
|
||||
return {
|
||||
'is_apache_cfg_valid': self._apache_cfg.is_valid(),
|
||||
'is_vrd_path_valid': self._is_vrd_path_valid(),
|
||||
'is_dir_path_valid': self._is_dir_path_valid(),
|
||||
'is_url_base_valid': self._is_url_base_valid(),
|
||||
'is_module_valid': self._is_module_valid()
|
||||
}
|
||||
|
||||
def has_module(self):
|
||||
return self._apache_cfg.has_1cws_module()
|
||||
|
||||
def add_module(self):
|
||||
""" Add 1cws module to apache config """
|
||||
|
||||
if self._apache_cfg.has_1cws_module():
|
||||
self._log.info('config unchanged')
|
||||
else:
|
||||
module: str = os.path.join(self._config['platform_path'], self._config['ws_module'])
|
||||
self._apache_cfg.add_1cws_module(module)
|
||||
self._log.info('module added')
|
||||
|
||||
def list(self) -> List[str]:
|
||||
""" List publication names """
|
||||
return list(self._apache_cfg.publications)
|
||||
|
||||
def publications(self) -> List[Dict[str, str]]:
|
||||
""" List of publications """
|
||||
|
||||
pubs = map(lambda p: publication_data(p), self._apache_cfg.iter())
|
||||
return list(pubs)
|
||||
|
||||
def get(self, ibname: str):
|
||||
""" Get publication info """
|
||||
|
||||
publication = self._apache_cfg.get_publication(ibname)
|
||||
if publication is None:
|
||||
return publication
|
||||
|
||||
return publication_data(publication)
|
||||
|
||||
def add(self, ibname: str, url: Optional[str] = None, file: str = '', force: bool = False) -> str:
|
||||
""" Add new publication """
|
||||
|
||||
publication = self._apache_cfg.create_publication(ibname, url, file, force)
|
||||
self._apache_cfg.add_publication(publication, force)
|
||||
self._log.info(f'publication added: {ibname}')
|
||||
return publication.url_path
|
||||
|
||||
def set_url(self, ibname: str, url: str) -> None:
|
||||
""" Set publication url """
|
||||
|
||||
publication = self._apache_cfg.get_publication(ibname)
|
||||
if publication is None:
|
||||
raise KeyError(f'infobase "{ibname}" not publicated')
|
||||
|
||||
publication.url_path = urlpath_join(self._url_base, url)
|
||||
self._apache_cfg.remove_publication(publication.name, destroy=False)
|
||||
self._apache_cfg.add_publication(publication)
|
||||
self._log.info(f'publication changed: {ibname}')
|
||||
|
||||
def remove(self, ibname: str, force: bool = False):
|
||||
""" Remove publication """
|
||||
|
||||
self._apache_cfg.remove_publication(ibname, force=force)
|
||||
self._log.info(f'publication removed: {ibname}')
|
10
app/utils.py
Normal file
10
app/utils.py
Normal file
@ -0,0 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from flask_restful import abort
|
||||
from pathvalidate import is_valid_filepath
|
||||
|
||||
|
||||
def validate_url(url: Optional[str]):
|
||||
if url is not None:
|
||||
if not is_valid_filepath(url, platform='posix'):
|
||||
abort(400, message='invalid url')
|
@ -1,7 +1,7 @@
|
||||
infobases:
|
||||
server_file: test/1CV8Clst.lst
|
||||
apache_restart_flagfile: test/apache_restart
|
||||
apache_config: webpub1c/test/apache.cfg
|
||||
apache_config: webpub1c/example/apache.cfg
|
||||
url_prefix: http://localhost
|
||||
vrd_path: webpub1c/test/vrds
|
||||
dir_path: webpub1c/test/pubs
|
||||
|
371
pub1c-rest.py
371
pub1c-rest.py
@ -1,371 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import List, Optional, Dict
|
||||
import traceback
|
||||
|
||||
import yaml
|
||||
from flask import Flask, g, render_template
|
||||
from flask_restful import Resource, Api, reqparse, abort
|
||||
from flask_cors import CORS
|
||||
from pathvalidate import is_valid_filepath
|
||||
|
||||
from brackets import get_infobases as br_get_infobases
|
||||
from webpub1c.webpub1c import VRDConfig, ApacheConfig, urlpath_join, WebPublication
|
||||
|
||||
apache_restart_flagfile = 'restart_apache'
|
||||
|
||||
|
||||
def infobase_data_blank(name: str):
|
||||
return {
|
||||
'name': name,
|
||||
'publicated': False,
|
||||
'url': '',
|
||||
'directory': '',
|
||||
'vrd_filename': '',
|
||||
}
|
||||
|
||||
|
||||
def publication_data(publication: WebPublication):
|
||||
return {
|
||||
'name': publication.name,
|
||||
'publicated': True,
|
||||
'url': publication.url_path,
|
||||
'directory': publication.directory,
|
||||
'vrd_filename': publication.vrd_filename,
|
||||
}
|
||||
|
||||
|
||||
class WebPub1C:
|
||||
def __init__(self, config, verbose: bool = False):
|
||||
level = logging.INFO if verbose else logging.WARNING
|
||||
logging.basicConfig(level=level)
|
||||
self._log = logging.getLogger("webpub1c")
|
||||
self._log.setLevel(level)
|
||||
|
||||
self._config = config
|
||||
|
||||
vrd_params: Optional[VRDConfig] = self._config.get('vrd_params', None)
|
||||
apache_config: str = self._config.get('apache_config', '')
|
||||
self._vrd_path: str = self._config.get('vrd_path', '')
|
||||
self._dir_path: str = self._config.get('dir_path', '')
|
||||
self._url_base: str = self._config.get('url_base', '')
|
||||
|
||||
self._apache_cfg = ApacheConfig(apache_config, self._vrd_path,
|
||||
self._dir_path, self._url_base,
|
||||
vrd_params)
|
||||
|
||||
def _is_vrd_path_valid(self) -> bool:
|
||||
return os.path.isdir(self._vrd_path)
|
||||
|
||||
def _is_dir_path_valid(self) -> bool:
|
||||
return os.path.isdir(self._dir_path)
|
||||
|
||||
def _is_url_base_valid(self) -> bool:
|
||||
return is_valid_filepath(self._url_base, platform='posix') and self._url_base.startswith('/')
|
||||
|
||||
def _is_module_valid(self) -> bool:
|
||||
if 'platform_path' not in self._config:
|
||||
return False
|
||||
if 'ws_module' not in self._config:
|
||||
return False
|
||||
return os.path.isfile(os.path.join(self._config['platform_path'], self._config['ws_module']))
|
||||
|
||||
def check(self):
|
||||
return {
|
||||
'is_apache_cfg_valid': self._apache_cfg.is_valid(),
|
||||
'is_vrd_path_valid': self._is_vrd_path_valid(),
|
||||
'is_dir_path_valid': self._is_dir_path_valid(),
|
||||
'is_url_base_valid': self._is_url_base_valid(),
|
||||
'is_module_valid': self._is_module_valid()
|
||||
}
|
||||
|
||||
def has_module(self):
|
||||
return self._apache_cfg.has_1cws_module()
|
||||
|
||||
def add_module(self):
|
||||
""" Add 1cws module to apache config """
|
||||
|
||||
if self._apache_cfg.has_1cws_module():
|
||||
self._log.info('config unchanged')
|
||||
else:
|
||||
module: str = os.path.join(self._config['platform_path'], self._config['ws_module'])
|
||||
self._apache_cfg.add_1cws_module(module)
|
||||
self._log.info('module added')
|
||||
|
||||
def list(self) -> List[str]:
|
||||
""" List publication names """
|
||||
return list(self._apache_cfg.publications)
|
||||
|
||||
def publications(self) -> List[Dict[str, str]]:
|
||||
""" List of publications """
|
||||
|
||||
pubs = map(lambda p: publication_data(p), self._apache_cfg.iter())
|
||||
return list(pubs)
|
||||
|
||||
def get(self, ibname: str):
|
||||
""" Get publication info """
|
||||
|
||||
publication = self._apache_cfg.get_publication(ibname)
|
||||
if publication is None:
|
||||
return publication
|
||||
|
||||
return publication_data(publication)
|
||||
|
||||
def add(self, ibname: str, url: Optional[str] = None) -> str:
|
||||
""" Add new publication """
|
||||
|
||||
publication = self._apache_cfg.create_publication(ibname, url)
|
||||
self._apache_cfg.add_publication(publication)
|
||||
self._log.info(f'publication added: {ibname}')
|
||||
return publication.url_path
|
||||
|
||||
def set_url(self, ibname: str, url: str) -> None:
|
||||
""" Set publication url """
|
||||
|
||||
publication = self._apache_cfg.get_publication(ibname)
|
||||
if publication is None:
|
||||
raise KeyError(f'infobase "{ibname}" not publicated')
|
||||
|
||||
publication.url_path = urlpath_join(self._url_base, url)
|
||||
self._apache_cfg.remove_publication(publication.name, destroy=False)
|
||||
self._apache_cfg.add_publication(publication)
|
||||
self._log.info(f'publication changed: {ibname}')
|
||||
|
||||
def remove(self, ibname: str):
|
||||
""" Remove publication """
|
||||
|
||||
self._apache_cfg.remove_publication(ibname)
|
||||
self._log.info(f'publication removed: {ibname}')
|
||||
|
||||
|
||||
pub_parser = reqparse.RequestParser()
|
||||
pub_parser.add_argument('name', required=True, type=str, help='name required')
|
||||
pub_parser.add_argument('url', type=str)
|
||||
|
||||
url_parser = reqparse.RequestParser()
|
||||
url_parser.add_argument('url', type=str)
|
||||
|
||||
url_req_parser = reqparse.RequestParser()
|
||||
url_req_parser.add_argument('url', type=str, required=True)
|
||||
|
||||
|
||||
def load_config(filename: str = 'config.yml'):
|
||||
with open(filename, 'r', encoding='utf-8') as cfg_file:
|
||||
return yaml.safe_load(cfg_file)
|
||||
|
||||
|
||||
def get_config():
|
||||
if 'config' not in g:
|
||||
g.config = load_config()
|
||||
|
||||
return g.config
|
||||
|
||||
|
||||
def get_webpub1c():
|
||||
if 'webpub1c' not in g:
|
||||
g.webpub1c = WebPub1C(get_config())
|
||||
|
||||
return g.webpub1c
|
||||
|
||||
|
||||
def load_infobases(config) -> List[str]:
|
||||
result = []
|
||||
if 'infobases' in config and 'server_file' in config['infobases']:
|
||||
result = br_get_infobases(config['infobases']['server_file'])
|
||||
return result
|
||||
|
||||
|
||||
def validate_url(url: Optional[str]):
|
||||
if url is not None:
|
||||
if not is_valid_filepath(url, platform='posix'):
|
||||
abort(400, message='invalid url')
|
||||
|
||||
|
||||
class InfobasesAvailable(Resource):
|
||||
def get(self) -> List[str]:
|
||||
cfg = get_config()
|
||||
bases = load_infobases(cfg)
|
||||
return bases
|
||||
|
||||
|
||||
class Config(Resource):
|
||||
def get(self):
|
||||
return get_config()
|
||||
|
||||
|
||||
class ConfigTest(Resource):
|
||||
def get(self):
|
||||
webpub = get_webpub1c()
|
||||
return webpub.check()
|
||||
|
||||
|
||||
class InfobasesAll(Resource):
|
||||
def get(self) -> List[Dict[str, str]]:
|
||||
cfg = get_config()
|
||||
webpub = get_webpub1c()
|
||||
infobase_names = load_infobases(cfg)
|
||||
pubs = webpub.publications()
|
||||
result: List[Dict[str, str]] = []
|
||||
for name in infobase_names:
|
||||
publicated = False
|
||||
for pub in filter(lambda x: x["name"] == name, pubs):
|
||||
publicated = True
|
||||
result.append(pub)
|
||||
break
|
||||
if not publicated:
|
||||
result.append(infobase_data_blank(name))
|
||||
return result
|
||||
|
||||
|
||||
class Publications(Resource):
|
||||
def get(self) -> List[Dict[str, str]]:
|
||||
webpub = get_webpub1c()
|
||||
return webpub.publications()
|
||||
|
||||
def put(self):
|
||||
args = pub_parser.parse_args()
|
||||
validate_url(args.url)
|
||||
webpub = get_webpub1c()
|
||||
url = args.url
|
||||
if args.name in webpub.list():
|
||||
abort(409, message=f'publication exists: {args.name}')
|
||||
try:
|
||||
url = webpub.add(args.name, url)
|
||||
except Exception as e:
|
||||
abort(422, message=f'publication failed: {args.name}', traceback=traceback.format_exc())
|
||||
return {'message': 'created', 'name': args.name, 'url': url}, 201
|
||||
|
||||
|
||||
class Publication(Resource):
|
||||
def get(self, name: str):
|
||||
webpub = get_webpub1c()
|
||||
if name not in webpub.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
return {
|
||||
name: webpub.get(name)
|
||||
}
|
||||
|
||||
def put(self, name: str):
|
||||
args = url_parser.parse_args()
|
||||
validate_url(args.url)
|
||||
webpub = get_webpub1c()
|
||||
url = args.url
|
||||
if name in webpub.list():
|
||||
abort(409, message=f'publication exists: {name}')
|
||||
try:
|
||||
url = webpub.add(name, url)
|
||||
except Exception as e:
|
||||
abort(422, message=f'publication failed: {name}', traceback=traceback.format_exc())
|
||||
return {'message': 'created', 'name': name, 'url': url}, 201
|
||||
|
||||
def delete(self, name: str):
|
||||
webpub = get_webpub1c()
|
||||
if name not in webpub.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
try:
|
||||
webpub.remove(name)
|
||||
except Exception as e:
|
||||
abort(422, message='delete failed', traceback=traceback.format_exc())
|
||||
return {'message': 'deleted', 'name': name}
|
||||
|
||||
|
||||
class PublicationURL(Resource):
|
||||
def get(self, name: str):
|
||||
webpub = get_webpub1c()
|
||||
if name not in webpub.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
return {
|
||||
'url': webpub.get(name)['url']
|
||||
}
|
||||
|
||||
def post(self, name: str):
|
||||
args = url_req_parser.parse_args()
|
||||
validate_url(args.url)
|
||||
webpub = get_webpub1c()
|
||||
url = args.url
|
||||
if name not in webpub.list():
|
||||
abort(404, message=f'publication not found: {name}')
|
||||
try:
|
||||
webpub.set_url(name, url)
|
||||
except Exception as e:
|
||||
abort(422, message=f'set url failed', traceback=traceback.format_exc())
|
||||
return {'message': 'success', 'name': name, 'url': url}
|
||||
|
||||
|
||||
class EnterpriseModule(Resource):
|
||||
def get(self):
|
||||
webpub = get_webpub1c()
|
||||
if not webpub.has_module():
|
||||
abort(404, message='not found')
|
||||
return {
|
||||
'message': 'found'
|
||||
}
|
||||
|
||||
def put(self):
|
||||
webpub = get_webpub1c()
|
||||
if webpub.has_module():
|
||||
abort(304, message='already added')
|
||||
webpub.add_module()
|
||||
return {
|
||||
'message': 'success'
|
||||
}
|
||||
|
||||
|
||||
class ApacheRestartFlag(Resource):
|
||||
def get(self):
|
||||
cfg = get_config()
|
||||
flagfile = cfg.get('apache_restart_flagfile', apache_restart_flagfile)
|
||||
if not os.path.isfile(flagfile):
|
||||
abort(404, message='not found')
|
||||
return {
|
||||
'message': 'found'
|
||||
}
|
||||
|
||||
def put(self):
|
||||
cfg = get_config()
|
||||
flagfile = cfg.get('apache_restart_flagfile', apache_restart_flagfile)
|
||||
if os.path.isfile(flagfile):
|
||||
abort(304, message='found')
|
||||
with open(flagfile, 'a'):
|
||||
pass
|
||||
return {
|
||||
'message': 'success'
|
||||
}
|
||||
|
||||
|
||||
class APIIndex(Resource):
|
||||
def get(self):
|
||||
return ['infobases-available', 'infobases-all', 'publications',
|
||||
'module', 'config', 'config-test', 'apache-restart']
|
||||
|
||||
|
||||
frontend_dir = 'frontend/dist'
|
||||
app = Flask(__name__, static_url_path='/', static_folder=frontend_dir, template_folder=frontend_dir)
|
||||
api = Api(app, '/api/v1/')
|
||||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
api.add_resource(InfobasesAvailable, '/infobases-available')
|
||||
api.add_resource(InfobasesAll, '/infobases-all')
|
||||
api.add_resource(Publications, '/publications', '/publications/')
|
||||
api.add_resource(Publication, '/publications/<string:name>')
|
||||
api.add_resource(PublicationURL, '/publications/<string:name>/url')
|
||||
api.add_resource(EnterpriseModule, '/module')
|
||||
api.add_resource(Config, '/config')
|
||||
api.add_resource(ConfigTest, '/config-test')
|
||||
api.add_resource(ApacheRestartFlag, '/apache-restart')
|
||||
api.add_resource(APIIndex, '/')
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return index_content
|
||||
|
||||
|
||||
with app.app_context():
|
||||
get_config()
|
||||
get_webpub1c()
|
||||
index_content = render_template("index.html")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
@ -5,5 +5,4 @@ jinja2==3.0
|
||||
PyYAML==5.4.1
|
||||
pathvalidate==2.4.1
|
||||
transliterate==1.10.2
|
||||
fire==0.4.0
|
||||
gunicorn==19.9.0
|
||||
|
7
run.py
Normal file
7
run.py
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
from app.app import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
2
run.sh
2
run.sh
@ -4,4 +4,4 @@ cd "$( dirname "$0" )" || exit
|
||||
|
||||
. venv/bin/activate
|
||||
|
||||
gunicorn --bind 0.0.0.0:18333 pub1c-rest:app
|
||||
gunicorn --bind 0.0.0.0:18333 run:app
|
||||
|
21
test.py
21
test.py
@ -1,21 +0,0 @@
|
||||
from requests import put, get, post, delete
|
||||
import json
|
||||
from pprint import pformat
|
||||
|
||||
api='http://localhost:5000/api/v1/'
|
||||
|
||||
|
||||
def main():
|
||||
#print(api, get(api).json())
|
||||
#print(api+'infobases', get(api+'infobases').json())
|
||||
#print(api+'publications', get(api+'publications').json())
|
||||
res = put(api+'publications', data={'name': 'test3'})
|
||||
print('put', api+'publications', res.status_code, pformat(res.json()))
|
||||
res = delete(api + 'publications/test3', data={'name': 'test3'})
|
||||
print('delete', api + 'publications/test3', res.status_code, pformat(res.json()))
|
||||
#res = get(api + 'publications/test1')
|
||||
#print('GET', api + 'publications/test1', res.status_code, pformat(res.json()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
256
tests/test_pub1c-rest.py
Normal file
256
tests/test_pub1c-rest.py
Normal file
@ -0,0 +1,256 @@
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
import pytest
|
||||
import yaml
|
||||
from flask.testing import FlaskClient
|
||||
|
||||
from app.app import create_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_config(tmpdir) -> str:
|
||||
configfile = str(tmpdir.join('config.yml'))
|
||||
vrd_path = tmpdir.mkdir('vrds')
|
||||
dir_path = tmpdir.mkdir('pubs')
|
||||
apache_config = tmpdir.join('apache.cfg')
|
||||
apache_config.write('#start\n')
|
||||
flagfile = tmpdir.join('apache_restart')
|
||||
platform_path = tmpdir.mkdir('platform')
|
||||
ws_module = 'wsap24.so'
|
||||
platform_path.join(ws_module).write('\0')
|
||||
|
||||
with open(configfile, 'w') as f:
|
||||
yaml.dump({
|
||||
'apache_config': str(apache_config),
|
||||
'vrd_path': str(vrd_path),
|
||||
'dir_path': str(dir_path),
|
||||
'url_base': '/1c',
|
||||
'platform_path': str(platform_path),
|
||||
'ws_module': ws_module,
|
||||
'vrd_params': {
|
||||
'debug': None,
|
||||
'server_addr': 'localhost'
|
||||
},
|
||||
'infobases': {
|
||||
'server_file': 'test/1CV8Clst.lst',
|
||||
},
|
||||
'apache_restart_flagfile': str(flagfile),
|
||||
'url_prefix': 'http://localhost',
|
||||
}, f)
|
||||
return configfile
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def flask_app(temp_config) -> Flask:
|
||||
os.environ['WEBPUB1C_CONFIG'] = temp_config
|
||||
return create_app()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(flask_app) -> FlaskClient:
|
||||
return flask_app.test_client()
|
||||
|
||||
|
||||
def test_index(client):
|
||||
response = client.get('/')
|
||||
assert b'<!DOCTYPE html>' in response.data
|
||||
|
||||
|
||||
def test_api_index(client):
|
||||
response = client.get('/api/v1/')
|
||||
data = response.get_json()
|
||||
assert data == ['infobases-available', 'infobases-all', 'publications',
|
||||
'module', 'config', 'config-test', 'apache-restart']
|
||||
|
||||
|
||||
def test_api_restart_flag(client):
|
||||
endpoint = '/api/v1/apache-restart'
|
||||
# test when flag not set
|
||||
response = client.get(endpoint)
|
||||
assert 404 == response.status_code
|
||||
# set flag
|
||||
response = client.put(endpoint)
|
||||
assert 200 == response.status_code
|
||||
# test when flag is set
|
||||
response = client.get(endpoint)
|
||||
assert 200 == response.status_code
|
||||
|
||||
|
||||
def test_api_config_test(client):
|
||||
response = client.get('/api/v1/config-test')
|
||||
data = response.get_json()
|
||||
assert data == {
|
||||
'is_apache_cfg_valid': True,
|
||||
'is_vrd_path_valid': True,
|
||||
'is_dir_path_valid': True,
|
||||
'is_url_base_valid': True,
|
||||
'is_module_valid': True,
|
||||
}
|
||||
|
||||
|
||||
def test_api_config(client):
|
||||
response = client.get('/api/v1/config')
|
||||
data = response.get_json()
|
||||
expected = ['apache_config', 'apache_restart_flagfile', 'infobases', 'dir_path', 'platform_path',
|
||||
'url_base', 'url_prefix', 'vrd_params', 'vrd_path', 'ws_module']
|
||||
expected.sort()
|
||||
actual = list(data.keys())
|
||||
actual.sort()
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_api_module(client):
|
||||
endpoint = '/api/v1/module'
|
||||
# test when module not added
|
||||
response = client.get(endpoint)
|
||||
assert 404 == response.status_code
|
||||
# add module
|
||||
response = client.put(endpoint)
|
||||
assert 200 == response.status_code
|
||||
# test when module added
|
||||
response = client.get(endpoint)
|
||||
assert 200 == response.status_code
|
||||
# try to add module again
|
||||
response = client.put(endpoint)
|
||||
assert 304 == response.status_code
|
||||
|
||||
|
||||
def test_api_infobases_available(client):
|
||||
response = client.get('/api/v1/infobases-available')
|
||||
data = response.get_json()
|
||||
expected = ['test1', 'test2', 'bpdemo']
|
||||
expected.sort()
|
||||
actual = data
|
||||
actual.sort()
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_api_publications(client):
|
||||
response = client.get('/api/v1/publications')
|
||||
data = response.get_json()
|
||||
assert data == []
|
||||
|
||||
|
||||
def test_api_publications_add(client):
|
||||
endpoint = '/api/v1/publications'
|
||||
# before add
|
||||
response = client.get(endpoint)
|
||||
data = response.get_json()
|
||||
assert data == []
|
||||
# add
|
||||
response = client.put(path=endpoint, data={'name': 'test123'})
|
||||
data = response.get_json()
|
||||
assert data['message'] == 'created'
|
||||
assert data['name'] == 'test123'
|
||||
# add same publication
|
||||
response = client.put(path=endpoint, data={'name': 'test123'})
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
def test_api_publications_add_empty(client):
|
||||
endpoint = '/api/v1/publications'
|
||||
# add with empty name
|
||||
response = client.put(path=endpoint, data={'name': ''})
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
def test_api_publications_add_force(client):
|
||||
endpoint = '/api/v1/publications'
|
||||
# add
|
||||
response = client.put(path=endpoint, data={'name': 'test123'})
|
||||
assert response.status_code == 201
|
||||
# add same publication
|
||||
response = client.put(path=endpoint, data={'name': 'test123'})
|
||||
assert response.status_code == 409
|
||||
# add same publication with force
|
||||
response = client.put(path=endpoint, data={'name': 'test123', 'force': True})
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_api_publications_add_and_list(client):
|
||||
endpoint = '/api/v1/publications'
|
||||
# add publications
|
||||
client.put(path=endpoint, data={'name': 'test123'})
|
||||
client.put(path=endpoint, data={'name': 'test456', 'file': '/path/to/infobase'})
|
||||
# get list
|
||||
response = client.get(endpoint)
|
||||
data = response.get_json()
|
||||
assert len(data) == 2
|
||||
# test123
|
||||
assert data[0]['name'] == 'test123'
|
||||
assert data[0]['publicated']
|
||||
assert data[0]['url'] == '/1c/test123'
|
||||
assert not data[0]['is_file_infobase']
|
||||
# test456 / file
|
||||
assert data[1]['name'] == 'test456'
|
||||
assert data[1]['publicated']
|
||||
assert data[1]['url'] == '/1c/test456'
|
||||
assert data[1]['is_file_infobase']
|
||||
assert data[1]['infobase_filepath'] == '/path/to/infobase'
|
||||
|
||||
|
||||
def test_api_publication_get(client):
|
||||
# add publications
|
||||
client.put(path='/api/v1/publications', data={'name': 'test123'})
|
||||
# get
|
||||
response = client.get('/api/v1/publications/test123')
|
||||
data = response.get_json()
|
||||
assert 'test123' in data
|
||||
data = data['test123']
|
||||
assert data['name'] == 'test123'
|
||||
assert data['publicated']
|
||||
assert data['url'] == '/1c/test123'
|
||||
assert not data['is_file_infobase']
|
||||
|
||||
|
||||
def test_api_publication_remove(client):
|
||||
# add publications
|
||||
client.put(path='/api/v1/publications', data={'name': 'test123'})
|
||||
# get
|
||||
response = client.get('/api/v1/publications/test123')
|
||||
assert response.status_code == 200
|
||||
# remove
|
||||
response = client.delete('/api/v1/publications/test123')
|
||||
assert response.status_code == 200
|
||||
# get
|
||||
response = client.get('/api/v1/publications/test123')
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_api_publication_set_url(client):
|
||||
# add publications
|
||||
client.put(path='/api/v1/publications', data={'name': 'test123', 'url': 'some/url'})
|
||||
# get
|
||||
response = client.get('/api/v1/publications/test123/url')
|
||||
data = response.get_json()
|
||||
assert data['url'] == '/1c/some/url'
|
||||
# set url
|
||||
response = client.post(path='/api/v1/publications/test123/url', data={'url': '/another/url'})
|
||||
assert response.status_code == 200
|
||||
# get
|
||||
response = client.get('/api/v1/publications/test123/url')
|
||||
data = response.get_json()
|
||||
assert data['url'] == '/1c/another/url'
|
||||
|
||||
|
||||
def test_api_infobases_all(client):
|
||||
# add publications
|
||||
client.put(path='/api/v1/publications', data={'name': 'test1'})
|
||||
# get list
|
||||
response = client.get('/api/v1/infobases-all')
|
||||
data = response.get_json()
|
||||
assert len(data) == 3
|
||||
# bpdemo
|
||||
assert data[0]['name'] == 'bpdemo'
|
||||
assert not data[0]['publicated']
|
||||
assert data[0]['url'] == ''
|
||||
# test1
|
||||
assert data[1]['name'] == 'test1'
|
||||
assert data[1]['publicated']
|
||||
assert data[1]['url'] == '/1c/test1'
|
||||
assert not data[1]['is_file_infobase']
|
||||
# test2
|
||||
assert data[2]['name'] == 'test2'
|
||||
assert not data[2]['publicated']
|
||||
assert data[2]['url'] == ''
|
2
webpub1c
2
webpub1c
@ -1 +1 @@
|
||||
Subproject commit 4b04a37001685cdd8790c65d1d935a5c73ba673e
|
||||
Subproject commit 8457b201e2d64ce602bde1e998f251a4a70175f9
|
Loading…
Reference in New Issue
Block a user