integration Directus cms
This commit is contained in:
29
src/pages/blog/[slug].astro
Normal file
29
src/pages/blog/[slug].astro
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getBlogPostBySlug, getBlogPosts } from '../../utils/directus';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getBlogPosts();
|
||||
return posts.map((post) => ({
|
||||
params: { slug: Array.isArray(post.slug) ? post.slug[0] : post.slug },
|
||||
props: { post },
|
||||
}));
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout title={`${post.title} | Tiber365 Blog`}>
|
||||
<main class="container mx-auto px-4 py-12">
|
||||
<article class="max-w-3xl mx-auto">
|
||||
<header class="mb-8">
|
||||
<h1 class="text-4xl font-bold mb-4">{post.title}</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
{new Date(post.date_created).toLocaleDateString()}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="prose dark:prose-invert max-w-none" set:html={post.content}></div>
|
||||
</article>
|
||||
</main>
|
||||
</BaseLayout>
|
63
src/pages/blog/index.astro
Normal file
63
src/pages/blog/index.astro
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { getBlogPosts } from '../../utils/directus';
|
||||
|
||||
let posts = [];
|
||||
let error = null;
|
||||
|
||||
function stripHtml(html) {
|
||||
return html.replace(/<[^>]+>/g, '');
|
||||
}
|
||||
|
||||
try {
|
||||
posts = await getBlogPosts();
|
||||
} catch (e) {
|
||||
console.error('Error in blog page:', e);
|
||||
error = e.message;
|
||||
}
|
||||
---
|
||||
|
||||
<BaseLayout title="Blog | Tiber365">
|
||||
<main class="flex flex-col items-center min-h-screen bg-background py-16 px-4">
|
||||
<h1 class="text-5xl font-extrabold mb-12 text-center">Blog</h1>
|
||||
|
||||
{error ? (
|
||||
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-8">
|
||||
<p class="text-red-800 dark:text-red-200">
|
||||
Sorry, we couldn't load the blog posts at this time. Please try again later.
|
||||
</p>
|
||||
{import.meta.env.DEV && (
|
||||
<pre class="mt-2 text-sm text-red-600 dark:text-red-400">{error}</pre>
|
||||
)}
|
||||
</div>
|
||||
) : posts.length === 0 ? (
|
||||
<p class="text-gray-600 dark:text-gray-400">No blog posts found.</p>
|
||||
) : (
|
||||
<div class="w-full max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||
{posts.map((post) => (
|
||||
<article class="bg-white/90 dark:bg-gray-900/80 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-lg hover:shadow-2xl transition-shadow duration-300 overflow-hidden flex flex-col h-full group">
|
||||
<div class="flex flex-col flex-1 p-8">
|
||||
<h2 class="text-2xl font-bold mb-3 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
||||
<a href={`/blog/${post.slug}`}>{post.title}</a>
|
||||
</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm mb-4">
|
||||
{new Date(post.date_created).toLocaleDateString()}
|
||||
</p>
|
||||
<div class="text-base text-gray-700 dark:text-gray-300 mb-6 line-clamp-4">
|
||||
{stripHtml(post.content).substring(0, 250)}{stripHtml(post.content).length > 250 ? '...' : ''}
|
||||
</div>
|
||||
<div class="mt-auto pt-2">
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
class="inline-block text-blue-600 dark:text-blue-400 font-medium hover:underline transition-colors"
|
||||
>
|
||||
Read more →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</BaseLayout>
|
@@ -32,8 +32,8 @@ export const NAVIGATION = [
|
||||
},
|
||||
{
|
||||
label: 'nav.blog',
|
||||
href: 'https://blog.tiber365.it',
|
||||
type: 'external'
|
||||
href: '/blog',
|
||||
type: 'internal'
|
||||
},
|
||||
{
|
||||
label: 'nav.support',
|
||||
|
127
src/utils/directus.ts
Normal file
127
src/utils/directus.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// Removed: import type { DirectusClient } from '@directus/sdk';
|
||||
|
||||
interface DirectusAuthResponse {
|
||||
data: {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expires: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface BlogPost {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
slug: string;
|
||||
date_created: string;
|
||||
status: string;
|
||||
// Add other fields as needed
|
||||
}
|
||||
|
||||
let cachedToken: string | null = null;
|
||||
let tokenExpiry: number = 0;
|
||||
|
||||
async function getAccessToken(): Promise<string> {
|
||||
// Return cached token if it's still valid
|
||||
if (cachedToken && Date.now() < tokenExpiry) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Attempting to authenticate with Directus...');
|
||||
const response = await fetch('https://cms.365devnet.eu/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'admin@365devnet.eu',
|
||||
password: 'DirectusAdmin2024!',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Authentication failed:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
throw new Error(`Failed to authenticate with Directus: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data: DirectusAuthResponse = await response.json();
|
||||
console.log('Successfully authenticated with Directus');
|
||||
|
||||
// Cache the token
|
||||
cachedToken = data.data.access_token;
|
||||
tokenExpiry = Date.now() + (data.data.expires * 1000); // Convert to milliseconds
|
||||
|
||||
return data.data.access_token;
|
||||
} catch (error) {
|
||||
console.error('Error getting Directus access token:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBlogPosts(): Promise<BlogPost[]> {
|
||||
try {
|
||||
const token = await getAccessToken();
|
||||
console.log('Fetching blog posts...');
|
||||
|
||||
const response = await fetch('https://cms.365devnet.eu/items/Posts?sort=-date_created', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Failed to fetch posts:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
throw new Error(`Failed to fetch blog posts: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Successfully fetched blog posts');
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching blog posts:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getBlogPostBySlug(slug: string): Promise<BlogPost | null> {
|
||||
try {
|
||||
const token = await getAccessToken();
|
||||
console.log(`Fetching blog post with slug: ${slug}`);
|
||||
|
||||
const response = await fetch(`https://cms.365devnet.eu/items/Posts?filter[slug][_eq]=${slug}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Failed to fetch post:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
throw new Error(`Failed to fetch blog post: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Successfully fetched blog post');
|
||||
return data.data[0] || null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching blog post:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user