badgeteam/Hatchery

View on GitHub
app/Http/Controllers/MchController.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\Badge;
use App\Models\Category;
use App\Models\File;
use App\Models\Project;
use App\Models\Version;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use OpenApi\Annotations as OA;

class MchController extends Controller
{
    /**
     * List the available devices.
     *
     * @OA\Get(
     *   path="/v2/devices",
     *   tags={"MCH2022"},
     * @OA\Response(response="default",ref="#/components/responses/undocumented")
     * )
     *
     * @return JsonResponse
     */
    public function devices(): JsonResponse
    {
        $devices = [];
        foreach (Badge::pluck('name', 'slug') as $slug => $name) {
            $devices[] = [
                'slug' => $slug,
                'name' => $name
            ];
        }
        return response()->json(
            $devices,
            200,
            ['Content-Type' => 'application/json'],
            JSON_UNESCAPED_SLASHES
        );
    }

    /**
     * Get the types of apps a device supports.
     *
     * @OA\Get(
     *   path="/v2/{device}/types",
     * @OA\Parameter(
     *     name="device",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="mch2022")
     *   ),
     *   tags={"MCH2022"},
     * @OA\Response(response="default",ref="#/components/responses/undocumented")
     * )
     *
     * @param string $device
     * @return JsonResponse
     */
    public function types(string $device): JsonResponse
    {
        /** @var Badge $badge */
        $badge = Badge::whereSlug($device)->firstOrFail();
        return response()->json($badge->types, 200, ['Content-Type' => 'application/json'], JSON_UNESCAPED_SLASHES);
    }

    /**
     * Get the types of apps a device supports.
     *
     * @OA\Get(
     *   path="/v2/{device}/{type}/categories",
     * @OA\Parameter(
     *     name="device",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="mch2022")
     *   ),
     * @OA\Parameter(
     *     name="type",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="esp32")
     *   ),
     *   tags={"MCH2022"},
     * @OA\Response(response="default",ref="#/components/responses/undocumented")
     * )
     *
     * @param string $device
     * @param string $type
     * @return JsonResponse
     */
    public function categories(string $device, string $type): JsonResponse
    {
        /** @var Badge $badge */
        $badge = Badge::whereSlug($device)->firstOrFail();

        $count = $categories = [];
        /** @var Project $project */
        foreach ($badge->projects()->whereProjectType($type)->get() as $project) {
            $count[$project->category_id] =
                isset($count[$project->category_id]) ? $count[$project->category_id] + 1 : 1;
        }
        foreach ($count as $id => $apps) {
            /** @var Category $category */
            $category = Category::find($id);
            $categories[] = [
                'name' => $category->name,
                'slug' => $category->slug,
                'apps' => $apps,
            ];
        }

        return response()->json($categories, 200, ['Content-Type' => 'application/json'], JSON_UNESCAPED_SLASHES);
    }

    /**
     * Get the apps from a device / type / category
     *
     * @OA\Get(
     *   path="/v2/{device}/{type}/{category}",
     * @OA\Parameter(
     *     name="device",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="mch2022")
     *   ),
     * @OA\Parameter(
     *     name="type",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="esp32")
     *   ),
     * @OA\Parameter(
     *     name="category",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="fun")
     *   ),
     *   tags={"MCH2022"},
     * @OA\Response(response="default",ref="#/components/responses/undocumented")
     * )
     *
     * @param string $device
     * @param string $type
     * @param string $category
     * @return JsonResponse
     */
    public function apps(string $device, string $type, string $category): JsonResponse
    {
        /** @var Badge $badge */
        $badge = Badge::whereSlug($device)->firstOrFail();
        /** @var Category $category */
        $categoryId = Category::whereSlug($category)->firstOrFail()->id;
        $apps = [];
        /** @var Project $project */
        foreach ($badge->projects()->whereNotNull('published_at')->whereProjectType($type)->whereCategoryId($categoryId)->get() as $project) {
            $apps[] = [
                'slug' => $project->slug,
                'name' => $project->name,
                'author' => $project->author,
                'license' => $project->license,
                'description' => $project->description,
                'version' => $project->revision,
            ];
        }
        return response()->json($apps, 200, ['Content-Type' => 'application/json'], JSON_UNESCAPED_SLASHES);
    }


