|  | @@ -1,4 +1,4 @@
 | 
	
		
			
				|  |  | -from flask import Flask, render_template, abort
 | 
	
		
			
				|  |  | +from flask import Flask, render_template, abort, send_file
 | 
	
		
			
				|  |  |  from flask_assets import Environment, Bundle
 | 
	
		
			
				|  |  |  import os
 | 
	
		
			
				|  |  |  import json
 | 
	
	
		
			
				|  | @@ -18,6 +18,34 @@ assets.config['PYSCSS_ASSETS_ROOT'] = assets.directory
 | 
	
		
			
				|  |  |  assets.register('scss_all', scss)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +class ArtifactItem:
 | 
	
		
			
				|  |  | +    file: str
 | 
	
		
			
				|  |  | +    description: str
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, file, description):
 | 
	
		
			
				|  |  | +        self.file = file
 | 
	
		
			
				|  |  | +        self.description = description
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class Artifact:
 | 
	
		
			
				|  |  | +    id: str
 | 
	
		
			
				|  |  | +    date: str
 | 
	
		
			
				|  |  | +    changelog: str
 | 
	
		
			
				|  |  | +    artifacts: List[ArtifactItem]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, id, date, changelog, artifacts):
 | 
	
		
			
				|  |  | +        self.id = id
 | 
	
		
			
				|  |  | +        self.date = date
 | 
	
		
			
				|  |  | +        self.changelog = changelog
 | 
	
		
			
				|  |  | +        self.artifacts = [ArtifactItem(**item)
 | 
	
		
			
				|  |  | +                          for item in artifacts]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @classmethod
 | 
	
		
			
				|  |  | +    def from_json(cls, json_str):
 | 
	
		
			
				|  |  | +        json_dict = json.loads(json_str)
 | 
	
		
			
				|  |  | +        return cls(**json_dict)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  class ProjectInfo:
 | 
	
		
			
				|  |  |      name: str
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -34,6 +62,24 @@ class Project:
 | 
	
		
			
				|  |  |      id: str
 | 
	
		
			
				|  |  |      info: ProjectInfo
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    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)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for artifact_folder in artifact_folders:
 | 
	
		
			
				|  |  | +            info_file_path = os.path.join(artifact_folder, "info.json")
 | 
	
		
			
				|  |  | +            if not os.path.exists(info_file_path):
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +            try:
 | 
	
		
			
				|  |  | +                with open(info_file_path, "r", encoding="utf-8") as f:
 | 
	
		
			
				|  |  | +                    artifact = Artifact.from_json(f.read())
 | 
	
		
			
				|  |  | +                    result.append(artifact)
 | 
	
		
			
				|  |  | +            except:
 | 
	
		
			
				|  |  | +                continue
 | 
	
		
			
				|  |  | +        return result
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def get_projects() -> List[Project]:
 | 
	
		
			
				|  |  |      result = []
 | 
	
	
		
			
				|  | @@ -53,6 +99,15 @@ def get_projects() -> List[Project]:
 | 
	
		
			
				|  |  |      return result
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +@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):
 | 
	
		
			
				|  |  | +        return abort(404)
 | 
	
		
			
				|  |  | +    return send_file(file_path)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  @app.route("/projects/<string:project_id>")
 | 
	
		
			
				|  |  |  def display_project(project_id):
 | 
	
		
			
				|  |  |      projects = get_projects()
 | 
	
	
		
			
				|  | @@ -63,7 +118,6 @@ def display_project(project_id):
 | 
	
		
			
				|  |  |          return abort(404)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      info_path = os.path.join("../builds", selected_project.id, "info.md")
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      readme = None
 | 
	
		
			
				|  |  |      try:
 | 
	
		
			
				|  |  |          if os.path.exists(info_path):
 | 
	
	
		
			
				|  | @@ -72,7 +126,9 @@ def display_project(project_id):
 | 
	
		
			
				|  |  |      except:
 | 
	
		
			
				|  |  |          readme = None
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    return render_template("project_view.html", projects=projects, selected_project=selected_project, readme=readme)
 | 
	
		
			
				|  |  | +    artifacts = selected_project.get_artifacts()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return render_template("project_view.html", projects=projects, selected_project=selected_project, readme=readme, artifacts=artifacts)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @app.route("/")
 |