Initial Version
This commit is contained in:
commit
3046b1ee7b
21 changed files with 491 additions and 0 deletions
72
.forgejo/workflows/build_release.yml
Normal file
72
.forgejo/workflows/build_release.yml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '[0-9]+\.[0-9]+\.[0-9]+-c\.[0-9]+'
|
||||||
|
- '[0-9]+\.[0-9]+\.[0-9]+-a\.[0-9]+'
|
||||||
|
- '[0-9]+\.[0-9]+\.[0-9]'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-artifacts:
|
||||||
|
runs-on: docker
|
||||||
|
container: nikolaik/python-nodejs:python3.11-nodejs21
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
|
- name: Install packages
|
||||||
|
run: pip install build
|
||||||
|
- name: Build package
|
||||||
|
run: python -m build
|
||||||
|
- name: Save build artifacts
|
||||||
|
uses: https://code.forgejo.org/actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: packages
|
||||||
|
path: dist/*
|
||||||
|
|
||||||
|
publish-artifacts:
|
||||||
|
needs: ["build-artifacts"]
|
||||||
|
runs-on: docker
|
||||||
|
container: nikolaik/python-nodejs:python3.11-nodejs21
|
||||||
|
steps:
|
||||||
|
- name: Downloading static site artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: packages
|
||||||
|
path: dist
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: pip install twine
|
||||||
|
- name: Upload package to registry
|
||||||
|
run: python -m twine upload --repository-url ${{ secrets.REPOSITORY_URL }} -u ${{ secrets.TWINE_DEPLOY_USER }} -p ${{ secrets.TWINE_DEPLOY_PASSWORD }} dist/*
|
||||||
|
|
||||||
|
build-and-push-container:
|
||||||
|
needs: [ "publish-artifacts" ]
|
||||||
|
runs-on: dind
|
||||||
|
steps:
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Log into Docker Package Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: forgejo.neshweb.net
|
||||||
|
username: ${{ secrets.FORGEJO_USERNAME }}
|
||||||
|
password: ${{ secrets.FORGEJO_TOKEN }}
|
||||||
|
- name: Build and push to Docker Package Registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
build-args: |
|
||||||
|
PACKAGE_VERSION=${{ github.ref_name }}
|
||||||
|
push: true
|
||||||
|
tags: forgejo.neshweb.net/firq/support-formatter:${{ github.ref_name }}
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: [ build-and-push-container, publish-artifacts ]
|
||||||
|
if: success()
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Release New Version
|
||||||
|
uses: actions/forgejo-release@v1
|
||||||
|
with:
|
||||||
|
direction: upload
|
||||||
|
url: https://forgejo.neshweb.net
|
||||||
|
release-dir: release
|
||||||
|
token: ${{ secrets.FORGEJO_TOKEN }}
|
||||||
|
tag: ${{ github.ref_name }}
|
20
.forgejo/workflows/default.yml
Normal file
20
.forgejo/workflows/default.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: "**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backend-pylint:
|
||||||
|
runs-on: docker
|
||||||
|
container: nikolaik/python-nodejs:python3.11-nodejs21
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
pip install -e . -q
|
||||||
|
python -m pip list --format=columns --disable-pip-version-check
|
||||||
|
pip install pylint~=2.17.7 --disable-pip-version-check -q
|
||||||
|
- name: Run pylint
|
||||||
|
run: |
|
||||||
|
pylint --version
|
||||||
|
pylint **/*.py --exit-zero
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.venv/
|
||||||
|
.vscode/
|
||||||
|
**/.temp/*
|
||||||
|
__pycache__/
|
||||||
|
*.egg-info
|
||||||
|
dist/
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FROM forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11
|
||||||
|
ARG PACKAGE_VERSION=0.1.0
|
||||||
|
RUN pip install support_formatter==${PACKAGE_VERSION}
|
||||||
|
|
||||||
|
CMD [ "support-formatter" ]
|
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include support_formatter/pages/*.html
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# support-organizer
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Set up the venv and requirements
|
||||||
|
|
||||||
|
```shell
|
||||||
|
py -m venv .venv
|
||||||
|
.venv/Scripts/activate
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Start the server using
|
||||||
|
|
||||||
|
```shell
|
||||||
|
py -m support-formatter
|
||||||
|
```
|
38
pyproject.toml
Normal file
38
pyproject.toml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
[project]
|
||||||
|
name = "support_formatter"
|
||||||
|
version = "0.1.0-a.1"
|
||||||
|
requires-python = ">= 3.10"
|
||||||
|
authors = [{name = "Firq", email = "firelp42@gmail.com"}]
|
||||||
|
maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]
|
||||||
|
description = "Tool to manage requests for supports"
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Framework :: Flask",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"flask~=2.3.3",
|
||||||
|
"flask-smorest~=0.42.1",
|
||||||
|
"marshmallow~=3.20.1",
|
||||||
|
"gevent~=23.9.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
support-formatter = "support_formatter.server:run_server"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["support_formatter*"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
support_formatter = ["*.html"]
|
||||||
|
|
||||||
|
[tool.pylint."MAIN"]
|
||||||
|
disable = [ "line-too-long", "missing-module-docstring" ]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools >= 61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
2
support_formatter/__init__.py
Normal file
2
support_formatter/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import importlib.metadata
|
||||||
|
__version__ = importlib.metadata.version(__package__ or "support_formatter")
|
38
support_formatter/app.py
Normal file
38
support_formatter/app.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Flask
|
||||||
|
from flask_smorest import Api
|
||||||
|
from .config import APISettings
|
||||||
|
|
||||||
|
class IsSingletonException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Application:
|
||||||
|
"""
|
||||||
|
This is a singleton that can be accessed using get_instance()
|
||||||
|
|
||||||
|
It has 2 properties
|
||||||
|
- app: Used for WSGI servers and such
|
||||||
|
- api: Used for Blueprints
|
||||||
|
"""
|
||||||
|
|
||||||
|
__instance = None
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(APISettings)
|
||||||
|
app.json.sort_keys = False
|
||||||
|
api = None
|
||||||
|
alive_since = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_instance():
|
||||||
|
if Application.__instance is None:
|
||||||
|
Application()
|
||||||
|
return Application.__instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if Application.__instance is not None:
|
||||||
|
raise IsSingletonException("This class is a singleton")
|
||||||
|
Application.__instance = self
|
||||||
|
self.api = Api(self.app)
|
||||||
|
self.alive_since = datetime.now()
|
1
support_formatter/config/__init__.py
Normal file
1
support_formatter/config/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .settings import APISettings, ServerSettings
|
35
support_formatter/config/settings.py
Normal file
35
support_formatter/config/settings.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .. import __version__
|
||||||
|
|
||||||
|
class ServerSettings:
|
||||||
|
HOSTNAME = os.environ.get("SUPPORT_FORMATTER_HOST", "localhost")
|
||||||
|
PORT = int(os.environ.get("SUPPORT_FORMATTER_PORT", 5000))
|
||||||
|
|
||||||
|
class APISettings:
|
||||||
|
API_TITLE = "Support Organizer"
|
||||||
|
API_VERSION = __version__
|
||||||
|
OPENAPI_VERSION = "3.1.0"
|
||||||
|
|
||||||
|
# openapi.json settings
|
||||||
|
OPENAPI_URL_PREFIX = "/"
|
||||||
|
OPENAPI_JSON_PATH = "openapi.json"
|
||||||
|
|
||||||
|
# swagger settings
|
||||||
|
OPENAPI_SWAGGER_UI_PATH = "/swagger"
|
||||||
|
OPENAPI_SWAGGER_UI_URL = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.10.0/"
|
||||||
|
SWAGGER_UI_DOC_EXPANSION = "list"
|
||||||
|
|
||||||
|
# Info settings
|
||||||
|
API_SPEC_OPTIONS = {
|
||||||
|
'info': {
|
||||||
|
'description': 'Support Organizer for FGO'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PAGES_DIRECTORY = Path(__file__).parents[1] / "pages"
|
||||||
|
|
||||||
|
ALLOWED_EXTENSIONS = { 'csv', 'txt' }
|
||||||
|
FILE_SAVE_DIRECTORY = Path(__file__).parents[1] / ".temp"
|
0
support_formatter/logic/__init__.py
Normal file
0
support_formatter/logic/__init__.py
Normal file
44
support_formatter/logic/csv_processor.py
Normal file
44
support_formatter/logic/csv_processor.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import csv
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
class FileTypeInvalidError(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def determine_type(row):
|
||||||
|
if set(row) == set(["servant", "level", "np_level", "skills", "bce"]):
|
||||||
|
return "servants"
|
||||||
|
if set(row) == set(["ce", "ce_level", "mlb"]):
|
||||||
|
return "ces"
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
def process_csv(path: pathlib.Path):
|
||||||
|
data, entries = {}, []
|
||||||
|
|
||||||
|
with open(path, "r") as csv_file:
|
||||||
|
csv_reader = csv.DictReader(csv_file)
|
||||||
|
csv_type = determine_type(csv_reader.fieldnames)
|
||||||
|
|
||||||
|
if csv_type == "unknown":
|
||||||
|
raise FileTypeInvalidError()
|
||||||
|
|
||||||
|
for row in csv_reader:
|
||||||
|
entry = row.copy()
|
||||||
|
if csv_type == "servants":
|
||||||
|
entry.update({
|
||||||
|
"level": int(entry['level']),
|
||||||
|
"np_level": int(entry['np_level']),
|
||||||
|
"bce": bool(entry["bce"])
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
entry.update({
|
||||||
|
"ce_level": int(entry['ce_level']),
|
||||||
|
"mlb": bool(entry["mlb"])
|
||||||
|
})
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
if csv_type == "servants":
|
||||||
|
data = { "support_list": entries }
|
||||||
|
else:
|
||||||
|
data = { "ce_list": entries }
|
||||||
|
|
||||||
|
return data
|
0
support_formatter/models/__init__.py
Normal file
0
support_formatter/models/__init__.py
Normal file
19
support_formatter/models/interface.py
Normal file
19
support_formatter/models/interface.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from enum import Enum
|
||||||
|
import marshmallow as ma
|
||||||
|
|
||||||
|
class HealthStatus(Enum):
|
||||||
|
OK = 0
|
||||||
|
WARNING = 1
|
||||||
|
ERROR = 2
|
||||||
|
CRITICAL = 3
|
||||||
|
|
||||||
|
class HealthGet(ma.Schema):
|
||||||
|
alive_since = ma.fields.String()
|
||||||
|
alive_for = ma.fields.String()
|
||||||
|
status = ma.fields.Enum(HealthStatus, type=ma.fields.String)
|
||||||
|
|
||||||
|
class ApiVersionGet(ma.Schema):
|
||||||
|
version = ma.fields.String(example="0.1")
|
||||||
|
|
||||||
|
class OpenAPIGet(ma.Schema):
|
||||||
|
pass
|
14
support_formatter/pages/error.html
Normal file
14
support_formatter/pages/error.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Support File Converter - Error</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Error</h1>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
34
support_formatter/pages/index.html
Normal file
34
support_formatter/pages/index.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Support File Converter</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Convert to Nerone-compatible data</h1>
|
||||||
|
<form method=post enctype=multipart/form-data>
|
||||||
|
<label for="fusername">Username:</label>
|
||||||
|
<input type="text" id="fusername" name="username" placeholder="Your username">
|
||||||
|
<br />
|
||||||
|
<label for="ffriendcode">Friendcode:</label>
|
||||||
|
<input type="text" id="ffriendcode" name="friendcode" placeholder="000,000,000">
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<label for="fservantdata">Servant Data</label>
|
||||||
|
<br />
|
||||||
|
<input type="file" id="fservantdata" name="servantdata">
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<label for="fcedata">CE Data (optional)</label>
|
||||||
|
<br />
|
||||||
|
<input type="file" id="fcedata" name="cedata">
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<input type="submit" value="Upload">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
7
support_formatter/routes/__init__.py
Normal file
7
support_formatter/routes/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# pylint: disable=wrong-import-position,cyclic-import
|
||||||
|
from flask_smorest import Blueprint
|
||||||
|
|
||||||
|
interface_routes = Blueprint("interface", "interface", url_prefix="/", description="")
|
||||||
|
formatter_routes = Blueprint("formatter", "formatter", url_prefix="/", description="")
|
||||||
|
|
||||||
|
from . import interface, upload # avoids circular imports problem
|
38
support_formatter/routes/interface.py
Normal file
38
support_formatter/routes/interface.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from flask import redirect, url_for
|
||||||
|
from flask.views import MethodView
|
||||||
|
|
||||||
|
from ..app import Application
|
||||||
|
from ..config import APISettings
|
||||||
|
from ..models.interface import ApiVersionGet, HealthGet, HealthStatus, OpenAPIGet
|
||||||
|
|
||||||
|
from . import interface_routes as blp
|
||||||
|
|
||||||
|
APP = Application.get_instance()
|
||||||
|
|
||||||
|
@blp.route("/health")
|
||||||
|
class ApiVersion(MethodView):
|
||||||
|
@blp.doc(summary="Returns the status and alive-time of the server")
|
||||||
|
@blp.response(200, HealthGet, description="Successful operation")
|
||||||
|
def get(self):
|
||||||
|
response = {
|
||||||
|
"alive_since": datetime.strftime(APP.alive_since, "%d.%m.%Y %H:%M:%S"),
|
||||||
|
"alive_for": str(datetime.now() - APP.alive_since),
|
||||||
|
"status": HealthStatus.OK
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
|
||||||
|
@blp.route("/openapi")
|
||||||
|
class ApiVersion(MethodView):
|
||||||
|
@blp.doc(summary="Get the OpenAPI spec as a JSON.")
|
||||||
|
@blp.response(200, OpenAPIGet, description="Successful operation")
|
||||||
|
def get(self):
|
||||||
|
return redirect(url_for('api-docs.openapi_json'))
|
||||||
|
|
||||||
|
@blp.route("/version")
|
||||||
|
class ApiVersion(MethodView):
|
||||||
|
@blp.doc(summary="Get the REST interface version identification.")
|
||||||
|
@blp.response(200, ApiVersionGet, description="Successful operation")
|
||||||
|
def get(self):
|
||||||
|
return { "version": APISettings.API_VERSION }
|
70
support_formatter/routes/upload.py
Normal file
70
support_formatter/routes/upload.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
from typing import List
|
||||||
|
from flask import send_from_directory
|
||||||
|
import marshmallow as ma
|
||||||
|
from flask_smorest.fields import Upload
|
||||||
|
from werkzeug.datastructures import FileStorage
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from ..app import Application
|
||||||
|
from ..config import APISettings
|
||||||
|
from ..logic.csv_processor import process_csv, FileTypeInvalidError
|
||||||
|
from . import formatter_routes as blp
|
||||||
|
|
||||||
|
APP = Application.get_instance()
|
||||||
|
|
||||||
|
def allowed_file(filename):
|
||||||
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in APISettings.ALLOWED_EXTENSIONS
|
||||||
|
|
||||||
|
class MultipartFormSchema(ma.Schema):
|
||||||
|
username = ma.fields.String(required=True)
|
||||||
|
friendcode = ma.fields.String(required=False)
|
||||||
|
|
||||||
|
class MultipartFileSchema(ma.Schema):
|
||||||
|
servantdata = Upload()
|
||||||
|
cedata = Upload()
|
||||||
|
|
||||||
|
@blp.route("/", methods=["POST"])
|
||||||
|
@blp.arguments(MultipartFormSchema, location="form")
|
||||||
|
@blp.arguments(MultipartFileSchema, location="files")
|
||||||
|
@blp.response(200)
|
||||||
|
def upload_file(form: dict[str, str], files: dict[str, FileStorage]):
|
||||||
|
filepaths: List[pathlib.Path] = []
|
||||||
|
returndata = {
|
||||||
|
"username": form["username"],
|
||||||
|
"friendcode": form.get("friendcode", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, file in files.items():
|
||||||
|
if name not in ("servantdata", "cedata"):
|
||||||
|
return send_from_directory(APISettings.PAGES_DIRECTORY, "error.html")
|
||||||
|
|
||||||
|
filepath = APISettings.FILE_SAVE_DIRECTORY / f"{uuid.uuid4()}.csv"
|
||||||
|
file.save(filepath)
|
||||||
|
|
||||||
|
if os.stat(filepath).st_size < 1 or not allowed_file(file.filename):
|
||||||
|
filepath.unlink()
|
||||||
|
continue
|
||||||
|
|
||||||
|
filepaths.append(filepath)
|
||||||
|
|
||||||
|
if len(filepaths) == 0:
|
||||||
|
return send_from_directory(APISettings.PAGES_DIRECTORY, "error.html")
|
||||||
|
|
||||||
|
try:
|
||||||
|
for f in filepaths:
|
||||||
|
result = process_csv(f)
|
||||||
|
returndata = returndata | result
|
||||||
|
f.unlink()
|
||||||
|
except FileTypeInvalidError:
|
||||||
|
for f in filepaths:
|
||||||
|
f.unlink()
|
||||||
|
return send_from_directory(APISettings.PAGES_DIRECTORY, "error.html")
|
||||||
|
|
||||||
|
return returndata
|
||||||
|
|
||||||
|
@blp.route("/", methods=["GET"])
|
||||||
|
def file_dialog():
|
||||||
|
return send_from_directory(APISettings.PAGES_DIRECTORY, "index.html")
|
28
support_formatter/server.py
Normal file
28
support_formatter/server.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# pylint: disable=multiple-statements,wrong-import-position,wrong-import-order
|
||||||
|
from gevent.monkey import patch_all; patch_all()
|
||||||
|
from gevent.pywsgi import WSGIServer
|
||||||
|
|
||||||
|
from .app import Application
|
||||||
|
from .routes import interface_routes, formatter_routes
|
||||||
|
from .config import APISettings, ServerSettings
|
||||||
|
|
||||||
|
APISettings.FILE_SAVE_DIRECTORY.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
APP = Application.get_instance()
|
||||||
|
app, api = APP.app, APP.api
|
||||||
|
api.register_blueprint(interface_routes)
|
||||||
|
api.register_blueprint(formatter_routes)
|
||||||
|
|
||||||
|
HOSTNAME, PORT = ServerSettings.HOSTNAME, ServerSettings.PORT
|
||||||
|
|
||||||
|
def run_server():
|
||||||
|
http_Server = WSGIServer((HOSTNAME, PORT), app)
|
||||||
|
try:
|
||||||
|
print(f"Server available on http://{HOSTNAME}:{PORT}/")
|
||||||
|
print(f"View docs on http://{HOSTNAME}:{PORT}/swagger")
|
||||||
|
http_Server.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Keyboard interrupt received, stopping...")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_server()
|
Loading…
Reference in a new issue