badgeteam/Hatchery

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

Summary

Maintainability
C
7 hrs
Test Coverage
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\ProjectNotificationRequest;
use App\Http\Requests\ProjectRenameRequest;
use App\Http\Requests\ProjectStoreRequest;
use App\Http\Requests\ProjectUpdateRequest;
use App\Jobs\PublishProject;
use App\Jobs\UpdateProject;
use App\Mail\ProjectNotificationMail;
use App\Models\Badge;
use App\Models\BadgeProject;
use App\Models\Category;
use App\Models\File;
use App\Models\License;
use App\Models\Project;
use App\Models\User;
use App\Models\Version;
use App\Models\Warning;
use App\Support\Helpers;
use CzProject\GitPhp\Git;
use CzProject\GitPhp\GitException;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Illuminate\View\View;

/**
 * Class ProjectsController.
 *
 * @author annejan@badge.team
 */
class ProjectsController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct()
    {
        $this->middleware('auth', ['except' => ['index', 'show']]);
        $this->authorizeResource(Project::class, null, ['except' => ['index', 'show']]);
    }

    /**
     * Display a listing of the resource.
     *
     * @param Request $request
     *
     * @return View
     */
    public function index(Request $request): View
    {
        $badge = $this->getBadge($request);
        $category = $this->getCategory($request);
        $search = $this->getSearch($request);

        if ($badge) {
            $projects = $badge->projects()->orderBy('id', 'desc');
            $badge = $badge->slug;
        } else {
            $projects = Project::orderBy('id', 'desc');
        }

        if ($category) {
            $projects = $projects->where('category_id', $category->id);
            $category = $category->slug;
        }

        if ($search) {
            $projects = $projects->where(
                function (Builder $query) use ($search) {
                    $query->where('name', 'like', '%' . $search . '%');
                    // @todo perhaps search in README ?
                }
            );
        }

        return view('projects.index')
            ->with(['projects' => $projects->paginate()])
            ->with('badge', $badge)
            ->with('category', $category)
            ->with('search', $search);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @param Request $request
     *
     * @return View
     */
    public function create(Request $request): View
    {
        return view('projects.create')
            ->with('type', $request->routeIs('projects.import') ? 'import' : 'create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param ProjectStoreRequest $request
     *
     * @return RedirectResponse
     */
    public function store(ProjectStoreRequest $request): RedirectResponse
    {
        if (Project::where('slug', Str::slug($request->name, '_'))->exists()) {
            return redirect()->route('projects.create')->withInput()->withErrors(['slug already exists :(']);
        }
        if (Project::isForbidden(Str::slug($request->name, '_'))) {
            return redirect()->route('projects.create')->withInput()->withErrors(['reserved name']);
        }

        try {
            $project = $this->storeProjectInfo($request);
        } catch (Exception $e) {
            return redirect()->route('projects.create')->withInput()->withErrors([$e->getMessage()]);
        }

        return redirect()->route('projects.edit', ['project' => $project->slug])
            ->withSuccesses([$project->name . ' created!']);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param Project $project
     *
     * @return View
     */
    public function edit(Project $project): View
    {
        return view('projects.edit')
            ->with('project', $project);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param ProjectUpdateRequest $request
     * @param Project $project
     *
     * @return RedirectResponse
     */
    public function update(ProjectUpdateRequest $request, Project $project): RedirectResponse
    {
        $project->category_id = $request->category_id;

        try {
            $project->min_firmware = $request->min_firmware;
            $project->max_firmware = $request->max_firmware;
            $project->allow_team_fixes = $request->has('allow_team_fixes');
            $project->save();
            $this->manageDependencies($project, $request);
            $this->manageBadges($project, $request);
            $this->manageCollaborators($project, $request);
            $this->manageLicense($project, $request);
            $this->manageType($project, $request);
        } catch (Exception $e) {
            return redirect()->route('projects.edit', ['project' => $project->slug])
                ->withInput()->withErrors([$e->getMessage()]);
        }
        if (isset($request->publish)) {
            return $this->publish($project);
        }

        return redirect()->route('projects.index')->withSuccesses([$project->name . ' saved']);
    }

    /**
     * Publish the latest version.
     *
     * @param Project $project
     *
     * @return RedirectResponse
     */
    public function publish(Project $project): RedirectResponse
    {
        PublishProject::dispatch($project, Auth::user());

        return redirect()->route('projects.index')->withSuccesses([$project->name . ' is being published.']);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param Project $project
     *
     * @return RedirectResponse
     */
    public function destroy(Project $project): RedirectResponse
    {
        $name = $project->name;

        try {
            $project->name = 'Deleted ' . mt_rand() . ' ' . $name;
            $project->slug = Str::slug($project->name);
            $project->save();
            $project->delete();
        } catch (Exception $e) {
            return redirect(URL()->previous())
                ->withInput()
                ->withErrors([$e->getMessage()]);
        }

        return redirect()->route('projects.index')->withSuccesses([$name . ' deleted']);
    }

    /**
     * Show project content, public method ツ.
     *
     * @param Project $project
     *
     * @return View
     */
    public function show(Project $project): View
    {
        return view('projects.show')
            ->with('project', $project);
    }

    /**
     * Notify badge.team of broken or dangerous app.
     *
     * @param Project $project
     * @param ProjectNotificationRequest $request
     *
     * @return RedirectResponse
     */
    public function notify(Project $project, ProjectNotificationRequest $request): RedirectResponse
    {
        $warning = Warning::create(['project_id' => $project->id, 'description' => $request->description]);
        $mail = new ProjectNotificationMail($warning);
        Mail::to('bugs@badge.team')->send($mail);

        return redirect()->route('projects.show', ['project' => $project])
            ->withSuccesses(['Notification sent to badge.team']);
    }

    /**
     * Show project rename form, public method ツ.
     *
     * @param Project $project
     *
     * @return View
     * @throws AuthorizationException
     *
     */
    public function renameForm(Project $project): View
    {
        $this->authorize('rename', $project);

        return view('projects.rename')
            ->with('project', $project);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param ProjectRenameRequest $request
     * @param Project $project
     *
     * @return RedirectResponse
     * @throws AuthorizationException
     *
     */
    public function rename(ProjectRenameRequest $request, Project $project): RedirectResponse
    {
        $this->authorize('rename', $project);
        $slug = Str::slug($request->name, '_');
        if (Project::where('slug', $slug)->exists()) {
            return redirect()->route(
                'projects.rename',
                ['project' => $project->slug]
            )->withInput()->withErrors(['Name not unique enough ツ']);
        }

        $project->name = $request->name;
        $project->slug = $slug;
        $project->save();

        return redirect()->route('projects.edit', ['project' => $project->slug])
            ->withSuccesses([$project->name . ' renamed']);
    }

    /**
     * Update the specified resource from git when applicable.
     *
     * @param Project $project
     *
     * @return RedirectResponse
     */
    public function pull(Project $project): RedirectResponse
    {
        if ($project->git === null) {
            return redirect()->route('projects.edit', ['project' => $project->slug])
                ->withInput()->withErrors(['No git repo for project.']);
        }

        UpdateProject::dispatch($project, Auth::user());

        return redirect()->route('projects.index')->withSuccesses([$project->name . ' is being updated.']);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param ProjectStoreRequest $request
     * @param Git $repo
     *
     * @return RedirectResponse
     * @throws Exception
     *
     */
    public function import(ProjectStoreRequest $request, Git $repo): RedirectResponse
    {
        if (Project::where('slug', Str::slug($request->name, '_'))->exists()) {
            return redirect()->route('projects.import')->withInput()->withErrors(['slug already exists :(']);
        }
        if (Project::isForbidden(Str::slug($request->name, '_'))) {
            return redirect()->route('projects.import')->withInput()->withErrors(['reserved name']);
        }

        $tempFolder = sys_get_temp_dir() . '/' . Str::slug($request->name);

        try {
            $repo->cloneRepository($request->git, $tempFolder, ['-q', '--single-branch', '--depth', 1]);
        } catch (GitException $e) {
            return redirect()->route('projects.import')->withInput()->withErrors([$e->getMessage()]);
        }

        try {
            $project = $this->storeProjectInfo($request);
            $project->git = $request->git;
            $project->save();
            UpdateProject::dispatch($project, Auth::user());
        } catch (Exception $e) {
            Helpers::delTree($tempFolder);

            return redirect()->route('projects.import')->withInput()->withErrors([$e->getMessage()]);
        }

        return redirect()->route('projects.index')->withSuccesses([$project->name . ' being imported!']);
    }

    /**
     * @param Request $request
     *
     * @return Project
     */
    private function storeProjectInfo(Request $request): Project
    {
        $project = Project::create([
            'name' => $request->name,
            'category_id' => $request->category_id,
        ]);

        if ($request->badge_ids) {
            $badges = Badge::find($request->badge_ids);
            $project->badges()->attach($badges);
        }
        if ($request->description) {
            /** @var Version $version */
            $version = $project->versions->last();
            $file = new File();
            $file->name = 'README.md';
            $file->content = $request->description;
            $file->version()->associate($version);
            $file->save();
        }

        $project->allow_team_fixes = $request->has('allow_team_fixes');

        $this->manageLicense($project, $request);
        $this->manageType($project, $request);

        return $project;
    }

    /**
     * @param Project $project
     * @param Request $request
     *
     * @return void
     */
    private function manageLicense(Project $project, Request $request): void
    {
        if ($request->license) {
            if (!License::where('licenseId', $request->license)->exists()) {
                throw new \RuntimeException('Unknown license');
            }
            $project->license = $request->license;
            $project->save();
        }
    }

    /**
     * @param Project $project
     * @param Request $request
     *
     * @return void
     */
    private function manageType(Project $project, Request $request): void
    {
        if ($request->project_type) {
            if (!in_array($request->project_type, $project->types, true)) {
                throw new \RuntimeException('Unknown type');
            }
            $project->project_type = (string)$request->project_type;
            $project->save();
        }
    }

    /**
     * @param Project $project
     * @param Request $request
     *
     * @return void
     */
    private function manageDependencies(Project $project, Request $request): void
    {
        $project->dependencies()->detach();
        if ($request->has('dependencies')) {
            $dependencies = Project::find($request->get('dependencies'));
            $project->dependencies()->attach($dependencies);
        }
    }

    /**
     * @param Project $project
     * @param Request $request
     *
     * @return void
     */
    private function manageBadges(Project $project, Request $request): void
    {
        if ($request->has('badge_ids')) {
            $project->badges()->detach();
            $badges = Badge::find($request->get('badge_ids'));
            $project->badges()->attach($badges);
            $status = $request->get('badge_status');
            if (!is_array($status)) {
                throw new \RuntimeException('Badge status must be an array');
            }
            foreach ($request->get('badge_ids') as $badge_id) {
                if (array_key_exists($badge_id, $status)) {
                    /** @var BadgeProject|null $state */
                    $state = BadgeProject::where('badge_id', $badge_id)
                        ->where('project_id', $project->id)->first();
                    if ($state === null) {
                        throw new \RuntimeException('BadgeProject not found');
                    }
                    $state->status = $status[$badge_id];
                    $state->save();
                }
            }
        }
    }

    /**
     * @param Project $project
     * @param Request $request
     *
     * @return void
     */
    private function manageCollaborators(Project $project, Request $request): void
    {
        $project->collaborators()->detach();
        if ($request->has('collaborators')) {
            $collaborators = User::find($request->get('collaborators'));
            $project->collaborators()->attach($collaborators);
        }
    }

    /**
     * @param Request $request
     *
     * @return Badge|null
     */
    private function getBadge(Request $request): ?Badge
    {
        $badge = null;
        if ($request->has('badge') && $request->get('badge')) {
            /** @var Badge|null $badge */
            $badge = Badge::where('slug', $request->get('badge'))->firstOrFail();
        }

        return $badge;
    }

    /**
     * @param Request $request
     *
     * @return Category|null
     */
    private function getCategory(Request $request): ?Category
    {
        $category = null;
        if ($request->has('category') && $request->get('category')) {
            /** @var Category|null $category */
            $category = Category::where('slug', $request->get('category'))->firstOrFail();
        }

        return $category;
    }

    /**
     * @param Request $request
     *
     * @return string|null
     */
    private function getSearch(Request $request): ?string
    {
        $search = null;
        if ($request->has('search')) {
            $search = $request->get('search');
        }

        return $search;
    }
}