db = Database::connection(); } public function latest(int $limit = 3): array { $statement = $this->db->prepare( "SELECT posts.id, posts.title, posts.slug, posts.category, posts.excerpt, posts.published_at, (SELECT file_path FROM post_images WHERE post_id = posts.id ORDER BY sort_order, id LIMIT 1) AS image_path, (SELECT alt_text FROM post_images WHERE post_id = posts.id ORDER BY sort_order, id LIMIT 1) AS image_alt FROM posts WHERE posts.status = 'published' AND posts.deleted_at IS NULL AND posts.published_at <= NOW() ORDER BY published_at DESC, id DESC LIMIT :limit" ); $statement->bindValue(':limit', $limit, PDO::PARAM_INT); $statement->execute(); return $statement->fetchAll(); } public function paginate(int $page, int $perPage, ?string $category = null): array { $where = "posts.status = 'published' AND posts.deleted_at IS NULL AND posts.published_at <= NOW()"; $params = []; if ($category !== null && $category !== '') { $where .= ' AND posts.category = :category'; $params['category'] = $category; } $count = $this->db->prepare("SELECT COUNT(*) FROM posts WHERE {$where}"); $count->execute($params); $total = (int) $count->fetchColumn(); $offset = max(0, ($page - 1) * $perPage); $query = $this->db->prepare( "SELECT posts.id, posts.title, posts.slug, posts.category, posts.excerpt, posts.published_at, (SELECT file_path FROM post_images WHERE post_id = posts.id ORDER BY sort_order, id LIMIT 1) AS image_path, (SELECT alt_text FROM post_images WHERE post_id = posts.id ORDER BY sort_order, id LIMIT 1) AS image_alt FROM posts WHERE {$where} ORDER BY published_at DESC, id DESC LIMIT :limit OFFSET :offset" ); foreach ($params as $key => $value) { $query->bindValue(':' . $key, $value); } $query->bindValue(':limit', $perPage, PDO::PARAM_INT); $query->bindValue(':offset', $offset, PDO::PARAM_INT); $query->execute(); return ['items' => $query->fetchAll(), 'total' => $total]; } public function findPublishedBySlug(string $slug): ?array { $statement = $this->db->prepare( "SELECT posts.id, posts.title, posts.slug, posts.category, posts.excerpt, posts.body, posts.author, posts.published_at, (SELECT file_path FROM post_images WHERE post_id = posts.id ORDER BY sort_order, id LIMIT 1) AS image_path, (SELECT alt_text FROM post_images WHERE post_id = posts.id ORDER BY sort_order, id LIMIT 1) AS image_alt FROM posts WHERE posts.slug = :slug AND posts.status = 'published' AND posts.deleted_at IS NULL AND posts.published_at <= NOW() LIMIT 1" ); $statement->execute(['slug' => $slug]); $post = $statement->fetch(); return $post ?: null; } }