PostPost

Laravel

Build social media features into your Laravel app with PostPost API.

Installation

composer require guzzlehttp/guzzle

Configuration

// config/services.php
return [
    // ...
    'postpost' => [
        'api_key' => env('PUBLORA_API_KEY'),
        'base_url' => env('PUBLORA_BASE_URL', 'https://api.postpost.dev/api/v1'),
    ],
];
# .env
PUBLORA_API_KEY=sk_your_api_key_here

PostPost Service Class

<?php
// app/Services/PostPostService.php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\RequestException;

class PostPostException extends \Exception
{
    public int $statusCode;
    public array $body;

    public function __construct(int $statusCode, string $message, array $body = [])
    {
        parent::__construct($message);
        $this->statusCode = $statusCode;
        $this->body = $body;
    }
}

class PostPostService
{
    protected string $apiKey;
    protected string $baseUrl;
    protected ?string $userId = null;

    public function __construct()
    {
        $this->apiKey = config('services.postpost.api_key');
        $this->baseUrl = config('services.postpost.base_url');
    }

    public function forUser(string $userId): self
    {
        $this->userId = $userId;
        return $this;
    }

    protected function request(string $method, string $endpoint, array $data = [])
    {
        $headers = [
            'x-api-key' => $this->apiKey,
        ];

        if ($this->userId) {
            $headers['x-postpost-user-id'] = $this->userId;
        }

        $response = Http::withHeaders($headers)
            ->$method($this->baseUrl . $endpoint, $data);

        if ($response->failed()) {
            $body = $response->json() ?? [];
            $message = $body['error'] ?? $body['message'] ?? 'API request failed';
            throw new PostPostException($response->status(), $message, $body);
        }

        return $response->json();
    }

    public function getConnections(): array
    {
        $result = $this->request('get', '/platform-connections');
        return $result['connections'] ?? [];
    }

    public function createPost(array $data): array
    {
        return $this->request('post', '/create-post', $data);
    }

    public function getPost(string $postGroupId): array
    {
        return $this->request('get', "/get-post/{$postGroupId}");
    }

    public function updatePost(string $postGroupId, array $updates): array
    {
        return $this->request('put', "/update-post/{$postGroupId}", $updates);
    }

    public function deletePost(string $postGroupId): array
    {
        return $this->request('delete', "/delete-post/{$postGroupId}");
    }

    public function getUploadUrl(string $fileName, string $contentType, string $postGroupId): array
    {
        return $this->request('post', '/get-upload-url', [
            'fileName' => $fileName,
            'contentType' => $contentType,
            'postGroupId' => $postGroupId,
        ]);
    }

    public function getLinkedInStats(string $platformId, string $postedId): array
    {
        return $this->request('post', '/linkedin-post-statistics', [
            'platformId' => $platformId,
            'postedId' => $postedId,
            'queryTypes' => 'ALL',
        ]);
    }
}

Service Provider

<?php
// app/Providers/PostPostServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\PostPostService;

class PostPostServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(PostPostService::class, function ($app) {
            return new PostPostService();
        });
    }

    public function boot()
    {
        //
    }
}

Models

<?php
// app/Models/SocialPost.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Services\PostPostService;

class SocialPost extends Model
{
    protected $fillable = [
        'user_id',
        'post_group_id',
        'content',
        'platforms',
        'scheduled_time',
        'status',
    ];

    protected $casts = [
        'platforms' => 'array',
        'scheduled_time' => 'datetime',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function refreshStatus(): void
    {
        try {
            $postpost = app(PostPostService::class);
            $data = $postpost->getPost($this->post_group_id);
            $this->update(['status' => $data['status'] ?? $this->status]);
        } catch (\Exception $e) {
            \Log::error("Failed to refresh post status: {$e->getMessage()}");
        }
    }
}
<?php
// database/migrations/create_social_posts_table.php

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

return new class extends Migration
{
    public function up()
    {
        Schema::create('social_posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('post_group_id')->unique();
            $table->text('content');
            $table->json('platforms');
            $table->timestamp('scheduled_time')->nullable();
            $table->string('status')->default('draft');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('social_posts');
    }
};

Controllers

<?php
// app/Http/Controllers/Api/SocialController.php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\SocialPost;
use App\Services\PostPostService;
use App\Services\PostPostException;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class SocialController extends Controller
{
    protected PostPostService $postpost;

    public function __construct(PostPostService $postpost)
    {
        $this->postpost = $postpost;
    }

    public function connections(): JsonResponse
    {
        try {
            $connections = $this->postpost->getConnections();
            return response()->json(['connections' => $connections]);
        } catch (PostPostException $e) {
            return response()->json(['error' => $e->getMessage()], $e->statusCode);
        }
    }

    public function createPost(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'content' => 'required|string|max:10000',
            'platforms' => 'required|array|min:1',
            'platforms.*' => 'string',
            'scheduled_time' => 'nullable|date|after:now',
        ]);

        try {
            $postData = [
                'content' => $validated['content'],
                'platforms' => $validated['platforms'],
            ];

            if (!empty($validated['scheduled_time'])) {
                $postData['scheduledTime'] = $validated['scheduled_time'];
            }

            $result = $this->postpost->createPost($postData);

            // Save to local database
            $post = SocialPost::create([
                'user_id' => auth()->id(),
                'post_group_id' => $result['postGroupId'],
                'content' => $validated['content'],
                'platforms' => $validated['platforms'],
                'scheduled_time' => $validated['scheduled_time'] ?? null,
                'status' => 'scheduled',
            ]);

            return response()->json([
                'success' => true,
                'post_group_id' => $result['postGroupId'],
                'post' => $post,
            ], 201);
        } catch (PostPostException $e) {
            return response()->json(['error' => $e->getMessage()], $e->statusCode);
        }
    }

