Compare commits
No commits in common. "main" and "0.1.0-a.2" have entirely different histories.
13 changed files with 71 additions and 57 deletions
|
@ -1,6 +1,8 @@
|
|||
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:
|
||||
|
@ -53,7 +55,7 @@ jobs:
|
|||
build-args: |
|
||||
PACKAGE_VERSION=${{ github.ref_name }}
|
||||
push: true
|
||||
tags: forgejo.neshweb.net/firq/support-formatter:${{ github.ref_name }}, forgejo.neshweb.net/firq/support-formatter:latest
|
||||
tags: forgejo.neshweb.net/firq/support-formatter:${{ github.ref_name }}
|
||||
|
||||
release:
|
||||
needs: [ build-and-push-container, publish-artifacts ]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "support_formatter"
|
||||
version = "0.2.1"
|
||||
version = "0.1.0-a.2"
|
||||
requires-python = ">= 3.10"
|
||||
authors = [{name = "Firq", email = "firelp42@gmail.com"}]
|
||||
maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]
|
||||
|
|
|
@ -33,5 +33,3 @@ class APISettings:
|
|||
|
||||
ALLOWED_EXTENSIONS = { 'csv', 'txt' }
|
||||
FILE_SAVE_DIRECTORY = Path(__file__).parents[1] / ".temp"
|
||||
|
||||
UI_URL = "https://support-formatter.firq.dev/"
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import csv
|
||||
import pathlib
|
||||
|
||||
from ..utils import convert_to_bool
|
||||
|
||||
|
||||
class FileTypeInvalidError(ValueError):
|
||||
pass
|
||||
|
||||
|
@ -33,7 +30,7 @@ def process_csv(path: pathlib.Path):
|
|||
entry.update({
|
||||
"level": int(entry['level']),
|
||||
"np_level": int(entry['np_level']),
|
||||
"bce": convert_to_bool(entry["bce"])
|
||||
"bce": bool(entry["bce"])
|
||||
})
|
||||
else:
|
||||
entry.update({
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from enum import Enum
|
||||
|
||||
import marshmallow as ma
|
||||
|
||||
|
||||
class HealthStatus(Enum):
|
||||
OK = 0
|
||||
WARNING = 1
|
||||
|
|
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>
|
|
@ -1,8 +1,7 @@
|
|||
# pylint: disable=wrong-import-position,cyclic-import
|
||||
from flask_smorest import Blueprint
|
||||
|
||||
redirect_routes = Blueprint("redirect", "recirects", url_prefix="/", description="")
|
||||
interface_routes = Blueprint("interface", "interface", url_prefix="/", description="")
|
||||
formatter_routes = Blueprint("formatter", "formatter", url_prefix="/", description="")
|
||||
|
||||
from . import interface, upload, redirects # avoids circular imports problem
|
||||
from . import interface, upload # avoids circular imports problem
|
||||
|
|
|
@ -6,6 +6,7 @@ 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()
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
from flask import redirect
|
||||
|
||||
from ..app import Application
|
||||
from ..config import APISettings
|
||||
from . import redirect_routes as blp
|
||||
|
||||
APP = Application.get_instance()
|
||||
|
||||
@blp.route("/", methods=["GET"])
|
||||
@blp.doc(summary="This route just exists to redirect any visitor to the root domain to the UI")
|
||||
def redirect_to_ui():
|
||||
return redirect(APISettings.UI_URL)
|
|
@ -1,16 +1,16 @@
|
|||
import os
|
||||
import pathlib
|
||||
import uuid
|
||||
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 FileTypeInvalidError, process_csv
|
||||
from ..utils import generate_error
|
||||
from ..logic.csv_processor import process_csv, FileTypeInvalidError
|
||||
from . import formatter_routes as blp
|
||||
|
||||
APP = Application.get_instance()
|
||||
|
@ -26,7 +26,7 @@ class MultipartFileSchema(ma.Schema):
|
|||
servantdata = Upload()
|
||||
cedata = Upload()
|
||||
|
||||
@blp.route("/upload", methods=["POST"])
|
||||
@blp.route("/", methods=["POST"])
|
||||
@blp.arguments(MultipartFormSchema, location="form")
|
||||
@blp.arguments(MultipartFileSchema, location="files")
|
||||
@blp.response(200)
|
||||
|
@ -39,28 +39,32 @@ def upload_file(form: dict[str, str], files: dict[str, FileStorage]):
|
|||
|
||||
for name, file in files.items():
|
||||
if name not in ("servantdata", "cedata"):
|
||||
return generate_error(406, message="Invalid form sent")
|
||||
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(missing_ok=True)
|
||||
filepath.unlink()
|
||||
continue
|
||||
|
||||
filepaths.append(filepath)
|
||||
|
||||
if len(filepaths) == 0:
|
||||
return generate_error(406, message="No files provided")
|
||||
return send_from_directory(APISettings.PAGES_DIRECTORY, "error.html")
|
||||
|
||||
try:
|
||||
for f in filepaths:
|
||||
result = process_csv(f)
|
||||
returndata = returndata | result
|
||||
f.unlink(missing_ok=True)
|
||||
f.unlink()
|
||||
except FileTypeInvalidError:
|
||||
for f in filepaths:
|
||||
f.unlink(missing_ok=True)
|
||||
return generate_error(500, message="Error whilst parsing uploaded file - please contact Firq on Fate/Sacc Order")
|
||||
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")
|
||||
|
|
|
@ -3,15 +3,13 @@ from gevent.monkey import patch_all; patch_all()
|
|||
from gevent.pywsgi import WSGIServer
|
||||
|
||||
from .app import Application
|
||||
from .routes import interface_routes, formatter_routes, redirect_routes
|
||||
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(redirect_routes)
|
||||
api.register_blueprint(interface_routes)
|
||||
api.register_blueprint(formatter_routes)
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
from typing import Any, Optional, Tuple, TypedDict
|
||||
|
||||
|
||||
class ErrorReturn(TypedDict):
|
||||
status: int
|
||||
message: Optional[str]
|
||||
details: Optional[dict[str, Any]]
|
||||
|
||||
|
||||
def generate_error(status: int, message: Optional[str]=None, additional_data: Optional[dict[str, Any]]=None) -> Tuple[ErrorReturn, int]:
|
||||
obj: ErrorReturn = { "status": status }
|
||||
if message is not None:
|
||||
obj |= { "message": message }
|
||||
if additional_data is not None:
|
||||
obj |= { "details": additional_data }
|
||||
return obj, status
|
||||
|
||||
def convert_to_bool(val: str) -> bool:
|
||||
return bool(val) if str(val).lower() not in ("null", "none") else False
|
Loading…
Reference in a new issue