diff --git a/.env.testing b/.env.testing index af5f090..91d13cd 100644 --- a/.env.testing +++ b/.env.testing @@ -8,9 +8,9 @@ APP_URL=http://localhost DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 -DB_DATABASE=badgeware_test +DB_DATABASE=hatchery DB_USERNAME=root -DB_PASSWORD= +DB_PASSWORD=root BROADCAST_DRIVER=log CACHE_DRIVER=file diff --git a/app/Http/Controllers/MchController.php b/app/Http/Controllers/MchController.php new file mode 100644 index 0000000..254c1b4 --- /dev/null +++ b/app/Http/Controllers/MchController.php @@ -0,0 +1,333 @@ + $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="/mch2022/{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="/mch2022/{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="/mch2022/{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()->whereProjectType($type)->whereCategoryId($categoryId)->get() as $project) { + $apps[] = [ + 'slug' => $project->slug, + 'name' => $project->name, + 'author' => $project->author, + 'license' => $project->license, + 'description' => $project->description, + ]; + } + return response()->json($apps, 200, ['Content-Type' => 'application/json'], JSON_UNESCAPED_SLASHES); + } + + + /** + * Get the apps from a device / type / category + * + * @OA\Get( + * path="/mch2022/{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 = $file->size_of_content; + + $files[] = $fileData; + } + + return response()->json( + [ + 'slug' => $project->slug, + 'name' => $project->name, + 'author' => $project->author, + 'license' => $project->license, + 'description' => $project->description, + 'files' => $files, + ], + 200, + ['Content-Type' => 'application/json'], + JSON_UNESCAPED_SLASHES + ); + } + + /** + * Get app file content + * + * @OA\Get( + * path="/mch2022/{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, + 200, + ['Content-Type' => $file->mime] + ); + } +} diff --git a/app/Http/Controllers/PublicController.php b/app/Http/Controllers/PublicController.php index 9dda605..6698092 100644 --- a/app/Http/Controllers/PublicController.php +++ b/app/Http/Controllers/PublicController.php @@ -54,7 +54,7 @@ public function index(Request $request): View /** @var Builder $projects */ $projects = Project::whereHas( 'versions', - function ($query) { + static function ($query) { $query->published(); } ); @@ -98,7 +98,7 @@ private function returnProjectView(Request $request, $projects, string $badge = $orderField = 'id'; $orderDirection = 'desc'; - if ($request->has('order') && in_array($request->get('order'), $this->orderFields)) { + if ($request->has('order') && in_array($request->get('order'), $this->orderFields, true)) { $orderField = $request->get('order'); if ($request->has('direction') && $request->get('direction') === 'asc') { $orderDirection = 'asc'; diff --git a/app/Models/Badge.php b/app/Models/Badge.php index cc01e5a..e1e26eb 100644 --- a/app/Models/Badge.php +++ b/app/Models/Badge.php @@ -4,6 +4,7 @@ namespace App\Models; +use Database\Factories\BadgeFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -29,6 +30,7 @@ * @property-read int|null $projects_count * @property-read Collection|BadgeProject[] $states * @property-read int|null $states_count + * @property-read array $types * @method static Builder|Badge newModelQuery() * @method static Builder|Badge newQuery() * @method static Builder|Badge query() @@ -40,8 +42,8 @@ * @method static Builder|Badge whereUpdatedAt($value) * @method static Builder|Badge whereCommands($value) * @method static Builder|Badge whereConstraints($value) + * @method static BadgeFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\BadgeFactory factory(...$parameters) */ class Badge extends Model { @@ -86,4 +88,25 @@ public function getRouteKeyName(): string { return 'slug'; } + + /** + * @return array> + */ + public function getTypesAttribute(): array + { + return [ + [ + 'name' => 'Espressif ESP32 binary', + 'slug' => 'esp32', + ], + [ + 'name' => 'MicroPython egg', + 'slug' => 'python', + ], + [ + 'name' => 'Lattice iCE40 bitstream', + 'slug' => 'ice40', + ], + ]; + } } diff --git a/app/Models/Category.php b/app/Models/Category.php index 4b3e084..1cd09b8 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -4,6 +4,7 @@ namespace App\Models; +use Database\Factories\CategoryFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -42,8 +43,8 @@ * @method static Builder|Category whereUpdatedAt($value) * @method static Builder|Category withTrashed() * @method static Builder|Category withoutTrashed() + * @method static CategoryFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\CategoryFactory factory(...$parameters) */ class Category extends Model { diff --git a/app/Models/File.php b/app/Models/File.php index d0be7d5..6de6b5b 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Support\Helpers; +use Database\Factories\FileFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -55,8 +56,8 @@ * @method static Builder|File whereVersionId($value) * @method static Builder|File withTrashed() * @method static Builder|File withoutTrashed() + * @method static FileFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\FileFactory factory(...$parameters) */ class File extends Model { diff --git a/app/Models/Project.php b/app/Models/Project.php index fd0817d..45c0f0b 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Support\Helpers; +use Database\Factories\ProjectFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -37,6 +38,9 @@ * @property Carbon $created_at * @property Carbon $updated_at * @property int $download_counter + * @property string|null $license + * @property int $allow_team_fixes + * @property string $project_type * @property-read Collection|Badge[] $badges * @property-read int|null $badges_count * @property-read string $category @@ -87,8 +91,11 @@ * @method static Builder|Project withoutTrashed() * @method static Builder|Project whereMaxFirmware($value) * @method static Builder|Project whereMinFirmware($value) + * @method static Builder|Project whereAllowTeamFixes($value) + * @method static Builder|Project whereProjectType($value) + * @method static Builder|Project whereLicense($value) + * @method static ProjectFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\ProjectFactory factory(...$parameters) */ class Project extends Model { @@ -138,6 +145,9 @@ class Project extends Model 'git', 'git_commit_id', 'user', + 'allow_team_fixes', + 'project_type', + 'license', ]; /** @@ -158,7 +168,7 @@ class Project extends Model 'os', 'uos', 'badge', 'esp32', 'ussl', 'time', 'utime', 'splash', 'launcher', 'installer', 'ota_update', 'boot', 'appglue', 'database', 'dialogs', 'deepsleep', 'magic', 'ntp', 'rtcmem', 'machine', 'setup', 'version', 'wifi', 'woezel', 'network', 'socket', 'uhashlib', 'hashlib', 'ugfx', 'btree', 'request', 'urequest', 'uzlib', - 'zlib', 'ssl', 'create', 'delete', 'system', + 'zlib', 'ssl', 'create', 'delete', 'system', 'categories', 'devices', 'types' ]; /** @@ -276,7 +286,7 @@ public function collaborators(): BelongsToMany } /** - * @return string + * @return string|null */ public function getRevisionAttribute(): ?string { @@ -312,7 +322,7 @@ public function badges(): BelongsToMany } /** - * @return int + * @return int|null */ public function getSizeOfZipAttribute(): ?int { @@ -322,7 +332,7 @@ public function getSizeOfZipAttribute(): ?int } /** - * @return int + * @return int|null */ public function getSizeOfContentAttribute(): ?int { @@ -366,7 +376,7 @@ public function getRouteKeyName(): string } /** - * @return string + * @return string|null */ public function getCategoryAttribute(): ?string { @@ -384,7 +394,7 @@ public function getCategoryAttribute(): ?string * * @return bool */ - public static function isForbidden(string $slug) + public static function isForbidden(string $slug): bool { return in_array($slug, self::$forbidden); } diff --git a/app/Models/User.php b/app/Models/User.php index 5a3e421..2899b25 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; +use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -35,6 +36,7 @@ * @property Carbon|null $deleted_at * @property Carbon|null $created_at * @property Carbon|null $updated_at + * @property string|null $email_verified_at * @property-read DatabaseNotificationCollection|DatabaseNotification[] $notifications * @property-read int|null $notifications_count * @property-read Collection|Project[] $projects @@ -67,12 +69,11 @@ * @method static Builder|User whereUpdatedAt($value) * @method static Builder|User wherePublic($value) * @method static Builder|User whereShowProjects($value) + * @method static Builder|User whereEmailVerifiedAt($value) * @method static Builder|User withTrashed() * @method static Builder|User withoutTrashed() + * @method static UserFactory factory(...$parameters) * @mixin \Eloquent - * @property string|null $email_verified_at - * @method static \Database\Factories\UserFactory factory(...$parameters) - * @method static Builder|User whereEmailVerifiedAt($value) */ class User extends Authenticatable { diff --git a/app/Models/Version.php b/app/Models/Version.php index 2ef9861..7ce8d2e 100644 --- a/app/Models/Version.php +++ b/app/Models/Version.php @@ -4,6 +4,7 @@ namespace App\Models; +use Database\Factories\VersionFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -53,8 +54,8 @@ * @method static Builder|Version whereZip($value) * @method static Builder|Version withTrashed() * @method static Builder|Version withoutTrashed() + * @method static VersionFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\VersionFactory factory(...$parameters) */ class Version extends Model { diff --git a/app/Models/Vote.php b/app/Models/Vote.php index 8fe4ea4..ac9e433 100644 --- a/app/Models/Vote.php +++ b/app/Models/Vote.php @@ -4,6 +4,7 @@ namespace App\Models; +use Database\Factories\VoteFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -42,8 +43,8 @@ * @method static Builder|Vote whereUserId($value) * @method static Builder|Vote withTrashed() * @method static Builder|Vote withoutTrashed() + * @method static VoteFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\VoteFactory factory(...$parameters) */ class Vote extends Model { diff --git a/app/Models/Warning.php b/app/Models/Warning.php index 4090b13..31fc78d 100644 --- a/app/Models/Warning.php +++ b/app/Models/Warning.php @@ -4,6 +4,7 @@ namespace App\Models; +use Database\Factories\WarningFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -40,8 +41,8 @@ * @method static Builder|Warning whereUserId($value) * @method static Builder|Warning withTrashed() * @method static Builder|Warning withoutTrashed() + * @method static WarningFactory factory(...$parameters) * @mixin \Eloquent - * @method static \Database\Factories\WarningFactory factory(...$parameters) */ class Warning extends Model { diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 8601759..91f3984 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -70,5 +70,9 @@ protected function mapWebRoutes() ->namespace($this->namespace) ->prefix('weather') ->group(base_path('routes/weather.php')); + Route::middleware('web') + ->namespace($this->namespace) + ->prefix('mch2022') + ->group(base_path('routes/mch2022.php')); } } diff --git a/database/factories/BadgeFactory.php b/database/factories/BadgeFactory.php index 94a1b13..4878f6d 100644 --- a/database/factories/BadgeFactory.php +++ b/database/factories/BadgeFactory.php @@ -24,7 +24,7 @@ class BadgeFactory extends Factory /** * Define the model's default state. * - * @return array + * @return string[] */ public function definition() { diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index 8194993..63e81cf 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -24,7 +24,7 @@ class CategoryFactory extends Factory /** * Define the model's default state. * - * @return array + * @return string[] */ public function definition() { diff --git a/database/factories/FileFactory.php b/database/factories/FileFactory.php index dc539e0..7599177 100644 --- a/database/factories/FileFactory.php +++ b/database/factories/FileFactory.php @@ -25,7 +25,7 @@ class FileFactory extends Factory /** * Define the model's default state. * - * @return array + * @return array */ public function definition() { diff --git a/database/factories/ProjectFactory.php b/database/factories/ProjectFactory.php index 7760d65..0649525 100644 --- a/database/factories/ProjectFactory.php +++ b/database/factories/ProjectFactory.php @@ -25,7 +25,7 @@ class ProjectFactory extends Factory /** * Define the model's default state. * - * @return array + * @return array */ public function definition() { diff --git a/database/factories/TeamFactory.php b/database/factories/TeamFactory.php deleted file mode 100644 index b3b7299..0000000 --- a/database/factories/TeamFactory.php +++ /dev/null @@ -1,38 +0,0 @@ - $this->faker->name, - 'user_id' => User::factory(), - 'personal_team' => false, - ]; - } -} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index ffdc7fc..daedb14 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -7,6 +7,7 @@ use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; +use Illuminate\Support\Carbon; /** * Class UserFactory. @@ -25,7 +26,7 @@ class UserFactory extends Factory /** * Define the model's default state. * - * @return array + * @return array */ public function definition() { diff --git a/database/factories/VersionFactory.php b/database/factories/VersionFactory.php index ce2e52e..8ed22ee 100644 --- a/database/factories/VersionFactory.php +++ b/database/factories/VersionFactory.php @@ -25,7 +25,7 @@ class VersionFactory extends Factory /** * Define the model's default state. * - * @return array + * @return array */ public function definition() { diff --git a/database/factories/VoteFactory.php b/database/factories/VoteFactory.php index 4262305..e9d6ec2 100644 --- a/database/factories/VoteFactory.php +++ b/database/factories/VoteFactory.php @@ -25,7 +25,7 @@ class VoteFactory extends Factory /** * Define the model's default state. * - * @return array + * @return array */ public function definition() { diff --git a/database/factories/WarningFactory.php b/database/factories/WarningFactory.php index 77d3836..c54e3ad 100644 --- a/database/factories/WarningFactory.php +++ b/database/factories/WarningFactory.php @@ -25,7 +25,7 @@ class WarningFactory extends Factory /** * Define the model's default state. * - * @return array + * @return array */ public function definition() { diff --git a/database/migrations/2019_09_06_170135_alter_projects_drop_description.php b/database/migrations/2019_09_06_170135_alter_projects_drop_description.php index 6ac59f2..98dbb23 100644 --- a/database/migrations/2019_09_06_170135_alter_projects_drop_description.php +++ b/database/migrations/2019_09_06_170135_alter_projects_drop_description.php @@ -3,6 +3,7 @@ declare(strict_types=1); use App\Models\Project; +use App\Models\File; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -51,7 +52,10 @@ function (Blueprint $table) { foreach (Project::all() as $project) { $version = $project->versions->last(); if ($version && $version->files()->where('name', 'like', 'README.md')->count() === 1) { - $project->description = $version->files()->where('name', 'like', 'README.md')->first()->content; + /** @var File $file */ + $file = $version->files()->where('name', 'like', 'README.md')->first(); + $project->description = $file->content; + $project->save(); } } } diff --git a/database/migrations/2019_10_24_180950_add_published_at_to_projects_table.php b/database/migrations/2019_10_24_180950_add_published_at_to_projects_table.php index adf46c8..61821ad 100644 --- a/database/migrations/2019_10_24_180950_add_published_at_to_projects_table.php +++ b/database/migrations/2019_10_24_180950_add_published_at_to_projects_table.php @@ -3,6 +3,7 @@ declare(strict_types=1); use App\Models\Project; +use App\Models\Version; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -29,7 +30,9 @@ function ($query) { } ); foreach ($projects->get() as $project) { - $project->published_at = $project->versions()->published()->get()->last()->updated_at; + /** @var Version $version */ + $version = $project->versions()->published()->get()->last(); + $project->published_at = $version->updated_at; $project->save(); } } diff --git a/database/migrations/2022_06_16_185859_alter_project_add_rights_flags.php b/database/migrations/2022_06_16_185859_alter_project_add_rights_flags.php new file mode 100644 index 0000000..f7b834c --- /dev/null +++ b/database/migrations/2022_06_16_185859_alter_project_add_rights_flags.php @@ -0,0 +1,38 @@ +boolean('allow_team_fixes')->default(true); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( + 'projects', + function (Blueprint $table) { + $table->dropColumn('allow_team_fixes'); + } + ); + } +} diff --git a/database/migrations/2022_06_24_142624_alter_projects_add_type_enum.php b/database/migrations/2022_06_24_142624_alter_projects_add_type_enum.php new file mode 100644 index 0000000..869b687 --- /dev/null +++ b/database/migrations/2022_06_24_142624_alter_projects_add_type_enum.php @@ -0,0 +1,38 @@ +enum('project_type', ['python', 'esp32', 'ice40'])->default('python'); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( + 'projects', + function (Blueprint $table) { + $table->dropColumn('project_type'); + } + ); + } +} diff --git a/database/migrations/2022_06_24_210616_alter_projects_add_license.php b/database/migrations/2022_06_24_210616_alter_projects_add_license.php new file mode 100644 index 0000000..080a2e7 --- /dev/null +++ b/database/migrations/2022_06_24_210616_alter_projects_add_license.php @@ -0,0 +1,38 @@ +string('license')->nullable(); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( + 'projects', + function (Blueprint $table) { + $table->dropColumn('license'); + } + ); + } +} diff --git a/phpstan.neon b/phpstan.neon index d7ff3a9..c43693a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,6 +8,7 @@ parameters: - public - routes - tests + - database # The level 8 is the highest level level: 8 inferPrivatePropertyTypeFromConstructor: true diff --git a/routes/channels.php b/routes/channels.php index b15d59b..3d7c4c6 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -4,7 +4,7 @@ use App\Models\User; -Broadcast::channel('App.User.*', function (User $user) { +Broadcast::channel('App.User.*', static function (User $user) { /** @var User $authUser */ $authUser = Auth::user(); diff --git a/routes/mch2022.php b/routes/mch2022.php new file mode 100644 index 0000000..34eb32e --- /dev/null +++ b/routes/mch2022.php @@ -0,0 +1,14 @@ +name('mch.file'); diff --git a/tests/Feature/Mch20222Test.php b/tests/Feature/Mch20222Test.php new file mode 100644 index 0000000..82b798b --- /dev/null +++ b/tests/Feature/Mch20222Test.php @@ -0,0 +1,217 @@ +json('GET', '/mch2022/devices'); + $response->assertStatus(200) + ->assertExactJson([]); + + /** @var User $user */ + $user = User::factory()->create(); + $this->be($user); + /** @var Badge $badge */ + $badge = Badge::factory()->create(); + + $response = $this->json('GET', '/mch2022/devices'); + $response->assertStatus(200) + ->assertExactJson([ + [ + 'name' => $badge->name, + 'slug' => $badge->slug, + ] + ]); + } + + /** + * Simple list + */ + public function testMchDeviceTypes(): void + { + $response = $this->json('GET', '/mch2022/random_device/types'); + $response->assertStatus(404); + + /** @var User $user */ + $user = User::factory()->create(); + $this->be($user); + /** @var Badge $badge */ + $badge = Badge::factory()->create(); + + $response = $this->json('GET', '/mch2022/' . $badge->slug . '/types'); + $response->assertStatus(200) + ->assertExactJson([ + [ + 'name' => 'Espressif ESP32 binary', + 'slug' => 'esp32', + ], + [ + 'name' => 'MicroPython egg', + 'slug' => 'python', + ], + [ + 'name' => 'Lattice iCE40 bitstream', + 'slug' => 'ice40', + ], + ]); + } + + /** + * List categories for badge / type + */ + public function testMchCategories(): void + { + $response = $this->json('GET', '/mch2022/iets/esp32/categories'); + $response->assertStatus(404); + + /** @var User $user */ + $user = User::factory()->create(); + $this->be($user); + /** @var Badge $badge */ + $badge = Badge::factory()->create(); + + $response = $this->json('GET', '/mch2022/' . $badge->slug . '/esp32/categories'); + $response->assertStatus(200) + ->assertExactJson([]); + + /** @var Version $version */ + $version = Version::factory()->create(); + $version->zip = 'some_path.tar.gz'; + $version->save(); + $version->project->badges()->attach($badge); + File::factory()->create(['version_id' => $version->id]); + /** @var Category $category */ + $category = $version->project->category()->first(); + + $response = $this->json('GET', '/mch2022/' . $badge->slug . '/esp32/categories'); + $response->assertStatus(200) + ->assertExactJson([]); + + $response = $this->json('GET', '/mch2022/' . $badge->slug . '/python/categories'); + $response->assertStatus(200) + ->assertExactJson([ + [ + 'slug' => $category->slug, + 'name' => $category->name, + 'apps' => 1 + ] + ]); + } + + /** + * Check JSON files / app info request . . + */ + public function testMchApps(): void + { + $response = $this->json('GET', '/mch2022/iets/app/some_app'); + $response->assertStatus(404); + + /** @var User $user */ + $user = User::factory()->create(); + $this->be($user); + + /** @var Badge $badge */ + $badge = Badge::factory()->create(); + /** @var Version $version */ + $version = Version::factory()->create(); + $version->zip = 'some_path.tar.gz'; + $version->save(); + $version->project->badges()->attach($badge); + /** @var Category $category */ + $category = $version->project->category()->first(); + + $response = $this->json('GET', '/mch2022/' . $badge->slug . '/python/' . $category->slug . '/iets'); + $response->assertStatus(404); + + $response = $this->json( + 'GET', + '/mch2022/' . $badge->slug . '/python/' . $category->slug . '/' . $version->project->slug + ); + $response->assertStatus(200) + ->assertJson([]); + /** @var File $file */ + $file = File::factory()->create(['version_id' => $version->id]); + $response = $this->json( + 'GET', + '/mch2022/' . $badge->slug . '/python/' . $category->slug . '/' . $version->project->slug + ); + $response->assertStatus(200) + ->assertJson([ + 'slug' => $version->project->slug, + 'name' => $version->project->name, + 'author' => $version->project->author, + 'license' => $version->project->license, + 'description' => $version->project->description, + 'files' => [ + [ + 'name' => $file->name, + 'url' => url( + 'mch2022/' . $badge->slug . '/python/' . $category->slug . '/' . + $version->project->slug . '/' . $file->name + ), + 'size' => $file->size_of_content + ] + ] + ]); + } + + /** + * Check File request . . + */ + public function testMchFile(): void + { + /** @var User $user */ + $user = User::factory()->create(); + $this->be($user); + /** @var Badge $badge */ + $badge = Badge::factory()->create(); + /** @var File $file */ + $file = File::factory()->create(); + + $version = $file->version; + $version->zip = 'some_path.tar.gz'; + $version->save(); + $version->project->badges()->attach($badge); + /** @var Category $category */ + $category = $version->project->category()->first(); + + $response = $this->json( + 'GET', + '/mch2022/' . $badge->slug . '/python/' . $category->slug . '/' . + $version->project->slug . '/random.txt' + ); + $response->assertStatus(404) + ->assertExactJson(['message' => 'File not found']); + + $response = $this->json( + 'GET', + '/mch2022/' . $badge->slug . '/python/' . $category->slug . '/' . + $version->project->slug . '/' . $file->name + ); + $response->assertStatus(200) + ->assertHeader('Content-Type', $file->mime) + ->assertSee($file->content); + } +}