|
|
|
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Models\Category;
|
|
|
|
|
use App\Models\MakerWorldCollection;
|
|
|
|
|
use App\Models\Product;
|
|
|
|
|
use App\Services\MakerWorldService;
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
@@ -18,17 +19,22 @@ class MakerWorldController extends Controller
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Display the MakerWorld importer dashboard.
|
|
|
|
|
* Display the MakerWorld importer & collections dashboard.
|
|
|
|
|
*/
|
|
|
|
|
public function index(Request $request): View
|
|
|
|
|
{
|
|
|
|
|
$isAuthenticated = $this->makerWorld->isAuthenticated();
|
|
|
|
|
$userProfile = $this->makerWorld->getUserProfile();
|
|
|
|
|
|
|
|
|
|
$tab = $request->query('tab', $isAuthenticated ? 'favorites' : 'community');
|
|
|
|
|
$tab = $request->query('tab', 'collections');
|
|
|
|
|
$models = [];
|
|
|
|
|
$apiCollections = [];
|
|
|
|
|
|
|
|
|
|
if ($tab === 'favorites' && $isAuthenticated) {
|
|
|
|
|
if ($tab === 'collections') {
|
|
|
|
|
if ($isAuthenticated) {
|
|
|
|
|
$apiCollections = $this->makerWorld->getFavoritesCollections();
|
|
|
|
|
}
|
|
|
|
|
} elseif ($tab === 'favorites' && $isAuthenticated) {
|
|
|
|
|
$models = $this->makerWorld->getFavorites();
|
|
|
|
|
} elseif ($tab === 'published' && $isAuthenticated) {
|
|
|
|
|
$models = $this->makerWorld->getPublished();
|
|
|
|
@@ -36,15 +42,11 @@ class MakerWorldController extends Controller
|
|
|
|
|
$models = $this->makerWorld->getCommunityShowcase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$importedProducts = Product::with('category')
|
|
|
|
|
->whereNotNull('makerworld_id')
|
|
|
|
|
$trackedCollections = MakerWorldCollection::withCount(['products', 'draftProducts', 'publishedProducts'])
|
|
|
|
|
->latest()
|
|
|
|
|
->paginate(15);
|
|
|
|
|
|
|
|
|
|
$importedIds = Product::whereNotNull('makerworld_id')
|
|
|
|
|
->pluck('makerworld_id')
|
|
|
|
|
->toArray();
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$stagedCount = Product::where('status', 'draft')->count();
|
|
|
|
|
$categories = Category::orderBy('name')->get();
|
|
|
|
|
|
|
|
|
|
return view('admin.makerworld.index', [
|
|
|
|
@@ -52,12 +54,266 @@ class MakerWorldController extends Controller
|
|
|
|
|
'userProfile' => $userProfile,
|
|
|
|
|
'currentTab' => $tab,
|
|
|
|
|
'models' => $models,
|
|
|
|
|
'importedProducts' => $importedProducts,
|
|
|
|
|
'importedIds' => $importedIds,
|
|
|
|
|
'apiCollections' => $apiCollections,
|
|
|
|
|
'trackedCollections' => $trackedCollections,
|
|
|
|
|
'stagedCount' => $stagedCount,
|
|
|
|
|
'categories' => $categories,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Display the "Artikel Beheer" staging area where imported models wait for review.
|
|
|
|
|
*/
|
|
|
|
|
public function staging(Request $request): View
|
|
|
|
|
{
|
|
|
|
|
$query = Product::with(['category', 'makerworldCollection'])
|
|
|
|
|
->where('status', 'draft');
|
|
|
|
|
|
|
|
|
|
if ($request->filled('collection_id')) {
|
|
|
|
|
$query->where('makerworld_collection_id', $request->input('collection_id'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('q')) {
|
|
|
|
|
$q = $request->input('q');
|
|
|
|
|
$query->where(function ($sub) use ($q) {
|
|
|
|
|
$sub->where('name', 'like', "%{$q}%")
|
|
|
|
|
->orWhere('makerworld_author', 'like', "%{$q}%")
|
|
|
|
|
->orWhere('makerworld_id', 'like', "%{$q}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$stagedProducts = $query->latest()->paginate(24)->withQueryString();
|
|
|
|
|
$categories = Category::orderBy('name')->get();
|
|
|
|
|
$collections = MakerWorldCollection::all();
|
|
|
|
|
$totalStaged = Product::where('status', 'draft')->count();
|
|
|
|
|
|
|
|
|
|
return view('admin.makerworld.staging', compact('stagedProducts', 'categories', 'collections', 'totalStaged'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a MakerWorld collection to be tracked and auto-synced.
|
|
|
|
|
*/
|
|
|
|
|
public function addCollection(Request $request): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'collection_input' => 'required|string',
|
|
|
|
|
'is_active_sync' => 'nullable|boolean',
|
|
|
|
|
'sync_now' => 'nullable|boolean',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$input = trim($validated['collection_input']);
|
|
|
|
|
$parsed = $this->makerWorld->parseCollectionIdentifier($input);
|
|
|
|
|
|
|
|
|
|
$collectionId = $parsed['id'];
|
|
|
|
|
$userHandle = $parsed['handle'] ?? 'user_1718037659';
|
|
|
|
|
|
|
|
|
|
// If user entered a user collections URL or handle without specific collection ID
|
|
|
|
|
if ($parsed['type'] === 'user_collections' && ! $collectionId) {
|
|
|
|
|
// Attempt to fetch collection list for user
|
|
|
|
|
$userCollections = $this->makerWorld->getFavoritesCollections();
|
|
|
|
|
|
|
|
|
|
if (! empty($userCollections)) {
|
|
|
|
|
$addedCount = 0;
|
|
|
|
|
foreach ($userCollections as $c) {
|
|
|
|
|
$cId = (string) ($c['id'] ?? '');
|
|
|
|
|
if ($cId !== '') {
|
|
|
|
|
$collection = MakerWorldCollection::firstOrCreate(
|
|
|
|
|
['makerworld_collection_id' => $cId],
|
|
|
|
|
[
|
|
|
|
|
'name' => $c['title'] ?? 'MakerWorld Collectie #'.$cId,
|
|
|
|
|
'description' => $c['description'] ?? null,
|
|
|
|
|
'cover_url' => is_array($c['designCover'] ?? null) ? ($c['designCover'][0] ?? null) : ($c['cover'] ?? null),
|
|
|
|
|
'user_handle' => $userHandle,
|
|
|
|
|
'source_url' => $input,
|
|
|
|
|
'is_active_sync' => true,
|
|
|
|
|
'sync_interval_minutes' => 10,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
$this->makerWorld->syncCollection($collection);
|
|
|
|
|
$addedCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', "{$addedCount} collecties van @{$userHandle} succesvol gekoppeld en gesynchroniseerd!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('warning', "Gebruikersprofiel @{$userHandle} herkend. Voer a.u.b. een specifieke collectie-link of ID in (bijv. https://makerworld.com/nl/collections/123456) of log in bij MakerWorld.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! $collectionId) {
|
|
|
|
|
return redirect()->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('error', 'Kon geen geldige MakerWorld collectie ID of URL herkennen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch collection details from MakerWorld
|
|
|
|
|
$details = $this->makerWorld->getCollectionDetails($collectionId);
|
|
|
|
|
$name = $details['title'] ?? 'MakerWorld Collectie #'.$collectionId;
|
|
|
|
|
$cover = is_array($details['designCover'] ?? null) ? ($details['designCover'][0] ?? null) : ($details['cover'] ?? null);
|
|
|
|
|
$desc = $details['description'] ?? null;
|
|
|
|
|
|
|
|
|
|
$collection = MakerWorldCollection::updateOrCreate(
|
|
|
|
|
['makerworld_collection_id' => (string) $collectionId],
|
|
|
|
|
[
|
|
|
|
|
'name' => $name,
|
|
|
|
|
'description' => $desc,
|
|
|
|
|
'cover_url' => $cover,
|
|
|
|
|
'user_handle' => $userHandle,
|
|
|
|
|
'source_url' => $input,
|
|
|
|
|
'is_active_sync' => $request->boolean('is_active_sync', true),
|
|
|
|
|
'sync_interval_minutes' => 10,
|
|
|
|
|
'raw_metadata' => $details,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$msg = "Collectie '{$collection->name}' is succesvol toegevoegd!";
|
|
|
|
|
|
|
|
|
|
if ($request->boolean('sync_now', true)) {
|
|
|
|
|
$syncResult = $this->makerWorld->syncCollection($collection);
|
|
|
|
|
$msg .= " Direct {$syncResult['imported']} modellen binnengehaald als concept voor Artikel Beheer.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', $msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggle active 10-minute sync for a collection.
|
|
|
|
|
*/
|
|
|
|
|
public function toggleCollectionSync(MakerWorldCollection $collection): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$collection->update([
|
|
|
|
|
'is_active_sync' => ! $collection->is_active_sync,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$statusStr = $collection->is_active_sync ? 'geactiveerd (elke 10 min)' : 'gepauzeerd';
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "Automatische synchronisatie voor '{$collection->name}' is {$statusStr}.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Trigger immediate synchronization for a collection.
|
|
|
|
|
*/
|
|
|
|
|
public function syncCollectionNow(MakerWorldCollection $collection): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$result = $this->makerWorld->syncCollection($collection);
|
|
|
|
|
|
|
|
|
|
if ($result['error']) {
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('error', "Synchronisatie mislukt: {$result['error']}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "Collectie '{$collection->name}' gesynchroniseerd: {$result['imported']} nieuwe modellen binnengehaald, {$result['updated']} bijgewerkt.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove a tracked collection.
|
|
|
|
|
*/
|
|
|
|
|
public function deleteCollection(MakerWorldCollection $collection): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$name = $collection->name;
|
|
|
|
|
$collection->delete();
|
|
|
|
|
|
|
|
|
|
return redirect()->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', "Collectie '{$name}' is verwijderd uit de synchronisatie.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Publish a single staged model to the active webshop catalog.
|
|
|
|
|
*/
|
|
|
|
|
public function publishStaged(Request $request, Product $product): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'name' => 'required|string|max:255',
|
|
|
|
|
'category_id' => 'required|exists:categories,id',
|
|
|
|
|
'base_price' => 'required|numeric|min:0.5',
|
|
|
|
|
'short_description' => 'nullable|string|max:500',
|
|
|
|
|
'is_featured' => 'nullable|boolean',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$product->update([
|
|
|
|
|
'name' => $validated['name'],
|
|
|
|
|
'category_id' => $validated['category_id'],
|
|
|
|
|
'base_price' => $validated['base_price'],
|
|
|
|
|
'short_description' => $validated['short_description'] ?? $product->short_description,
|
|
|
|
|
'is_featured' => $request->boolean('is_featured', false),
|
|
|
|
|
'status' => 'published',
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "Artikel '{$product->name}' is succesvol goedgekeurd en gepubliceerd in de webshop!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bulk publish selected staged models.
|
|
|
|
|
*/
|
|
|
|
|
public function bulkPublishStaged(Request $request): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'product_ids' => 'required|array',
|
|
|
|
|
'product_ids.*' => 'integer|exists:products,id',
|
|
|
|
|
'category_id' => 'required|exists:categories,id',
|
|
|
|
|
'custom_markup' => 'nullable|numeric',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$products = Product::whereIn('id', $validated['product_ids'])
|
|
|
|
|
->where('status', 'draft')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
|
foreach ($products as $p) {
|
|
|
|
|
$price = $p->base_price;
|
|
|
|
|
if (! empty($validated['custom_markup']) && $validated['custom_markup'] > 0) {
|
|
|
|
|
$price = round($price * (1 + ($validated['custom_markup'] / 100)), 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$p->update([
|
|
|
|
|
'category_id' => $validated['category_id'],
|
|
|
|
|
'base_price' => $price,
|
|
|
|
|
'status' => 'published',
|
|
|
|
|
'is_active' => true,
|
|
|
|
|
]);
|
|
|
|
|
$count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "{$count} artikelen succesvol gepubliceerd in de webshop!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reject and delete a staged model.
|
|
|
|
|
*/
|
|
|
|
|
public function rejectStaged(Product $product): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$name = $product->name;
|
|
|
|
|
$product->delete();
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "Concept '{$name}' is verwijderd.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bulk reject staged models.
|
|
|
|
|
*/
|
|
|
|
|
public function bulkRejectStaged(Request $request): RedirectResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'product_ids' => 'required|array',
|
|
|
|
|
'product_ids.*' => 'integer|exists:products,id',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$count = Product::whereIn('id', $validated['product_ids'])
|
|
|
|
|
->where('status', 'draft')
|
|
|
|
|
->delete();
|
|
|
|
|
|
|
|
|
|
return redirect()->back()
|
|
|
|
|
->with('success', "{$count} concepten zijn verwijderd.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Authenticate with MakerWorld credentials.
|
|
|
|
|
*/
|
|
|
|
@@ -75,7 +331,7 @@ class MakerWorldController extends Controller
|
|
|
|
|
|
|
|
|
|
if ($result['status'] === 'success') {
|
|
|
|
|
return redirect()
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'favorites'])
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', $result['message']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -108,7 +364,7 @@ class MakerWorldController extends Controller
|
|
|
|
|
|
|
|
|
|
if ($result['status'] === 'success') {
|
|
|
|
|
return redirect()
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'favorites'])
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', $result['message']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -130,7 +386,7 @@ class MakerWorldController extends Controller
|
|
|
|
|
$this->makerWorld->setAccessToken($validated['token']);
|
|
|
|
|
|
|
|
|
|
return redirect()
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'favorites'])
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', 'MakerWorld token succesvol opgeslagen en verbonden!');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -142,7 +398,7 @@ class MakerWorldController extends Controller
|
|
|
|
|
$this->makerWorld->logout();
|
|
|
|
|
|
|
|
|
|
return redirect()
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'community'])
|
|
|
|
|
->route('admin.makerworld.index', ['tab' => 'collections'])
|
|
|
|
|
->with('success', 'MakerWorld account succesvol ontkoppeld.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -191,6 +447,7 @@ class MakerWorldController extends Controller
|
|
|
|
|
'custom_price' => 'nullable|numeric|min:0.5',
|
|
|
|
|
'custom_name' => 'nullable|string|max:255',
|
|
|
|
|
'is_active' => 'nullable|boolean',
|
|
|
|
|
'status' => 'nullable|string|in:draft,published',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$details = $this->makerWorld->getDesignDetails($validated['makerworld_id']);
|
|
|
|
@@ -204,15 +461,18 @@ class MakerWorldController extends Controller
|
|
|
|
|
return redirect()->back()->with('error', $msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$status = $validated['status'] ?? ($request->boolean('is_active', true) ? 'published' : 'draft');
|
|
|
|
|
|
|
|
|
|
$product = $this->makerWorld->importDesign(
|
|
|
|
|
$details,
|
|
|
|
|
$validated['category_id'] ?? null,
|
|
|
|
|
$validated['custom_price'] ?? null,
|
|
|
|
|
$validated['custom_name'] ?? null,
|
|
|
|
|
$request->boolean('is_active', true)
|
|
|
|
|
$status === 'published',
|
|
|
|
|
$status
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$msg = "Model \"{$product->name}\" is succesvol toegevoegd aan je webshop!";
|
|
|
|
|
$msg = "Model \"{$product->name}\" is succesvol toegevoegd!";
|
|
|
|
|
|
|
|
|
|
if ($request->wantsJson()) {
|
|
|
|
|
return response()->json([
|
|
|
|
@@ -249,12 +509,13 @@ class MakerWorldController extends Controller
|
|
|
|
|
$categoryId,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
$isActive
|
|
|
|
|
$isActive,
|
|
|
|
|
$isActive ? 'published' : 'draft'
|
|
|
|
|
);
|
|
|
|
|
$count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect()->back()->with('success', "{$count} modellen succesvol geïmporteerd naar je webshop!");
|
|
|
|
|
return redirect()->back()->with('success', "{$count} modellen succesvol geïmporteerd!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|