Статьи

Начиная бизнес с Laravel Spark

Я действительно взволнован Laravel Spark . К тому времени, когда вы прочитаете это, вероятно, будет множество постов, объясняющих, как вы можете настроить это. Это не так интересно для меня, как путешествие, которое я собираюсь предпринять для создания реального бизнеса со Spark!

Идея проста. Я создал модуль Pagekit, который вы можете использовать для резервного копирования и восстановления данных сайта. Модуль позволяет легко сохранять и загружать эти резервные копии, а также восстанавливать их на разных серверах.

Проблема в том, что получение этих файлов резервных копий на удаленный сервер занимает много времени и немного хлопот. Мне часто хотелось, чтобы можно было быстро и безболезненно перенести это состояние приложения с одного сервера на другой и сделать автоматическое резервное копирование за пределы площадки. Поэтому я собираюсь настроить это для себя, и, возможно, другие найдут это достаточно полезным, чтобы заплатить за это.

Экран-заставка Spark

Начиная

Я использую Stripe и намерен иметь единый план без проб. Настройка для этого довольно проста, но я запомнил идентификатор плана. Мне нужно это, чтобы настроить план в Spark …

Экран приветствия в полоску

Затем я сбрасываю свои секретные и открытые ключи Stripe и обновляю до последней версии API (через тот же экран, https://dashboard.stripe.com/account/apikeys ).

Я забыл, что настройки в .env

У Spark есть несколько ожидаемых полей регистрации / профиля, но я хочу добавить еще несколько. Я хотел бы спросить пользователей, хотят ли они автоматическое резервное копирование, и я также хотел бы получить их платежный адрес, чтобы я мог указать его в своем счете. Сначала я должен создать миграцию для нового поля:

 php artisan make:migration add_should_backup_field

Для этого мы можем добавить столбец (убедитесь, что удалили его, если миграция откатывается):

 use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddShouldBackupField extends Migration
{
    public function up()
    {
        Schema::table("users", function (Blueprint $table) {
            $table->boolean("should_backup");
        });
    }

    public function down()
    {
        Schema::table("users", function (Blueprint $table) {
            $table->dropColumn("should_backup");
        });
    }
}

Сбор платежных адресов так же прост, как один вызов метода, в app/Providers/SparkServiceProvider.php

 /**
 * @inheritdoc
 */
public function booted()
{
    Spark::useStripe();

    Spark::collectBillingAddress();

    Spark::plan("[Stripe plan name]", "[Stripe plan ID]")
        ->price(4.99)
        ->features([
            "Backups"
        ]);
}

Это добавляет поля адреса выставления счета к форме регистрации:

Добавлено поле адреса выставления счета

Не забудьте настроить остальную часть SparkServiceProvider.php

Хранение резервных копий

Весь смысл этого приложения в том, чтобы дать авторам контента возможность загружать, перечислять и загружать свои резервные копии. Далее я создам модель Backup

 php artisan make:migration create_backups_table
php artisan make:model Backups

Таблица резервных копий нуждается в нескольких полях:

  1. Имя каждой резервной копии (возможно, содержит имя сайта, дату и включает ли оно базу данных и данные для загрузки).
  2. Загруженный файл резервной копии.
  3. Размер каждой резервной копии.
  4. Поле для связи резервной копии с пользователем, который ее создал.

Вот как выглядит настроенный файл миграции:

 use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;

class CreateBackupsTable extends Migration
{
    public function up()
    {
        Schema::create("backups", function (Blueprint $table) {
            $table->increments("id");
            $table->string("name");
            $table->string("file");
            $table->integer("size");
            $table->integer("user_id");
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists("backups");
    }
}

Учитывая эту структуру базы данных, я хочу предоставить набор конечных точек JSON, с помощью которых пользователи могут просматривать и управлять резервными копиями. Опять же, мы можем использовать команду make, чтобы начать работу:

 php artisan make:controller BackupsController --resource

Spark специально разработан, чтобы стать хорошей отправной точкой для создания аутентифицированного API. Когда вы входите в систему с учетной записью Spark, вы можете перейти в раздел, в котором вы можете создавать токены API:

С помощью этого токена и создав контроллер ресурсов, я могу заставить метод index отображать некоторый отладочный текст. Тогда я могу сделать к нему запросы curl:

 curl -XGET http://localhost:8000/api/backup

Чтобы это работало, мне нужно определить маршрут API (в app/Http/api.php

 Route::group([
    "prefix" => "api",
    "middleware" => "auth:api"
], function () {
    Route::resource("backup", "BackupsController");
});

Поскольку я не предоставил параметр GET api_token Если я вошел в систему в то время, я перенаправлен обратно на экран приборной панели.

Если я определяю параметр api_token

Я выбрал простой механизм загрузки, который очень мало проверяет файлы (пока я работаю с прототипом). Я хочу уточнить это позже с некоторой проверкой, но сейчас у меня есть:

 public function store(Request $request)
{
    $file = $request->file("file");
    $name = $request->get("name");

    if (!$file || !$name) {
        return [
            "status" => "error",
            "error" => "file or name missing",
        ];
    }

    $fileExtension = "." . $file->getClientOriginalExtension();
    $fileName = str_slug(time() . " " . $name) . $fileExtension;

    try {
        $file->move(storage_path("backups"), $fileName);
    } catch (Exception $exception) {
        return [
            "status" => "error",
            "error" => "could not store file",
        ];
    }

    $backup = Backup::create([
        "name" => $name,
        "file" => $fileName,
        "size" => $file->getClientSize(),
        "user_id" => Auth::user()->id,
    ]);

    return $backup;
}

Я могу проверить это, создав пустой файл резервной копии и затем отправив его в store

 touch backup.zip
curl
    -X POST
    -F name="My First Backup"
    -F file=@backup.zip
    "http://localhost:8000/api/backup?api_token=[my token]"

Я получаю обратно что-то похожее:

 {
    "name": "My First Backup",
    "file": "1461802753-my-first-backup.zip",
    "size": 0,
    "user_id": 1,
    "updated_at": "2016-04-28 00:19:13",
    "created_at": "2016-04-28 00:19:13",
    "id": 8
}

Обратите внимание, как файл называется по-разному, чтобы избежать коллизий? Он на самом деле назван в честь имени резервной копии, а не исходного имени файла, и я добавил временную метку, чтобы можно было загружать резервные копии с одинаковыми именами без конфликта имен.

Существует еще условие гонки, когда пользователи загружают резервные копии с одинаковым именем в одну секунду. Риск этого меньше, и я готов иметь дело с этим, когда это становится проблемой.

Теперь я могу настроить действие index

 public function index()
{
    return [
        "status" => "ok",
        "data" => Auth::user()->backups,
    ];
}

Мне также пришлось создать эти отношения между пользователями и резервными копиями, добавив метод backupsUser

 public function backups()
{
    return $this->hasMany(Backup::class);
}

Загрузка резервных копий

Я загружаю резервные копии в непубличную папку по причине. Я не хочу, чтобы пользователи могли угадывать (независимо от того, насколько маловероятно, что они догадаются правильно), как получить доступ к резервным копиям других пользователей.

Поэтому я должен создать специальную конечную точку загрузки. Я могу, однако, отыграть от действия show

 public function show($id)
{
    $backup = Auth::user()->backups()->findOrFail($id);
    $path = storage_path("backups/". $backup->file);

    return response()->download($path, $backup->file);
}

Из-за того, как определяется область действия запроса, пользователям будет разрешено загружать только резервные копии, связанные с их учетной записью, и эти резервные копии не будут общедоступными за пределами этого одного маршрута.

Вывод

За невероятно короткий промежуток времени мне удалось использовать Spark для создания хостинговой платформы на основе подписки с аутентифицированным API для хранения и загрузки файлов резервных копий.

Предстоит проделать еще много работы, начиная от приложения и заканчивая повышением безопасности, внедрением ограничений хранилища и удалением резервной копии. Тем не менее, я воодушевлен началом, которое я сделал, и я могу вернуться к этому приложению в будущих уроках, так как я узнаю больше о Spark!

У вас есть какие-либо вопросы по поводу этого кода или комментарии о том, что бы вы сделали по-другому? Дайте нам знать об этом в комментариях!