    public function getPost(string $postGroupId): JsonResponse
    {
        try {
            $result = $this->postpost->getPost($postGroupId);
            return response()->json($result);
        } catch (PostPostException $e) {
            return response()->json(['error' => $e->getMessage()], $e->statusCode);
        }
    }

    public function deletePost(string $postGroupId): JsonResponse
    {
        try {
            $result = $this->postpost->deletePost($postGroupId);

            // Delete from local database
            SocialPost::where('user_id', auth()->id())
                ->where('post_group_id', $postGroupId)
                ->delete();

            return response()->json($result);
        } catch (PostPostException $e) {
            return response()->json(['error' => $e->getMessage()], $e->statusCode);
        }
    }

    public function userPosts(): JsonResponse
    {
        $posts = SocialPost::where('user_id', auth()->id())
            ->orderBy('created_at', 'desc')
            ->paginate(20);

        return response()->json($posts);
    }
}

Routes

<?php
// routes/api.php

use App\Http\Controllers\Api\SocialController;

Route::middleware('auth:sanctum')->group(function () {
    Route::prefix('social')->group(function () {
        Route::get('connections', [SocialController::class, 'connections']);
        Route::get('posts', [SocialController::class, 'userPosts']);
        Route::post('posts', [SocialController::class, 'createPost']);
        Route::get('posts/{postGroupId}', [SocialController::class, 'getPost']);
        Route::delete('posts/{postGroupId}', [SocialController::class, 'deletePost']);
    });
});

Form Request Validation

<?php
// app/Http/Requests/CreateSocialPostRequest.php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreateSocialPostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'content' => 'required|string|max:10000',
            'platforms' => 'required|array|min:1',
            'platforms.*' => 'string|regex:/^[a-z]+-[A-Za-z0-9]+$/',
            'scheduled_time' => 'nullable|date|after:now',
            'platform_settings' => 'nullable|array',
        ];
    }

    public function messages(): array
    {
        return [
            'platforms.*.regex' => 'Platform ID must be in format: platform-id (e.g., twitter-123456)',
            'scheduled_time.after' => 'Scheduled time must be in the future',
        ];
    }
}

Jobs for Background Processing

<?php
// app/Jobs/ScheduleSocialPost.php

namespace App\Jobs;

use App\Models\SocialPost;
use App\Services\PostPostService;
use App\Services\PostPostException;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ScheduleSocialPost implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 60;

    public function __construct(
        protected SocialPost $post
    ) {}

    public function handle(PostPostService $postpost): void
    {
        try {
            $result = $postpost->createPost([
                'content' => $this->post->content,
                'platforms' => $this->post->platforms,
                'scheduledTime' => $this->post->scheduled_time?->toIso8601String(),
            ]);

            $this->post->update([
                'post_group_id' => $result['postGroupId'],
                'status' => 'scheduled',
            ]);
        } catch (PostPostException $e) {
            $this->post->update(['status' => 'failed']);
            throw $e;
        }
    }
}
<?php
// app/Jobs/RefreshPostStatuses.php

namespace App\Jobs;

use App\Models\SocialPost;
use App\Services\PostPostService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class RefreshPostStatuses implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(PostPostService $postpost): void
    {
        $pendingPosts = SocialPost::whereIn('status', ['scheduled', 'processing'])->get();

        foreach ($pendingPosts as $post) {
            try {
                $data = $postpost->getPost($post->post_group_id);
                $post->update(['status' => $data['status'] ?? $post->status]);
            } catch (\Exception $e) {
                \Log::warning("Failed to refresh post {$post->post_group_id}: {$e->getMessage()}");
            }

            usleep(200000); // Rate limiting: 200ms between requests
        }
    }
}

Scheduled Task

<?php
// app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
    $schedule->job(new \App\Jobs\RefreshPostStatuses)
        ->everyFiveMinutes()
        ->withoutOverlapping();
}

Livewire Component

<?php
// app/Livewire/SocialPostForm.php

namespace App\Livewire;