    /**
     * Get the apps from a device / type / category
     *
     * @OA\Get(
     *   path="/v2/{device}/{type}/{category}/{app}",
     * @OA\Parameter(
     *     name="device",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="mch2022")
     *   ),
     * @OA\Parameter(
     *     name="type",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="esp32")
     *   ),
     * @OA\Parameter(
     *     name="category",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="fun")
     *   ),
     * @OA\Parameter(
     *     name="app",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="game_of_life")
     *   ),
     *   tags={"MCH2022"},
     * @OA\Response(response="default",ref="#/components/responses/undocumented")
     * )
     *
     * @param string $device
     * @param string $type
     * @param string $category
     * @param string $app
     * @return JsonResponse
     */
    public function app(string $device, string $type, string $category, string $app): JsonResponse
    {
        /** @var Badge $badge */
        $badge = Badge::whereSlug($device)->firstOrFail();
        $categoryId = Category::whereSlug($category)->firstOrFail()->id;
        /** @var Project $project */
        $project = $badge->projects()
            ->whereProjectType($type)->whereCategoryId($categoryId)->whereSlug($app)->firstOrFail();

        /** @var Version $version */
        $version = $project->versions()->published()->get()->last();
        $files = [];
        /** @var File $file */
        foreach ($version->files as $file) {
            $fileData = new \stdClass();
            $fileData->name = $file->name;
            $fileData->url = route('mch.file', [
                'device' => $badge->slug,
                'type' => $project->project_type,
                'category' => $category,
                'app' => $project->slug,
                'file' => $file->name
            ]);
            $fileData->size = (int)$file->size_of_content;
            $fileData->crc32 = $file->crc32;

            $files[] = $fileData;
        }

        return response()->json(
            [
                'device' => $badge->slug,
                'type' => $project->project_type,
                'category' => $category,
                'slug' => $project->slug,
                'name' => $project->name,
                'author' => $project->author,
                'license' => $project->license,
                'description' => $project->description,
                'version' => (int)$project->revision,
                'files' => $files,
            ],
            200,
            ['Content-Type' => 'application/json'],
            JSON_UNESCAPED_SLASHES
        );
    }

    /**
     * Get app file content
     *
     * @OA\Get(
     *   path="/v2/{device}/{type}/{category}/{app}/{file}",
     * @OA\Parameter(
     *     name="device",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="mch2022")
     *   ),
     * @OA\Parameter(
     *     name="type",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="esp32")
     *   ),
     * @OA\Parameter(
     *     name="category",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="fun")
     *   ),
     * @OA\Parameter(
     *     name="app",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="game_of_life")
     *   ),
     * @OA\Parameter(
     *     name="file",
     *     in="path",
     *     required=true,
     * @OA\Schema(type="string", format="slug", example="file.py")
     *   ),
     *   tags={"MCH2022"},
     * @OA\Response(response="default",ref="#/components/responses/undocumented")
     * )
     *
     * @param string $device
     * @param string $type
     * @param string $category
     * @param string $app
     * @param string $name
     * @return Response|JsonResponse
     */
    public function file(
        string $device,
        string $type,
        string $category,
        string $app,
        string $name
    ): Response|JsonResponse {
        /** @var Badge $badge */
        $badge = Badge::whereSlug($device)->firstOrFail();
        $categoryId = Category::whereSlug($category)->firstOrFail()->id;
        /** @var Project $project */
        $project = $badge->projects()
            ->whereProjectType($type)->whereCategoryId($categoryId)->whereSlug($app)->firstOrFail();

        /** @var Version|null $version */
        $version = $project->versions()->published()->get()->last();

        if ($version === null || empty($version->files)) {
            return response()->json(['message' => 'File not found'], 404);
        }

        /** @var File|null $file */
        $file = $version->files()->where('name', $name)->first();
        if ($file === null) {
            return response()->json(['message' => 'File not found'], 404);
        }

        return response($file->content)
            ->header('Cache-Control', 'no-cache private')
            ->header('Content-Description', 'File Transfer')
            ->header('Content-Type', $file->mime)
            ->header('Content-length', (string) strlen($file->content))
            ->header('Content-Disposition', 'attachment; filename=' . $file->name)
            ->header('Content-Transfer-Encoding', 'binary');
    }
}