Compare commits

...

45 Commits

Author SHA1 Message Date
Dmitry Belyaev f8f18c40e7
add test_api_publications_add_empty 2022-02-23 21:24:20 +03:00
Dmitry Belyaev 231d11a5fe
update webpub1c 2022-02-23 21:13:35 +03:00
Dmitry Belyaev 8d65d3787c
fix example config path 2021-10-07 14:43:42 +03:00
Dmitry Belyaev a50d5b177f
fix init index_content 2021-10-07 14:41:08 +03:00
Dmitry Belyaev 316cf7e581
fix create_app import in tests 2021-10-07 14:38:38 +03:00
Dmitry Belyaev fa282b96e0
create_app type hint 2021-10-07 14:37:03 +03:00
Dmitry Belyaev cf66a0d0d0
move brackets and rename launcher 2021-10-07 14:35:46 +03:00
Dmitry Belyaev 710277abae
add create_app 2021-10-07 14:31:12 +03:00
Dmitry Belyaev 05ef983dc2
+ add_api_resources 2021-10-07 14:14:08 +03:00
Dmitry Belyaev 701cbf94e1
move publications api 2021-10-07 14:04:48 +03:00
Dmitry Belyaev 24d3f50e29
move config and module api 2021-10-07 13:59:51 +03:00
Dmitry Belyaev 83d6388e35
move infobases api 2021-10-07 13:57:39 +03:00
Dmitry Belyaev f5680564d2
move api index and apache_restart 2021-10-07 13:54:49 +03:00
Dmitry Belyaev 8eef627c2e
move glob and utils 2021-10-07 13:39:09 +03:00
Dmitry Belyaev 96023662d3
move config and manager 2021-10-07 13:26:43 +03:00
Dmitry Belyaev d89715b8fd
rename PublicationManager 2021-09-24 16:14:59 +03:00
Dmitry Belyaev f715fb11dc
add force param to publications put/delete 2021-09-24 15:24:11 +03:00
Dmitry Belyaev ab6ab52f24
add test_api_publications_add_force 2021-09-24 15:23:30 +03:00
Dmitry Belyaev be03d196b7
remove fire from requirements.txt 2021-09-23 15:53:02 +03:00
Dmitry Belyaev 8f7af381e6
update webpub1c 2021-09-23 15:52:33 +03:00
Dmitry Belyaev f04db259c6
tests: client type hint 2021-09-23 12:04:36 +03:00
Dmitry Belyaev a716df4beb
tests: remove json import 2021-09-23 11:20:05 +03:00
Dmitry Belyaev bffa816ef1
add test_api_infobases_all 2021-09-23 11:19:09 +03:00
Dmitry Belyaev 4fd9c7330e
remove test.py 2021-09-22 16:40:17 +03:00
Dmitry Belyaev f311a5788e
add test_api_publication_set_url 2021-09-22 16:36:48 +03:00
Dmitry Belyaev 7d91b69e81
add test_api_publication_remove 2021-09-22 16:28:36 +03:00
Dmitry Belyaev ee163fe9a1
add test_api_publication_get 2021-09-22 16:17:19 +03:00
Dmitry Belyaev cb2de342c2
add test_api_publications_add_and_list 2021-09-22 16:12:51 +03:00
Dmitry Belyaev de454fe5c1
fix default value for file param 2021-09-22 16:06:59 +03:00
Dmitry Belyaev 57265c4e7c
add status:409 test to test_api_publications_add 2021-09-22 15:53:55 +03:00
Dmitry Belyaev abb1434b8d
update webpub1c 2021-09-22 15:50:34 +03:00
Dmitry Belyaev 72b13b7e98
add test_api_publications_add 2021-09-22 15:49:58 +03:00
Dmitry Belyaev 9d9e213607
add test_api_infobases_available 2021-09-22 15:33:58 +03:00
Dmitry Belyaev e8341cb899
add test_api_module 2021-09-22 15:19:02 +03:00
Dmitry Belyaev 7f87191f2a
fix 304 codes 2021-09-22 15:18:25 +03:00
Dmitry Belyaev 3d3d3df19f
add test_api_config 2021-09-22 15:02:45 +03:00
Dmitry Belyaev 27ab87a7a2
add test_api_config_test 2021-09-22 14:55:44 +03:00
Dmitry Belyaev 92d495dfd5
add test_api_restart_flag 2021-09-22 14:48:40 +03:00
Dmitry Belyaev b2e2cda3ea
tests: fix config 2021-09-22 14:48:05 +03:00
Dmitry Belyaev 30a9e6400a
add tests 2021-09-22 13:57:03 +03:00
Dmitry Belyaev b4e04c5e46
config location from env 2021-09-22 13:03:58 +03:00
Dmitry Belyaev d37db9fb86
add file param 2021-09-22 12:46:18 +03:00
Dmitry Belyaev d289a41e4d
add file infobase info 2021-09-22 12:22:51 +03:00
Dmitry Belyaev 1165606a37
update webpub1c 2021-09-22 12:10:41 +03:00
Dmitry Belyaev d1389abaf2
apache_restarter service: create dir 2021-06-22 17:23:29 +03:00
24 changed files with 701 additions and 396 deletions

View File

@ -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
View File

0
app/api/__init__.py Normal file
View File

29
app/api/apache_restart.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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')

View File

@ -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

View File

@ -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)

View File

@ -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
View File

@ -0,0 +1,7 @@
from app.app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)

2
run.sh
View File

@ -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
View File

@ -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
View File

256
tests/test_pub1c-rest.py Normal file
View 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'] == ''

@ -1 +1 @@
Subproject commit 4b04a37001685cdd8790c65d1d935a5c73ba673e
Subproject commit 8457b201e2d64ce602bde1e998f251a4a70175f9