обновление лаунчера + упущенная картинка на лендинг
This commit is contained in:
@@ -22,3 +22,4 @@
|
|||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
/public/appcast.xml
|
||||||
+28449
File diff suppressed because it is too large
Load Diff
@@ -4,10 +4,10 @@ namespace App\Http\Controllers\Api;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
|
||||||
class LauncherController extends Controller
|
class LauncherController extends Controller
|
||||||
{
|
{
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
@@ -135,6 +135,75 @@ class LauncherController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
path: '/api/service/update-appcast',
|
||||||
|
summary: 'Обновить appcast лаунчера',
|
||||||
|
description: 'Служебный endpoint для разработчика лаунчера. Принимает готовый XML appcast в формате RSS 2.0/Sparkle, который использует Flutter-пакет `upgrader`, и сохраняет его как публичный `/appcast.xml` на сервере. Тело запроса передается как raw XML, без JSON-обертки.',
|
||||||
|
tags: ['Launcher Service'],
|
||||||
|
security: [['launcherAuth' => []]],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/xml',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
example: "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\" xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\">\n <channel>\n <title>Moonwell Launcher - Appcast</title>\n <item>\n <title>Version 1.2.0</title>\n <description>Описание изменений для окна обновления.</description>\n <pubDate>Sat, 16 May 2026 12:00:00 +0400</pubDate>\n <enclosure url=\"https://moonwell.su/launcher/download\" sparkle:version=\"1.2.0\" sparkle:os=\"windows\" />\n </item>\n </channel>\n</rss>",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Appcast сохранен',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'XML saved'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'Нет доступа или передано пустое тело запроса',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'empty body'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 500,
|
||||||
|
description: 'Не удалось сохранить appcast.xml',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Failed to save XML'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
public function updateAppcast(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$xml = $request->getContent();
|
||||||
|
|
||||||
|
if (blank($xml)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'empty body',
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = Storage::disk('public')->put('appcast.xml', $xml);
|
||||||
|
|
||||||
|
if (! $saved) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Failed to save XML',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'XML saved',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private function disk(): string
|
private function disk(): string
|
||||||
{
|
{
|
||||||
return config('moonwell.launcher.disk', 's3');
|
return config('moonwell.launcher.disk', 's3');
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ use OpenApi\Attributes as OA;
|
|||||||
bearerFormat: 'JWT',
|
bearerFormat: 'JWT',
|
||||||
description: 'Токен, полученный через POST /api/launcher/login',
|
description: 'Токен, полученный через POST /api/launcher/login',
|
||||||
)]
|
)]
|
||||||
|
#[OA\SecurityScheme(
|
||||||
|
securityScheme: 'launcherAuth',
|
||||||
|
type: 'apiKey',
|
||||||
|
name: 'Auth',
|
||||||
|
in: 'header',
|
||||||
|
description: 'Сервисный ключ из LAUNCHER_AUTH_KEY для запросов лаунчера к служебным endpoint-ам.',
|
||||||
|
)]
|
||||||
abstract class Controller
|
abstract class Controller
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class EnsureLauncherRequest
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
abort_unless(
|
||||||
|
filled(config('auth.launcher_auth_key'))
|
||||||
|
&& hash_equals(config('auth.launcher_auth_key'), (string) $request->header('Auth')),
|
||||||
|
Response::HTTP_FORBIDDEN,
|
||||||
|
'Недостаточно прав для доступа к админке.',
|
||||||
|
);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
|
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'gamemaster' => \App\Http\Middleware\EnsureGameMaster::class,
|
'gamemaster' => \App\Http\Middleware\EnsureGameMaster::class,
|
||||||
|
'launcher' => \App\Http\Middleware\EnsureLauncherRequest::class,
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"zircote/swagger-php": "^6.0"
|
"zircote/swagger-php": "^6.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"barryvdh/laravel-ide-helper": "^3.7",
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
"laravel/pail": "^1.2.2",
|
"laravel/pail": "^1.2.2",
|
||||||
"laravel/pint": "^1.24",
|
"laravel/pint": "^1.24",
|
||||||
|
|||||||
Generated
+297
-2
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "7aed03d9ac748ed4faa8187c53fd26af",
|
"content-hash": "cb49ff467452a9a586f2d6d30b1c2957",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@@ -7717,6 +7717,301 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
{
|
||||||
|
"name": "barryvdh/laravel-ide-helper",
|
||||||
|
"version": "v3.7.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
|
||||||
|
"reference": "ad7e37676f1ff985d55ef1b6b96a0c0a40f2609a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/ad7e37676f1ff985d55ef1b6b96a0c0a40f2609a",
|
||||||
|
"reference": "ad7e37676f1ff985d55ef1b6b96a0c0a40f2609a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"barryvdh/reflection-docblock": "^2.4",
|
||||||
|
"composer/class-map-generator": "^1.0",
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/console": "^11.15 || ^12 || ^13.0",
|
||||||
|
"illuminate/database": "^11.15 || ^12 || ^13.0",
|
||||||
|
"illuminate/filesystem": "^11.15 || ^12 || ^13.0",
|
||||||
|
"illuminate/support": "^11.15 || ^12 || ^13.0",
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-pdo_sqlite": "*",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3",
|
||||||
|
"illuminate/config": "^11.15 || ^12 || ^13.0",
|
||||||
|
"illuminate/view": "^11.15 || ^12 || ^13.0",
|
||||||
|
"larastan/larastan": "^3.1",
|
||||||
|
"mockery/mockery": "^1.4",
|
||||||
|
"orchestra/testbench": "^9.2 || ^10 || ^11.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
|
"phpunit/phpunit": "^10.5 || ^11.5.3 || ^12.5.12",
|
||||||
|
"spatie/phpunit-snapshot-assertions": "^4 || ^5",
|
||||||
|
"vlucas/phpdotenv": "^5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.6-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Barryvdh\\LaravelIdeHelper\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Barry vd. Heuvel",
|
||||||
|
"email": "barryvdh@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.",
|
||||||
|
"keywords": [
|
||||||
|
"autocomplete",
|
||||||
|
"codeintel",
|
||||||
|
"dev",
|
||||||
|
"helper",
|
||||||
|
"ide",
|
||||||
|
"laravel",
|
||||||
|
"netbeans",
|
||||||
|
"phpdoc",
|
||||||
|
"phpstorm",
|
||||||
|
"sublime"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
|
||||||
|
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.7.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://fruitcake.nl",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/barryvdh",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-03-17T14:12:51+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "barryvdh/reflection-docblock",
|
||||||
|
"version": "v2.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/barryvdh/ReflectionDocBlock.git",
|
||||||
|
"reference": "4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b",
|
||||||
|
"reference": "4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^8.5.14|^9"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"dflydev/markdown": "~1.0",
|
||||||
|
"erusev/parsedown": "~1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Barryvdh": [
|
||||||
|
"src/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mike van Riel",
|
||||||
|
"email": "mike.vanriel@naenius.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.1"
|
||||||
|
},
|
||||||
|
"time": "2026-03-05T20:09:01+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "composer/class-map-generator",
|
||||||
|
"version": "1.7.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/class-map-generator.git",
|
||||||
|
"reference": "86d8208fc3c649a3a999daf1a63c25201be2990f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/class-map-generator/zipball/86d8208fc3c649a3a999daf1a63c25201be2990f",
|
||||||
|
"reference": "86d8208fc3c649a3a999daf1a63c25201be2990f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/pcre": "^2.1 || ^3.1",
|
||||||
|
"php": "^7.2 || ^8.0",
|
||||||
|
"symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.12 || ^2",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
|
||||||
|
"phpstan/phpstan-phpunit": "^1 || ^2",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1.1 || ^2",
|
||||||
|
"phpunit/phpunit": "^8",
|
||||||
|
"symfony/filesystem": "^5.4 || ^6 || ^7 || ^8"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\ClassMapGenerator\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "https://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Utilities to scan PHP code and generate class maps.",
|
||||||
|
"keywords": [
|
||||||
|
"classmap"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/composer/class-map-generator/issues",
|
||||||
|
"source": "https://github.com/composer/class-map-generator/tree/1.7.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-05T09:17:07+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "composer/pcre",
|
||||||
|
"version": "3.3.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/pcre.git",
|
||||||
|
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||||
|
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan": "<1.11.10"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.12 || ^2",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
||||||
|
"phpunit/phpunit": "^8 || ^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\Pcre\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "http://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
|
||||||
|
"keywords": [
|
||||||
|
"PCRE",
|
||||||
|
"preg",
|
||||||
|
"regex",
|
||||||
|
"regular expression"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
|
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-11-12T16:29:46+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "fakerphp/faker",
|
"name": "fakerphp/faker",
|
||||||
"version": "v1.24.1",
|
"version": "v1.24.1",
|
||||||
@@ -10028,5 +10323,5 @@
|
|||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "8.3.30"
|
"php": "8.3.30"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.9.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,4 +116,5 @@ return [
|
|||||||
|
|
||||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||||
|
|
||||||
|
'launcher_auth_key' => env('LAUNCHER_AUTH_KEY','$2y$12$FLru8WxfUzHhoG6Dfp7Ts.nvrshKg6Mg/p2o465/LKCzDaSWqLso6')
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,374 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Filename
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The default filename.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'filename' => '_ide_helper.php',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Models filename
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The default filename for the models helper file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'models_filename' => '_ide_helper_models.php',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PhpStorm meta filename
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary
|
||||||
|
| files in it, should you need additional files for your project; e.g.
|
||||||
|
| `.phpstorm.meta.php/laravel_ide_Helper.php'.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'meta_filename' => '.phpstorm.meta.php',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Fluent helpers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set to true to generate commonly used Fluent methods.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'include_fluent' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Write model query methods
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set to false to disable generated docs for the 'query()', 'newQuery()' and 'newModelQuery()' methods.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'write_query_methods' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Write model magic methods
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set to false to disable write magic methods of model.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'write_model_magic_where' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Write model external Eloquent builder methods
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set to false to disable write external Eloquent builder methods.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'write_model_external_builder_methods' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Write model relation count and exists properties
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set to false to disable writing of relation count and exists properties
|
||||||
|
| to model DocBlocks.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'write_model_relation_count_properties' => true,
|
||||||
|
'write_model_relation_exists_properties' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Write Eloquent model mixins
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This will add the necessary DocBlock mixins to the model class
|
||||||
|
| contained in the Laravel framework. This helps the IDE with
|
||||||
|
| auto-completion.
|
||||||
|
|
|
||||||
|
| Please be aware that this setting changes a file within the /vendor directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'write_eloquent_model_mixins' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Helper files to include
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Include helper files. By default not included, but can be toggled with the
|
||||||
|
| -- helpers (-H) option. Extra helper files can be included.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'include_helpers' => false,
|
||||||
|
|
||||||
|
'helper_files' => [
|
||||||
|
base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
|
||||||
|
base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Model locations to include
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define in which directories the ide-helper:models command should look
|
||||||
|
| for models.
|
||||||
|
|
|
||||||
|
| glob patterns are supported to easier reach models in sub-directories,
|
||||||
|
| e.g. `app/Services/* /Models` (without the space).
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_locations' => [
|
||||||
|
'app',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Models to ignore
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define which models should be ignored.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'ignored_models' => [
|
||||||
|
// App\MyModel::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Models hooks
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define which hook classes you want to run for models to add custom information.
|
||||||
|
|
|
||||||
|
| Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_hooks' => [
|
||||||
|
// App\Support\IdeHelper\MyModelHook::class
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Extra classes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These implementations are not really extended, but called with magic functions.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'extra' => [
|
||||||
|
'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'],
|
||||||
|
'Session' => ['Illuminate\Session\Store'],
|
||||||
|
],
|
||||||
|
|
||||||
|
'magic' => [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Interface implementations
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These interfaces will be replaced with the implementing class. Some interfaces
|
||||||
|
| are detected by the helpers, others can be listed below.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'interfaces' => [
|
||||||
|
// App\MyInterface::class => App\MyImplementation::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Support for camel cased models
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| There are some Laravel packages (such as Eloquence) that allow for accessing
|
||||||
|
| Eloquent model properties via camel case, instead of snake case.
|
||||||
|
|
|
||||||
|
| Enabling this option will support these packages by saving all model
|
||||||
|
| properties as camel case, instead of snake case.
|
||||||
|
|
|
||||||
|
| For example, normally you would see this:
|
||||||
|
|
|
||||||
|
| * @property \Illuminate\Support\Carbon $created_at
|
||||||
|
| * @property \Illuminate\Support\Carbon $updated_at
|
||||||
|
|
|
||||||
|
| With this enabled, the properties will be this:
|
||||||
|
|
|
||||||
|
| * @property \Illuminate\Support\Carbon $createdAt
|
||||||
|
| * @property \Illuminate\Support\Carbon $updatedAt
|
||||||
|
|
|
||||||
|
| Note, it is currently an all-or-nothing option.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'model_camel_case_properties' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Property casts
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Cast the given "real type" to the given "type".
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'type_overrides' => [
|
||||||
|
'integer' => 'int',
|
||||||
|
'boolean' => 'bool',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Include DocBlocks from classes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Include DocBlocks from classes to allow additional code inspection for
|
||||||
|
| magic methods and properties.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'include_class_docblocks' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Force FQN usage
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Use the fully qualified (class) name in DocBlocks,
|
||||||
|
| even if the class exists in the same namespace
|
||||||
|
| or there is an import (use className) of the class.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'force_fqn' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Use generics syntax
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Use generics syntax within DocBlocks,
|
||||||
|
| e.g. `Collection<User>` instead of `Collection|User[]`.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'use_generics_annotations' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default return types for macros
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Define default return types for macros without explicit return types.
|
||||||
|
| e.g. `\Illuminate\Database\Query\Builder::class => 'static'`,
|
||||||
|
| `\Illuminate\Support\Str::class => 'string'`
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'macro_default_return_types' => [
|
||||||
|
Illuminate\Http\Client\Factory::class => Illuminate\Http\Client\PendingRequest::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Additional relation types
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Sometimes it's needed to create custom relation types. The key of the array
|
||||||
|
| is the relationship method name. The value of the array is the fully-qualified
|
||||||
|
| class name of the relationship, e.g. `'relationName' => RelationShipClass::class`.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'additional_relation_types' => [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Additional relation return types
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When using custom relation types its possible for the class name to not contain
|
||||||
|
| the proper return type of the relation. The key of the array is the relationship
|
||||||
|
| method name. The value of the array is the return type of the relation ('many'
|
||||||
|
| or 'morphTo').
|
||||||
|
| e.g. `'relationName' => 'many'`.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'additional_relation_return_types' => [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enforce nullable Eloquent relationships on not null columns
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When set to true (default), this option enforces nullable Eloquent relationships.
|
||||||
|
| However, in cases where the application logic ensures the presence of related
|
||||||
|
| records it may be desirable to set this option to false to avoid unwanted null warnings.
|
||||||
|
|
|
||||||
|
| Default: true
|
||||||
|
| A not null column with no foreign key constraint will have a "nullable" relationship.
|
||||||
|
| * @property int $not_null_column_with_no_foreign_key_constraint
|
||||||
|
| * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint
|
||||||
|
|
|
||||||
|
| Option: false
|
||||||
|
| A not null column with no foreign key constraint will have a "not nullable" relationship.
|
||||||
|
| * @property int $not_null_column_with_no_foreign_key_constraint
|
||||||
|
| * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enforce_nullable_relationships' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Make soft deletable relations nullable
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When set to true (default), relationships to models using SoftDeletes trait
|
||||||
|
| will be marked as nullable. This is because soft-deleted records are excluded
|
||||||
|
| from queries by default, meaning even non-nullable foreign keys can return
|
||||||
|
| null when the related model is soft-deleted.
|
||||||
|
|
|
||||||
|
| Default: true
|
||||||
|
| A relationship to a soft-deletable model will include |null in the type:
|
||||||
|
| * @property-read Team|null $team
|
||||||
|
|
|
||||||
|
| Option: false
|
||||||
|
| A relationship to a soft-deletable model will NOT include |null (unless
|
||||||
|
| nullable for other reasons such as nullable foreign key column):
|
||||||
|
| * @property-read Team $team
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'soft_deletes_force_nullable' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Run artisan commands after migrations to generate model helpers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The specified commands should run after migrations are finished running.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'post_migrate' => [
|
||||||
|
// 'ide-helper:models --nowrite',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -275,6 +275,82 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/service/update-appcast": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Launcher Service"
|
||||||
|
],
|
||||||
|
"summary": "Обновить appcast лаунчера",
|
||||||
|
"description": "Служебный endpoint для разработчика лаунчера. Принимает готовый XML appcast в формате RSS 2.0/Sparkle, который использует Flutter-пакет `upgrader`, и сохраняет его как публичный `/appcast.xml` на сервере. Тело запроса передается как raw XML, без JSON-обертки.",
|
||||||
|
"operationId": "0b1b8798e0eefc46277891ccc1440afb",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/xml": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\" xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\">\n <channel>\n <title>Moonwell Launcher - Appcast</title>\n <item>\n <title>Version 1.2.0</title>\n <description>Описание изменений для окна обновления.</description>\n <pubDate>Sat, 16 May 2026 12:00:00 +0400</pubDate>\n <enclosure url=\"https://moonwell.su/launcher/download\" sparkle:version=\"1.2.0\" sparkle:os=\"windows\" />\n </item>\n </channel>\n</rss>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Appcast сохранен",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "XML saved"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Нет доступа или передано пустое тело запроса",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "empty body"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Не удалось сохранить appcast.xml",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Failed to save XML"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"launcherAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/launcher/news": {
|
"/api/launcher/news": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -358,6 +434,12 @@
|
|||||||
"description": "Токен, полученный через POST /api/launcher/login",
|
"description": "Токен, полученный через POST /api/launcher/login",
|
||||||
"bearerFormat": "JWT",
|
"bearerFormat": "JWT",
|
||||||
"scheme": "bearer"
|
"scheme": "bearer"
|
||||||
|
},
|
||||||
|
"launcherAuth": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"description": "Сервисный ключ из LAUNCHER_AUTH_KEY для запросов лаунчера к служебным endpoint-ам.",
|
||||||
|
"name": "Auth",
|
||||||
|
"in": "header"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -369,6 +451,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Launcher",
|
"name": "Launcher",
|
||||||
"description": "Launcher"
|
"description": "Launcher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launcher Service",
|
||||||
|
"description": "Launcher Service"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
@@ -28,12 +28,9 @@ export default function About() {
|
|||||||
|
|
||||||
<div className="about-visual corners">
|
<div className="about-visual corners">
|
||||||
<div className="about-stats">
|
<div className="about-stats">
|
||||||
<span>WoW 3.3.5a</span>
|
|
||||||
<span>Est. 2026</span>
|
<span>Est. 2026</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="placeholder-note">
|
<img src="landing.png" alt="концепт-арт: вход в Лунный Колодец, туман, силуэт ночного эльфа" />
|
||||||
[ placeholder ] концепт-арт: вход в Лунный Колодец, туман, силуэт ночного эльфа · 1200×1600
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -14,3 +14,7 @@ Route::middleware('auth:api')->prefix('launcher')->group(function () {
|
|||||||
->where('path', '.*');
|
->where('path', '.*');
|
||||||
Route::get('news', [LauncherNewsController::class, 'index']);
|
Route::get('news', [LauncherNewsController::class, 'index']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::middleware(['launcher'])->prefix('service')->name('service.')->group(function () {
|
||||||
|
Route::post('/update-appcast', [LauncherController::class, 'updateAppcast']);
|
||||||
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Route;
|
|||||||
|
|
||||||
Route::get('/', [LandingController::class, 'index'])->name('landing.index');
|
Route::get('/', [LandingController::class, 'index'])->name('landing.index');
|
||||||
Route::get('/launcher/login-news.txt', [WowLoginNewsFeedController::class, 'show'])->name('launcher.login-news.show');
|
Route::get('/launcher/login-news.txt', [WowLoginNewsFeedController::class, 'show'])->name('launcher.login-news.show');
|
||||||
|
Route::get('/appcast.xml', static fn() => Storage::disk('public')->get('appcast.xml'));
|
||||||
Route::post('/register', [LandingController::class, 'register'])
|
Route::post('/register', [LandingController::class, 'register'])
|
||||||
->middleware('throttle:8,1')
|
->middleware('throttle:8,1')
|
||||||
->name('game-account.store');
|
->name('game-account.store');
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class LauncherAppcastTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_update_appcast_requires_launcher_auth_header(): void
|
||||||
|
{
|
||||||
|
config(['auth.launcher_auth_key' => 'launcher-secret']);
|
||||||
|
|
||||||
|
$response = $this->call(
|
||||||
|
method: 'POST',
|
||||||
|
uri: '/api/service/update-appcast',
|
||||||
|
content: $this->appcastXml(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$response->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_appcast_saves_raw_xml_to_public_storage(): void
|
||||||
|
{
|
||||||
|
Storage::fake('public');
|
||||||
|
config(['auth.launcher_auth_key' => 'launcher-secret']);
|
||||||
|
|
||||||
|
$xml = $this->appcastXml();
|
||||||
|
|
||||||
|
$response = $this->call(
|
||||||
|
method: 'POST',
|
||||||
|
uri: '/api/service/update-appcast',
|
||||||
|
server: [
|
||||||
|
'HTTP_AUTH' => 'launcher-secret',
|
||||||
|
'CONTENT_TYPE' => 'application/xml',
|
||||||
|
],
|
||||||
|
content: $xml,
|
||||||
|
);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertOk()
|
||||||
|
->assertJson(['message' => 'XML saved']);
|
||||||
|
|
||||||
|
Storage::disk('public')->assertExists('appcast.xml');
|
||||||
|
$this->assertSame($xml, Storage::disk('public')->get('appcast.xml'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function appcastXml(): string
|
||||||
|
{
|
||||||
|
return <<<'XML'
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
||||||
|
<channel>
|
||||||
|
<title>Moonwell Launcher - Appcast</title>
|
||||||
|
<item>
|
||||||
|
<title>Version 1.2.0</title>
|
||||||
|
<description>Launcher update.</description>
|
||||||
|
<pubDate>Sat, 16 May 2026 12:00:00 +0400</pubDate>
|
||||||
|
<enclosure url="https://moonwell.su/launcher/download" sparkle:version="1.2.0" sparkle:os="windows" />
|
||||||
|
</item>
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
XML;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user