|
@@ -1,13 +1,16 @@
|
|
|
-from flask import Flask, render_template, abort, send_file, jsonify
|
|
|
-import os
|
|
|
-import json
|
|
|
-from typing import List
|
|
|
+from pathlib import Path
|
|
|
+from flask import Flask, Response, render_template, abort, send_file, jsonify
|
|
|
import markdown as md
|
|
|
from dataclasses import dataclass
|
|
|
from dataclasses_json import DataClassJsonMixin
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
+BUILDS_PATH = Path("../builds")
|
|
|
+ARTIFACTS_FOLDER = "artifacts"
|
|
|
+INFO_FILE = "info.json"
|
|
|
+
|
|
|
+
|
|
|
@dataclass
|
|
|
class ArtifactItem(DataClassJsonMixin):
|
|
|
file: str
|
|
@@ -19,9 +22,9 @@ class Artifact(DataClassJsonMixin):
|
|
|
id: str
|
|
|
date: str
|
|
|
changelog: str
|
|
|
- artifacts: List[ArtifactItem]
|
|
|
- hash: str = None
|
|
|
- short_hash: str = None
|
|
|
+ artifacts: list[ArtifactItem]
|
|
|
+ hash: str | None = None
|
|
|
+ short_hash: str | None = None
|
|
|
|
|
|
|
|
|
@dataclass
|
|
@@ -30,50 +33,47 @@ class ProjectInfo(DataClassJsonMixin):
|
|
|
commit_url: str
|
|
|
|
|
|
|
|
|
+@dataclass
|
|
|
class Project:
|
|
|
id: str
|
|
|
info: ProjectInfo
|
|
|
|
|
|
+ def _get_artifact_paths(self) -> list[Path]:
|
|
|
+ artifacts_path = BUILDS_PATH / self.id / ARTIFACTS_FOLDER
|
|
|
+ if not artifacts_path.exists():
|
|
|
+ return []
|
|
|
|
|
|
- def get_artifacts(self) -> List[Artifact]:
|
|
|
- result = []
|
|
|
- artifacts_path = os.path.join("../builds", self.id, "artifacts")
|
|
|
- artifact_folders = sorted([folder.path for folder in os.scandir(
|
|
|
- artifacts_path) if folder.is_dir()], reverse=True)
|
|
|
+ return sorted(
|
|
|
+ [p for p in artifacts_path.iterdir() if p.is_dir()],
|
|
|
+ reverse=True,
|
|
|
+ )
|
|
|
|
|
|
- for artifact_folder in artifact_folders:
|
|
|
- info_file_path = os.path.join(artifact_folder, "info.json")
|
|
|
+ def get_artifacts(self) -> list[Artifact]:
|
|
|
+ result = []
|
|
|
+ for artifact_folder in self._get_artifact_paths():
|
|
|
+ info_file_path = artifact_folder / INFO_FILE
|
|
|
artifact = self._parse_artifact(info_file_path)
|
|
|
if artifact:
|
|
|
result.append(artifact)
|
|
|
return result
|
|
|
|
|
|
-
|
|
|
- def get_lastest_artifact(self) -> Artifact:
|
|
|
- artifacts_path = os.path.join("../builds", self.id, "artifacts")
|
|
|
- artifact_folders = sorted([folder.path for folder in os.scandir(
|
|
|
- artifacts_path) if folder.is_dir()], reverse=True)
|
|
|
- if not artifact_folders:
|
|
|
+ def get_lastest_artifact(self) -> Artifact | None:
|
|
|
+ latest_artifact_folder = max(self._get_artifact_paths(), default=None)
|
|
|
+ if not latest_artifact_folder:
|
|
|
return None
|
|
|
-
|
|
|
- latest_artifact_folder = max(artifact_folders)
|
|
|
- info_file_path = os.path.join(latest_artifact_folder, "info.json")
|
|
|
- return self._parse_artifact(info_file_path)
|
|
|
-
|
|
|
+ return self._parse_artifact(latest_artifact_folder / INFO_FILE)
|
|
|
|
|
|
- def get_artifact(self, artifact_id) -> Artifact:
|
|
|
- artifact_folder = os.path.join("../builds", self.id, "artifacts", artifact_id)
|
|
|
- if not os.path.isdir(artifact_folder):
|
|
|
+ def get_artifact(self, artifact_id: str) -> Artifact | None:
|
|
|
+ artifact_folder = BUILDS_PATH / self.id / ARTIFACTS_FOLDER / artifact_id
|
|
|
+ if not artifact_folder.is_dir():
|
|
|
return None
|
|
|
- info_file_path = os.path.join(artifact_folder, "info.json")
|
|
|
- return self._parse_artifact(info_file_path)
|
|
|
-
|
|
|
+ return self._parse_artifact(artifact_folder / INFO_FILE)
|
|
|
|
|
|
- def _parse_artifact(self, info_file_path):
|
|
|
- if not os.path.exists(info_file_path):
|
|
|
+ def _parse_artifact(self, info_file_path: Path) -> Artifact | None:
|
|
|
+ if not info_file_path.exists():
|
|
|
return None
|
|
|
try:
|
|
|
- with open(info_file_path, "r", encoding="utf-8") as f:
|
|
|
+ with info_file_path.open("r", encoding="utf-8") as f:
|
|
|
artifact = Artifact.from_json(f.read())
|
|
|
artifact.date = artifact.date.strip()
|
|
|
return artifact
|
|
@@ -81,93 +81,102 @@ class Project:
|
|
|
return None
|
|
|
|
|
|
|
|
|
-def get_projects() -> List[Project]:
|
|
|
+def get_projects() -> list[Project]:
|
|
|
result = []
|
|
|
- projects = [f.path for f in os.scandir("../builds") if f.is_dir()]
|
|
|
+ projects = [p for p in BUILDS_PATH.iterdir() if p.is_dir()]
|
|
|
for project in projects:
|
|
|
- info_path = os.path.join(project, "info.json")
|
|
|
- if not os.path.exists(info_path):
|
|
|
+ info_path = project / INFO_FILE
|
|
|
+ if not info_path.exists():
|
|
|
continue
|
|
|
try:
|
|
|
- proj = Project()
|
|
|
- _, proj.id = os.path.split(project)
|
|
|
- with open(info_path, "r", encoding="utf-8") as f:
|
|
|
- proj.info = ProjectInfo.from_json(f.read())
|
|
|
- result.append(proj)
|
|
|
+ *_, proj_id = project.parts
|
|
|
+ with info_path.open("r", encoding="utf-8") as f:
|
|
|
+ proj_info = ProjectInfo.from_json(f.read())
|
|
|
+ result.append(Project(proj_id, proj_info))
|
|
|
except:
|
|
|
continue
|
|
|
return result
|
|
|
|
|
|
|
|
|
-@app.route("/api/projects/<string:project_id>/artifacts/latest")
|
|
|
-def get_latest_artifact(project_id):
|
|
|
+@app.get("/api/projects/<string:project_id>/artifacts/latest")
|
|
|
+def get_latest_artifact(project_id: str) -> Response:
|
|
|
projects = get_projects()
|
|
|
|
|
|
selected_project = next(
|
|
|
- (project for project in projects if project.id == project_id), None)
|
|
|
+ (project for project in projects if project.id == project_id), None
|
|
|
+ )
|
|
|
if not selected_project:
|
|
|
return abort(404)
|
|
|
|
|
|
artifact = selected_project.get_lastest_artifact()
|
|
|
if not artifact:
|
|
|
return abort(404)
|
|
|
-
|
|
|
+
|
|
|
return jsonify(artifact)
|
|
|
|
|
|
|
|
|
-@app.route("/api/projects/<string:project_id>/artifacts/<string:artifact_id>")
|
|
|
-def get_artifact_by_id(project_id, artifact_id):
|
|
|
+@app.get("/api/projects/<string:project_id>/artifacts/<string:artifact_id>")
|
|
|
+def get_artifact_by_id(project_id: str, artifact_id: str) -> Response:
|
|
|
projects = get_projects()
|
|
|
|
|
|
selected_project = next(
|
|
|
- (project for project in projects if project.id == project_id), None)
|
|
|
+ (project for project in projects if project.id == project_id), None
|
|
|
+ )
|
|
|
if not selected_project:
|
|
|
return abort(404)
|
|
|
|
|
|
artifact = selected_project.get_artifact(artifact_id)
|
|
|
if not artifact:
|
|
|
return abort(404)
|
|
|
-
|
|
|
+
|
|
|
return jsonify(artifact)
|
|
|
|
|
|
|
|
|
-@app.route("/projects/<string:project_id>/<string:artifact_id>/<string:download_item>")
|
|
|
-def download_item(project_id, artifact_id, download_item):
|
|
|
- file_path = os.path.join("../builds", project_id,
|
|
|
- "artifacts", artifact_id, download_item)
|
|
|
- if not os.path.exists(file_path):
|
|
|
+@app.get("/projects/<string:project_id>/<string:artifact_id>/<string:download_item>")
|
|
|
+def download_item(project_id: str, artifact_id: str, download_item: str) -> Response:
|
|
|
+ file_path = (
|
|
|
+ BUILDS_PATH / project_id / ARTIFACTS_FOLDER / artifact_id / download_item
|
|
|
+ )
|
|
|
+ if not file_path.exists():
|
|
|
return abort(404)
|
|
|
return send_file(file_path)
|
|
|
|
|
|
|
|
|
-@app.route("/projects/<string:project_id>")
|
|
|
-def display_project(project_id):
|
|
|
+@app.get("/projects/<string:project_id>")
|
|
|
+def display_project(project_id: str) -> Response | str:
|
|
|
projects = get_projects()
|
|
|
|
|
|
selected_project = next(
|
|
|
- (project for project in projects if project.id == project_id), None)
|
|
|
+ (project for project in projects if project.id == project_id), None
|
|
|
+ )
|
|
|
if not selected_project:
|
|
|
return abort(404)
|
|
|
|
|
|
- info_path = os.path.join("../builds", selected_project.id, "info.md")
|
|
|
+ info_path = BUILDS_PATH / selected_project.id / "info.md"
|
|
|
readme = None
|
|
|
try:
|
|
|
- if os.path.exists(info_path):
|
|
|
- with open(info_path, "r", encoding="utf-8") as f:
|
|
|
+ if info_path.exists():
|
|
|
+ with info_path.open("r", encoding="utf-8") as f:
|
|
|
readme = md.markdown(f.read())
|
|
|
except:
|
|
|
readme = None
|
|
|
|
|
|
artifacts = selected_project.get_artifacts()
|
|
|
|
|
|
- return render_template("project_view.html", projects=projects, selected_project=selected_project, readme=readme, artifacts=artifacts)
|
|
|
+ return render_template(
|
|
|
+ "project_view.html",
|
|
|
+ projects=projects,
|
|
|
+ selected_project=selected_project,
|
|
|
+ readme=readme,
|
|
|
+ artifacts=artifacts,
|
|
|
+ )
|
|
|
|
|
|
|
|
|
-@app.route("/")
|
|
|
-def index():
|
|
|
+@app.get("/")
|
|
|
+def index() -> Response | str:
|
|
|
projects = get_projects()
|
|
|
return render_template("main.html", projects=projects)
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
- app.run(host='0.0.0.0')
|
|
|
+ app.run(host="0.0.0.0")
|