use Livewire\Component;
use App\Services\PostPostService;
use App\Services\PostPostException;
use App\Models\SocialPost;

class SocialPostForm extends Component
{
    public string $content = '';
    public array $selectedPlatforms = [];
    public ?string $scheduledTime = null;
    public array $connections = [];
    public ?string $successMessage = null;
    public ?string $errorMessage = null;

    public function mount(PostPostService $postpost)
    {
        try {
            $this->connections = $postpost->getConnections();
        } catch (PostPostException $e) {
            $this->errorMessage = 'Failed to load connections';
        }
    }

    public function togglePlatform(string $platformId)
    {
        if (in_array($platformId, $this->selectedPlatforms)) {
            $this->selectedPlatforms = array_diff($this->selectedPlatforms, [$platformId]);
        } else {
            $this->selectedPlatforms[] = $platformId;
        }
    }

    public function submit(PostPostService $postpost)
    {
        $this->validate([
            'content' => 'required|string|max:10000',
            'selectedPlatforms' => 'required|array|min:1',
        ]);

        try {
            $postData = [
                'content' => $this->content,
                'platforms' => array_values($this->selectedPlatforms),
            ];

            if ($this->scheduledTime) {
                $postData['scheduledTime'] = $this->scheduledTime;
            }

            $result = $postpost->createPost($postData);

            SocialPost::create([
                'user_id' => auth()->id(),
                'post_group_id' => $result['postGroupId'],
                'content' => $this->content,
                'platforms' => $this->selectedPlatforms,
                'scheduled_time' => $this->scheduledTime,
                'status' => 'scheduled',
            ]);

            $this->successMessage = "Post created: {$result['postGroupId']}";
            $this->reset(['content', 'selectedPlatforms', 'scheduledTime']);
            $this->errorMessage = null;

        } catch (PostPostException $e) {
            $this->errorMessage = $e->getMessage();
            $this->successMessage = null;
        }
    }

    public function render()
    {
        return view('livewire.social-post-form');
    }
}
<!-- resources/views/livewire/social-post-form.blade.php -->
<div class="max-w-2xl mx-auto p-4">
    <h2 class="text-xl font-bold mb-4">Create Social Post</h2>

    @if($successMessage)
        <div class="bg-green-100 text-green-800 p-3 rounded mb-4">
            {{ $successMessage }}
        </div>
    @endif

    @if($errorMessage)
        <div class="bg-red-100 text-red-800 p-3 rounded mb-4">
            {{ $errorMessage }}
        </div>
    @endif

    <form wire:submit="submit">
        <div class="mb-4">
            <label class="block text-sm font-medium mb-1">Content</label>
            <textarea
                wire:model="content"
                class="w-full p-2 border rounded"
                rows="4"
            ></textarea>
            <p class="text-sm text-gray-500 mt-1">{{ strlen($content) }} characters</p>
            @error('content') <p class="text-red-500 text-sm">{{ $message }}</p> @enderror
        </div>

        <div class="mb-4">
            <label class="block text-sm font-medium mb-1">Platforms</label>
            <div class="flex flex-wrap gap-2">
                @foreach($connections as $conn)
                    <button
                        type="button"
                        wire:click="togglePlatform('{{ $conn['platformId'] }}')"
                        class="px-3 py-1 rounded {{ in_array($conn['platformId'], $selectedPlatforms) ? 'bg-blue-500 text-white' : 'bg-gray-200' }}"
                    >
                        {{ $conn['platform'] }}: {{ $conn['username'] }}
                    </button>
                @endforeach
            </div>
            @error('selectedPlatforms') <p class="text-red-500 text-sm">{{ $message }}</p> @enderror
        </div>

        <div class="mb-4">
            <label class="block text-sm font-medium mb-1">Schedule (optional)</label>
            <input
                type="datetime-local"
                wire:model="scheduledTime"
                class="p-2 border rounded"
            >
        </div>

        <button
            type="submit"
            class="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
            wire:loading.attr="disabled"
        >
            <span wire:loading.remove>Schedule Post</span>
            <span wire:loading>Posting...</span>
        </button>
    </form>
</div>

Artisan Command

<?php
// app/Console/Commands/SyncConnections.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\PostPostService;
use App\Services\PostPostException;

class SyncConnections extends Command
{
    protected $signature = 'postpost:sync-connections';
    protected $description = 'Sync platform connections from PostPost';

    public function handle(PostPostService $postpost): int
    {
        try {
            $connections = $postpost->getConnections();

            $this->info("Found " . count($connections) . " connections:");

            foreach ($connections as $conn) {
                $this->line("  - {$conn['platform']}: {$conn['username']} ({$conn['platformId']})");
            }

            return Command::SUCCESS;
        } catch (PostPostException $e) {
            $this->error("Failed: {$e->getMessage()}");
            return Command::FAILURE;
        }
    }
}

PostPost — Social media API with free tier, paid plans from $2.99/account

On this page