Compare commits

...

5 commits
0.1.0 ... main

Author SHA1 Message Date
98b2ec10fe Bumped ver
All checks were successful
/ backend-pylint (push) Successful in 45s
/ build-and-push-container (push) Successful in 2m23s
/ build-artifacts (push) Successful in 24s
/ publish-artifacts (push) Successful in 24s
/ release (push) Successful in 29s
2025-02-04 15:42:10 +00:00
662d5dd3d6 Fixed bool error
All checks were successful
/ backend-pylint (push) Successful in 23s
2025-02-04 15:41:45 +00:00
1d3e7557b1
new ver
Some checks failed
/ build-artifacts (push) Successful in 11s
/ publish-artifacts (push) Successful in 10s
/ build-and-push-container (push) Successful in 3m16s
/ release (push) Successful in 9s
/ backend-pylint (push) Failing after 12m1s
2025-01-20 13:56:22 +01:00
8d2de1766b
Utils and better handling
Some checks failed
/ backend-pylint (push) Successful in 1m30s
/ build-artifacts (push) Successful in 16s
/ publish-artifacts (push) Successful in 1m7s
/ release (push) Failing after 22m48s
/ build-and-push-container (push) Successful in 3m14s
2024-10-04 15:03:44 +02:00
1eafc1a0e1
Switched API
All checks were successful
/ backend-pylint (push) Successful in 1m5s
/ build-artifacts (push) Successful in 13s
/ publish-artifacts (push) Successful in 43s
/ release (push) Successful in 2m54s
/ build-and-push-container (push) Successful in 2m10s
2024-10-04 00:31:31 +02:00
11 changed files with 56 additions and 17 deletions

View file

@ -1,8 +1,6 @@
on: on:
push: push:
tags: 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]' - '[0-9]+\.[0-9]+\.[0-9]'
jobs: jobs:
@ -55,7 +53,7 @@ jobs:
build-args: | build-args: |
PACKAGE_VERSION=${{ github.ref_name }} PACKAGE_VERSION=${{ github.ref_name }}
push: true push: true
tags: forgejo.neshweb.net/firq/support-formatter:${{ github.ref_name }} tags: forgejo.neshweb.net/firq/support-formatter:${{ github.ref_name }}, forgejo.neshweb.net/firq/support-formatter:latest
release: release:
needs: [ build-and-push-container, publish-artifacts ] needs: [ build-and-push-container, publish-artifacts ]

View file

@ -1,6 +1,6 @@
[project] [project]
name = "support_formatter" name = "support_formatter"
version = "0.1.0" version = "0.2.3"
requires-python = ">= 3.10" requires-python = ">= 3.10"
authors = [{name = "Firq", email = "firelp42@gmail.com"}] authors = [{name = "Firq", email = "firelp42@gmail.com"}]
maintainers = [{name = "Firq", email = "firelp42@gmail.com"}] maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]

View file

@ -33,3 +33,5 @@ class APISettings:
ALLOWED_EXTENSIONS = { 'csv', 'txt' } ALLOWED_EXTENSIONS = { 'csv', 'txt' }
FILE_SAVE_DIRECTORY = Path(__file__).parents[1] / ".temp" FILE_SAVE_DIRECTORY = Path(__file__).parents[1] / ".temp"
UI_URL = "https://support-formatter.firq.dev/"

View file

