Add BackPRO News theme and update database schema for article tracking
- Introduced a new WordPress theme "BackPRO News" with a lightweight magazine-style design. - Added columns for tracking retry attempts and timestamps for unpublished/generated articles in the articles table. - Included remote service metadata fields in the sites table for better management. - Created log files for image replacements, installer actions, OpenAI article generation, and publishing processes. - Implemented a dashboard template for site management, including permalink settings and theme installation options.
This commit is contained in:
@@ -24,13 +24,13 @@ class PublisherService
|
||||
|
||||
public function publishNext(): array
|
||||
{
|
||||
Logger::info('Rozpoczynam automatyczną publikację', 'publish');
|
||||
Logger::info('Rozpoczynam automatyczna publikacje', 'publish');
|
||||
|
||||
$sites = Site::findDueForPublishing();
|
||||
|
||||
if (empty($sites)) {
|
||||
Logger::info('Brak stron do publikacji', 'publish');
|
||||
return ['success' => false, 'message' => 'Brak stron wymagających publikacji.'];
|
||||
return ['success' => false, 'message' => 'Brak stron wymagajacych publikacji.'];
|
||||
}
|
||||
|
||||
$site = $sites[0];
|
||||
@@ -41,19 +41,42 @@ class PublisherService
|
||||
{
|
||||
Logger::info("Publikacja dla strony: {$site['name']} (ID: {$site['id']})", 'publish');
|
||||
|
||||
// 1. Select topic
|
||||
// 1. Najpierw publikuj gotowe, nieopublikowane artykuly.
|
||||
$retryArticle = Article::findNextRetryableBySite((int) $site['id']);
|
||||
if ($retryArticle) {
|
||||
$topic = Topic::find((int) $retryArticle['topic_id']);
|
||||
if (!$topic) {
|
||||
Logger::error("Nie znaleziono tematu dla artykulu ID {$retryArticle['id']}", 'publish');
|
||||
return ['success' => false, 'message' => 'Nie znaleziono tematu dla oczekujacego artykulu.'];
|
||||
}
|
||||
|
||||
Logger::info("Ponowna proba publikacji artykulu ID {$retryArticle['id']}: {$retryArticle['title']}", 'publish');
|
||||
Article::markRetryAttempt((int) $retryArticle['id']);
|
||||
|
||||
return $this->publishPreparedArticle(
|
||||
$site,
|
||||
$topic,
|
||||
[
|
||||
'title' => (string) $retryArticle['title'],
|
||||
'content' => (string) $retryArticle['content'],
|
||||
'model' => $retryArticle['ai_model'] ?? null,
|
||||
'prompt' => $retryArticle['prompt_used'] ?? null,
|
||||
],
|
||||
(int) $retryArticle['id']
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Gdy brak zaleglych, generuj nowy artykul.
|
||||
$topic = $this->topicBalancer->getNextTopic($site['id']);
|
||||
if (!$topic) {
|
||||
Logger::error("Brak aktywnych tematów dla strony {$site['name']}", 'publish');
|
||||
return ['success' => false, 'message' => "Brak aktywnych tematów dla strony {$site['name']}."];
|
||||
Logger::error("Brak aktywnych tematow dla strony {$site['name']}", 'publish');
|
||||
return ['success' => false, 'message' => "Brak aktywnych tematow dla strony {$site['name']}."];
|
||||
}
|
||||
|
||||
Logger::info("Wybrany temat: {$topic['name']} (ID: {$topic['id']})", 'publish');
|
||||
|
||||
// 2. Get existing titles to avoid repetition
|
||||
$existingTitles = Article::getRecentTitlesByTopic($topic['id'], 20);
|
||||
$existingTitles = Article::getRecentTitlesByTopic((int) $topic['id'], 20);
|
||||
|
||||
// 3. Generate article
|
||||
$article = $this->openAI->generateArticle(
|
||||
$topic['name'],
|
||||
$topic['description'] ?? '',
|
||||
@@ -61,16 +84,22 @@ class PublisherService
|
||||
);
|
||||
|
||||
if (!$article) {
|
||||
$this->saveFailedArticle($site, $topic, 'Nie udało się wygenerować artykułu przez OpenAI.');
|
||||
return ['success' => false, 'message' => 'Błąd generowania artykułu przez AI.'];
|
||||
$this->saveFailedArticle($site, $topic, 'Nie udalo sie wygenerowac artykulu przez OpenAI.');
|
||||
return ['success' => false, 'message' => 'Blad generowania artykulu przez AI.'];
|
||||
}
|
||||
|
||||
Logger::info("Wygenerowano artykuł: {$article['title']}", 'publish');
|
||||
Logger::info("Wygenerowano artykul: {$article['title']}", 'publish');
|
||||
|
||||
// 4. Generate/fetch image
|
||||
$article['title'] = $this->normalizeArticleTitle((string) ($article['title'] ?? ''), (string) $topic['name']);
|
||||
|
||||
return $this->publishPreparedArticle($site, $topic, $article);
|
||||
}
|
||||
|
||||
private function publishPreparedArticle(array $site, array $topic, array $article, ?int $existingArticleId = null): array
|
||||
{
|
||||
$imageUrl = null;
|
||||
$mediaId = null;
|
||||
$image = $this->imageService->generate($article['title'], $topic['name']);
|
||||
$image = $this->imageService->generate((string) $article['title'], (string) $topic['name']);
|
||||
|
||||
if ($image) {
|
||||
$mediaId = $this->wordpress->uploadMedia($site, $image['data'], $image['filename']);
|
||||
@@ -78,42 +107,59 @@ class PublisherService
|
||||
Logger::info("Upload obrazka: media_id={$mediaId}", 'publish');
|
||||
}
|
||||
} else {
|
||||
Logger::warning('Nie udało się wygenerować obrazka, publikacja bez obrazka', 'publish');
|
||||
Logger::warning('Nie udalo sie wygenerowac obrazka, publikacja bez obrazka', 'publish');
|
||||
}
|
||||
|
||||
// 5. Publish to WordPress
|
||||
$wpPostId = $this->wordpress->createPost(
|
||||
$site,
|
||||
$article['title'],
|
||||
$article['content'],
|
||||
(string) $article['title'],
|
||||
(string) $article['content'],
|
||||
$topic['wp_category_id'],
|
||||
$mediaId
|
||||
);
|
||||
|
||||
if (!$wpPostId) {
|
||||
$this->saveFailedArticle($site, $topic, 'Nie udało się opublikować posta na WordPress.', $article);
|
||||
return ['success' => false, 'message' => 'Błąd publikacji na WordPress.'];
|
||||
$this->saveFailedArticle(
|
||||
$site,
|
||||
$topic,
|
||||
'Nie udalo sie opublikowac posta na WordPress.',
|
||||
$article,
|
||||
$existingArticleId
|
||||
);
|
||||
return ['success' => false, 'message' => 'Blad publikacji na WordPress.'];
|
||||
}
|
||||
|
||||
Logger::info("Opublikowano post: wp_post_id={$wpPostId}", 'publish');
|
||||
|
||||
// 6. Save article in database
|
||||
Article::create([
|
||||
'site_id' => $site['id'],
|
||||
'topic_id' => $topic['id'],
|
||||
'title' => $article['title'],
|
||||
'content' => $article['content'],
|
||||
'wp_post_id' => $wpPostId,
|
||||
'image_url' => $imageUrl,
|
||||
'status' => 'published',
|
||||
'ai_model' => $article['model'],
|
||||
'prompt_used' => $article['prompt'],
|
||||
'published_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
if ($existingArticleId !== null) {
|
||||
Article::update($existingArticleId, [
|
||||
'title' => (string) $article['title'],
|
||||
'content' => (string) $article['content'],
|
||||
'wp_post_id' => $wpPostId,
|
||||
'image_url' => $imageUrl,
|
||||
'status' => 'published',
|
||||
'ai_model' => $article['model'] ?? null,
|
||||
'prompt_used' => $article['prompt'] ?? null,
|
||||
'error_message' => null,
|
||||
'published_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
} else {
|
||||
Article::create([
|
||||
'site_id' => $site['id'],
|
||||
'topic_id' => $topic['id'],
|
||||
'title' => (string) $article['title'],
|
||||
'content' => (string) $article['content'],
|
||||
'wp_post_id' => $wpPostId,
|
||||
'image_url' => $imageUrl,
|
||||
'status' => 'published',
|
||||
'ai_model' => $article['model'] ?? null,
|
||||
'prompt_used' => $article['prompt'] ?? null,
|
||||
'published_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
// 7. Update counters
|
||||
Topic::incrementArticleCount($topic['id']);
|
||||
Site::updateLastPublished($site['id']);
|
||||
Topic::incrementArticleCount((int) $topic['id']);
|
||||
Site::updateLastPublished((int) $site['id']);
|
||||
|
||||
$message = "Opublikowano: \"{$article['title']}\" na {$site['name']}";
|
||||
Logger::info($message, 'publish');
|
||||
@@ -121,18 +167,51 @@ class PublisherService
|
||||
return ['success' => true, 'message' => $message];
|
||||
}
|
||||
|
||||
private function saveFailedArticle(array $site, array $topic, string $error, ?array $article = null): void
|
||||
private function normalizeArticleTitle(string $title, string $topicName): string
|
||||
{
|
||||
Article::create([
|
||||
'site_id' => $site['id'],
|
||||
'topic_id' => $topic['id'],
|
||||
'title' => $article['title'] ?? 'FAILED - nie wygenerowano',
|
||||
'content' => $article['content'] ?? '',
|
||||
'status' => 'failed',
|
||||
'ai_model' => $article['model'] ?? null,
|
||||
'prompt_used' => $article['prompt'] ?? null,
|
||||
'error_message' => $error,
|
||||
]);
|
||||
$title = trim($title);
|
||||
$topicName = trim($topicName);
|
||||
|
||||
if ($title === '' || $topicName === '') {
|
||||
return $title;
|
||||
}
|
||||
|
||||
// Remove "<topic name>: " / "<topic name> - " prefixes from generated titles.
|
||||
$topicPattern = preg_quote($topicName, '/');
|
||||
$normalized = preg_replace('/^' . $topicPattern . '\s*[:\-\x{2013}\x{2014}|]\s*/iu', '', $title);
|
||||
$normalized = is_string($normalized) ? trim($normalized) : $title;
|
||||
|
||||
return $normalized !== '' ? $normalized : $title;
|
||||
}
|
||||
|
||||
private function saveFailedArticle(
|
||||
array $site,
|
||||
array $topic,
|
||||
string $error,
|
||||
?array $article = null,
|
||||
?int $existingArticleId = null
|
||||
): void {
|
||||
if ($existingArticleId !== null) {
|
||||
Article::update($existingArticleId, [
|
||||
'title' => $article['title'] ?? 'FAILED - nie wygenerowano',
|
||||
'content' => $article['content'] ?? '',
|
||||
'status' => 'failed',
|
||||
'ai_model' => $article['model'] ?? null,
|
||||
'prompt_used' => $article['prompt'] ?? null,
|
||||
'error_message' => $error,
|
||||
]);
|
||||
} else {
|
||||
Article::create([
|
||||
'site_id' => $site['id'],
|
||||
'topic_id' => $topic['id'],
|
||||
'title' => $article['title'] ?? 'FAILED - nie wygenerowano',
|
||||
'content' => $article['content'] ?? '',
|
||||
'status' => 'failed',
|
||||
'ai_model' => $article['model'] ?? null,
|
||||
'prompt_used' => $article['prompt'] ?? null,
|
||||
'error_message' => $error,
|
||||
]);
|
||||
}
|
||||
|
||||
Logger::error("Publikacja nieudana: {$error}", 'publish');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user