@ -1,6 +1,9 @@
import csv import csv
import pathlib import pathlib
from ..utils import convert_to_bool
class FileTypeInvalidError(ValueError): class FileTypeInvalidError(ValueError):
pass pass
@ -30,7 +33,7 @@ def process_csv(path: pathlib.Path):
entry.update({ entry.update({
"level": int(entry['level']), "level": int(entry['level']),
"np_level": int(entry['np_level']), "np_level": int(entry['np_level']),
"bce": bool(entry["bce"]) "bce": convert_to_bool(entry["bce"])
}) })
else: else:
entry.update({ entry.update({

View file

@ -1,6 +1,8 @@
from enum import Enum from enum import Enum
import marshmallow as ma import marshmallow as ma
class HealthStatus(Enum): class HealthStatus(Enum):
OK = 0 OK = 0
WARNING = 1 WARNING = 1

View file

@ -1,7 +1,8 @@
# pylint: disable=wrong-import-position,cyclic-import # pylint: disable=wrong-import-position,cyclic-import
from flask_smorest import Blueprint from flask_smorest import Blueprint
redirect_routes = Blueprint("redirect", "recirects", url_prefix="/", description="")
interface_routes = Blueprint("interface", "interface", url_prefix="/", description="") interface_routes = Blueprint("interface", "interface", url_prefix="/", description="")
formatter_routes = Blueprint("formatter", "formatter", url_prefix="/", description="") formatter_routes = Blueprint("formatter", "formatter", url_prefix="/", description="")
from . import interface, upload # avoids circular imports problem from . import interface, upload, redirects # avoids circular imports problem

View file

@ -6,7 +6,6 @@ from flask.views import MethodView
from ..app import Application from ..app import Application
from ..config import APISettings from ..config import APISettings
from ..models.interface import ApiVersionGet, HealthGet, HealthStatus, OpenAPIGet from ..models.interface import ApiVersionGet, HealthGet, HealthStatus, OpenAPIGet
from . import interface_routes as blp from . import interface_routes as blp
APP = Application.get_instance() APP = Application.get_instance()

View file

@ -0,0 +1,12 @@
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)

View file

@ -1,15 +1,16 @@
import os import os
import pathlib import pathlib
import uuid
from typing import List from typing import List
import marshmallow as ma import marshmallow as ma
from flask_smorest.fields import Upload from flask_smorest.fields import Upload
from werkzeug.datastructures import FileStorage from werkzeug.datastructures import FileStorage
import uuid
from ..app import Application from ..app import Application
from ..config import APISettings from ..config import APISettings
from ..logic.csv_processor import process_csv, FileTypeInvalidError from ..logic.csv_processor import FileTypeInvalidError, process_csv
from ..utils import generate_error
from . import formatter_routes as blp from . import formatter_routes as blp
APP = Application.get_instance() APP = Application.get_instance()
@ -38,28 +39,28 @@ def upload_file(form: dict[str, str], files: dict[str, FileStorage]):
for name, file in files.items(): for name, file in files.items():
if name not in ("servantdata", "cedata"): if name not in ("servantdata", "cedata"):
return { "status": 406, "message": "Invalid form sent" }, 406 return generate_error(406, message="Invalid form sent")
filepath = APISettings.FILE_SAVE_DIRECTORY / f"{uuid.uuid4()}.csv" filepath = APISettings.FILE_SAVE_DIRECTORY / f"{uuid.uuid4()}.csv"
file.save(filepath) file.save(filepath)
if os.stat(filepath).st_size < 1 or not allowed_file(file.filename): if os.stat(filepath).st_size < 1 or not allowed_file(file.filename):
filepath.unlink() filepath.unlink(missing_ok=True)
continue continue
filepaths.append(filepath) filepaths.append(filepath)
if len(filepaths) == 0: if len(filepaths) == 0:
return { "status": 406, "message": "No files provided" }, 406 return generate_error(406, message="No files provided")
try: try:
for f in filepaths: for f in filepaths:
result = process_csv(f) result = process_csv(f)
returndata = returndata | result returndata = returndata | result
f.unlink() f.unlink(missing_ok=True)
except FileTypeInvalidError: except FileTypeInvalidError:
for f in filepaths: for f in filepaths:
f.unlink() f.unlink(missing_ok=True)
return { "status": 500, "message": "Error whilst parsing uploaded file - please contact Firq on Fate/Sacc Order" }, 406 return generate_error(500, message="Error whilst parsing uploaded file - please contact Firq on Fate/Sacc Order")
return returndata return returndata

View file

@ -3,13 +3,15 @@ from gevent.monkey import patch_all; patch_all()
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
from .app import Application from .app import Application
from .routes import interface_routes, formatter_routes from .routes import interface_routes, formatter_routes, redirect_routes
from .config import APISettings, ServerSettings from .config import APISettings, ServerSettings
APISettings.FILE_SAVE_DIRECTORY.mkdir(parents=True, exist_ok=True) APISettings.FILE_SAVE_DIRECTORY.mkdir(parents=True, exist_ok=True)
APP = Application.get_instance() APP = Application.get_instance()
app, api = APP.app, APP.api app, api = APP.app, APP.api
api.register_blueprint(redirect_routes)
api.register_blueprint(interface_routes) api.register_blueprint(interface_routes)
api.register_blueprint(formatter_routes) api.register_blueprint(formatter_routes)

View file

@ -0,0 +1,19 @@
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 str(val).lower() in ("1", "true")