umstieg auf supabase

This commit is contained in:
michi 2025-08-26 15:45:35 +02:00
parent 3797c83c28
commit 00af25d9d6
35 changed files with 438 additions and 91 deletions

View File

@ -27,10 +27,15 @@
"static/chunks/main.js",
"static/chunks/pages/admin.js"
],
"/admin/login": [
"/blog/[slug]": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/admin/login.js"
"static/chunks/pages/blog/[slug].js"
],
"/devlog": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/devlog.js"
]
},
"ampFirstPages": []

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +1,18 @@
{}
{
"..\\node_modules\\@supabase\\auth-js\\dist\\module\\lib\\helpers.js -> @supabase/node-fetch": {
"id": "..\\node_modules\\@supabase\\auth-js\\dist\\module\\lib\\helpers.js -> @supabase/node-fetch",
"files": []
},
"..\\node_modules\\@supabase\\functions-js\\dist\\module\\helper.js -> @supabase/node-fetch": {
"id": "..\\node_modules\\@supabase\\functions-js\\dist\\module\\helper.js -> @supabase/node-fetch",
"files": []
},
"..\\node_modules\\@supabase\\realtime-js\\dist\\module\\RealtimeClient.js -> @supabase/node-fetch": {
"id": "..\\node_modules\\@supabase\\realtime-js\\dist\\module\\RealtimeClient.js -> @supabase/node-fetch",
"files": []
},
"..\\node_modules\\@supabase\\storage-js\\dist\\module\\lib\\helpers.js -> @supabase/node-fetch": {
"id": "..\\node_modules\\@supabase\\storage-js\\dist\\module\\lib\\helpers.js -> @supabase/node-fetch",
"files": []
}
}

View File

@ -1 +1 @@
self.__BUILD_MANIFEST={"polyfillFiles":["static/chunks/polyfills.js"],"devFiles":["static/chunks/react-refresh.js"],"ampDevFiles":[],"lowPriorityFiles":["static/development/_buildManifest.js","static/development/_ssgManifest.js"],"rootMainFiles":[],"pages":{"/_app":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/_app.js"],"/_error":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/_error.js"],"/admin":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/admin.js"],"/admin/login":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/admin/login.js"]},"ampFirstPages":[]}
self.__BUILD_MANIFEST={"polyfillFiles":["static/chunks/polyfills.js"],"devFiles":["static/chunks/react-refresh.js"],"ampDevFiles":[],"lowPriorityFiles":["static/development/_buildManifest.js","static/development/_ssgManifest.js"],"rootMainFiles":[],"pages":{"/_app":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/_app.js"],"/_error":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/_error.js"],"/admin":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/admin.js"],"/blog/[slug]":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/blog/[slug].js"],"/devlog":["static/chunks/webpack.js","static/chunks/main.js","static/chunks/pages/devlog.js"]},"ampFirstPages":[]}

View File

@ -1 +1 @@
self.__REACT_LOADABLE_MANIFEST="{}"
self.__REACT_LOADABLE_MANIFEST="{\"..\\\\node_modules\\\\@supabase\\\\auth-js\\\\dist\\\\module\\\\lib\\\\helpers.js -> @supabase/node-fetch\":{\"id\":\"..\\\\node_modules\\\\@supabase\\\\auth-js\\\\dist\\\\module\\\\lib\\\\helpers.js -> @supabase/node-fetch\",\"files\":[]},\"..\\\\node_modules\\\\@supabase\\\\functions-js\\\\dist\\\\module\\\\helper.js -> @supabase/node-fetch\":{\"id\":\"..\\\\node_modules\\\\@supabase\\\\functions-js\\\\dist\\\\module\\\\helper.js -> @supabase/node-fetch\",\"files\":[]},\"..\\\\node_modules\\\\@supabase\\\\realtime-js\\\\dist\\\\module\\\\RealtimeClient.js -> @supabase/node-fetch\":{\"id\":\"..\\\\node_modules\\\\@supabase\\\\realtime-js\\\\dist\\\\module\\\\RealtimeClient.js -> @supabase/node-fetch\",\"files\":[]},\"..\\\\node_modules\\\\@supabase\\\\storage-js\\\\dist\\\\module\\\\lib\\\\helpers.js -> @supabase/node-fetch\":{\"id\":\"..\\\\node_modules\\\\@supabase\\\\storage-js\\\\dist\\\\module\\\\lib\\\\helpers.js -> @supabase/node-fetch\",\"files\":[]}}"

View File

@ -2,6 +2,7 @@
"/_app": "pages/_app.js",
"/_error": "pages/_error.js",
"/_document": "pages/_document.js",
"/admin/login": "pages/admin/login.js",
"/admin": "pages/admin.js"
"/admin": "pages/admin.js",
"/devlog": "pages/devlog.js",
"/blog/[slug]": "pages/blog/[slug].js"
}

View File

@ -1 +1 @@
self.__BUILD_MANIFEST = {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/_error":["static\u002Fchunks\u002Fpages\u002F_error.js"],"/admin":["static\u002Fchunks\u002Fpages\u002Fadmin.js"],"/admin/login":["static\u002Fchunks\u002Fpages\u002Fadmin\u002Flogin.js"],sortedPages:["\u002F_app","\u002F_error","\u002Fadmin","\u002Fadmin\u002Flogin"]};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
self.__BUILD_MANIFEST = {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/_error":["static\u002Fchunks\u002Fpages\u002F_error.js"],"/admin":["static\u002Fchunks\u002Fpages\u002Fadmin.js"],"/blog/[slug]":["static\u002Fchunks\u002Fpages\u002Fblog\u002F[slug].js"],"/devlog":["static\u002Fchunks\u002Fpages\u002Fdevlog.js"],sortedPages:["\u002F_app","\u002F_error","\u002Fadmin","\u002Fblog\u002F[slug]","\u002Fdevlog"]};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()

53
components/AdminLayout.js Normal file
View File

@ -0,0 +1,53 @@
import { useRouter } from 'next/router';
import { useState } from 'react';
const AdminLayout = ({ children }) => {
const router = useRouter();
const [isLoggingOut, setIsLoggingOut] = useState(false);
const handleLogout = async () => {
setIsLoggingOut(true);
try {
// Cookie löschen
document.cookie = 'isAuthenticated=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
// Zur Login-Seite weiterleiten
router.push('/admin/login');
} catch (error) {
console.error('Logout-Fehler:', error);
} finally {
setIsLoggingOut(false);
}
};
return (
<div className="min-h-screen bg-gray-50">
{/* Admin Header */}
<header className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<h1 className="text-xl font-semibold text-gray-900">
Admin Panel
</h1>
<button
onClick={handleLogout}
disabled={isLoggingOut}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoggingOut ? 'Wird abgemeldet...' : 'Abmelden'}
</button>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
{children}
</div>
</main>
</div>
);
};
export default AdminLayout;

View File

@ -21,7 +21,7 @@ export default function LoginForm() {
const data = await res.json();
if (data.success) {
router.reload();
router.push('/admin');
} else {
setError('Ungültige Zugangsdaten');
}

View File

@ -25,6 +25,7 @@ export default function Navbar() {
alt="Pixelbrew Logo"
width={300}
height={300}
priority
style={{ marginTop: '4rem', marginBottom: '1rem' }}
/>
</Link>

11
lib/supabase.js Normal file
View File

@ -0,0 +1,11 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://dlwcsisbvxvlmfbzjszs.supabase.co'
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRsd2NzaXNidnh2bG1mYnpqc3pzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTYyMTQzODMsImV4cCI6MjA3MTc5MDM4M30.xJGPs921Ta5gSLvMuCpAwOVSL9DfQpJeruhgH1350vU'
const supabaseServiceKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRsd2NzaXNidnh2bG1mYnpqc3pzIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc1NjIxNDM4MywiZXhwIjoyMDcxNzkwMzgzfQ.jOaOmZkk6MLzpyHeshtEG5RTEzBUfnMe1Riij6cfOg0'
// Client for frontend (anon key)
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
// Client for API routes (service role key)
export const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey)

135
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@heroicons/react": "^2.2.0",
"@supabase/supabase-js": "^2.56.0",
"axios": "^1.11.0",
"framer-motion": "^12.16.0",
"leaflet": "^1.9.4",
@ -378,6 +379,80 @@
"react-dom": "^18.0.0"
}
},
"node_modules/@supabase/auth-js": {
"version": "2.71.1",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz",
"integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz",
"integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/node-fetch": {
"version": "2.6.15",
"resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
"integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "1.21.3",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.21.3.tgz",
"integrity": "sha512-rg3DmmZQKEVCreXq6Am29hMVe1CzemXyIWVYyyua69y6XubfP+DzGfLxME/1uvdgwqdoaPbtjBDpEBhqxq1ZwA==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.15.1.tgz",
"integrity": "sha512-edRFa2IrQw50kNntvUyS38hsL7t2d/psah6om6aNTLLcWem0R6bOUq7sk7DsGeSlNfuwEwWn57FdYSva6VddYw==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "^2.6.13",
"@types/phoenix": "^1.6.6",
"@types/ws": "^8.18.1",
"ws": "^8.18.2"
}
},
"node_modules/@supabase/storage-js": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.11.0.tgz",
"integrity": "sha512-Y+kx/wDgd4oasAgoAq0bsbQojwQ+ejIif8uczZ9qufRHWFLMU5cODT+ApHsSrDufqUcVKt+eyxtOXSkeh2v9ww==",
"license": "MIT",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.56.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.56.0.tgz",
"integrity": "sha512-XqwhHSyVnkjdliPN61CmXsmFGnFHTX2WDdwjG3Ukvdzuu3Trix+dXupYOQ3BueIyYp7B6t0yYpdQtJP2hIInyg==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.71.1",
"@supabase/functions-js": "2.4.5",
"@supabase/node-fetch": "2.6.15",
"@supabase/postgrest-js": "1.21.3",
"@supabase/realtime-js": "2.15.1",
"@supabase/storage-js": "^2.10.4"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
@ -391,12 +466,17 @@
"version": "22.15.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz",
"integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/phoenix": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
@ -407,6 +487,15 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -2234,6 +2323,12 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@ -2265,7 +2360,6 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": {
@ -2319,6 +2413,22 @@
"node": ">=10.13.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -2432,6 +2542,27 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",

View File

@ -9,6 +9,7 @@
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@supabase/supabase-js": "^2.56.0",
"axios": "^1.11.0",
"framer-motion": "^12.16.0",
"leaflet": "^1.9.4",

View File

@ -1,8 +1,23 @@
import { useRouter } from 'next/router';
import Layout from '../components/Layout';
import AdminLayout from '../components/AdminLayout';
import CookieBanner from '../components/CookieBanner';
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
const router = useRouter();
// Prüfen ob es sich um eine Admin-Seite handelt
const isAdminPage = router.pathname.startsWith('/admin');
if (isAdminPage) {
return (
<AdminLayout>
<Component {...pageProps} />
</AdminLayout>
);
}
return (
<Layout>
<Component {...pageProps} />

View File

@ -1,8 +1,9 @@
import { useState } from 'react';
import devlogData from '../../data/devlog.json';
import { useState, useEffect } from 'react';
import { supabase } from '../../lib/supabase';
export default function AdminPanel() {
const [entries, setEntries] = useState(devlogData);
const [entries, setEntries] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedEntry, setSelectedEntry] = useState(null);
const emptyEntry = {
date: new Date().toLocaleDateString('de-DE'),
@ -13,15 +14,42 @@ export default function AdminPanel() {
content: []
};
// Load data from Supabase on component mount
useEffect(() => {
loadEntries();
}, []);
const loadEntries = async () => {
try {
const { data, error } = await supabase
.from('devlog_entries')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
setEntries(data || []);
} catch (error) {
console.error('Fehler beim Laden der Einträge:', error);
alert('Fehler beim Laden der Daten');
} finally {
setLoading(false);
}
};
const handleSave = async (entry, index = null) => {
// Entferne das 'index' Feld aus dem Entry bevor es gespeichert wird
// Entferne das 'index' und 'editIndex' Feld aus dem Entry
const cleanEntry = { ...entry };
delete cleanEntry.index;
delete cleanEntry.editIndex;
delete cleanEntry.id; // Remove id for new entries
let updatedEntries;
if (index !== null) {
// Bearbeite bestehenden Eintrag
// Bearbeite bestehenden Eintrag - keep the original id
const originalEntry = entries[index];
cleanEntry.id = originalEntry.id;
updatedEntries = entries.map((e, i) => i === index ? cleanEntry : e);
} else {
// Füge neuen Eintrag hinzu
@ -37,7 +65,8 @@ export default function AdminPanel() {
if (!response.ok) throw new Error('Fehler beim Speichern');
setEntries(updatedEntries);
// Reload data from Supabase to get updated entries with IDs
await loadEntries();
setSelectedEntry(null);
} catch (error) {
console.error('Fehler:', error);
@ -153,6 +182,17 @@ export default function AdminPanel() {
);
};
if (loading) {
return (
<div className="container mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">DevLog Admin</h1>
<div className="flex justify-center items-center h-64">
<div className="text-xl">Lade Daten...</div>
</div>
</div>
);
}
return (
<div className="container mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">DevLog Admin</h1>

View File

@ -1,75 +1,75 @@
import fs from 'fs';
import path from 'path';
import { supabaseAdmin } from '../../lib/supabase';
export default function handler(req, res) {
console.log('API /update-devlog aufgerufen');
console.log('Request method:', req.method);
console.log('Request body:', JSON.stringify(req.body, null, 2));
const filePath = path.join(process.cwd(), 'data', 'devlog.json');
console.log('File path:', filePath);
export default async function handler(req, res) {
console.log('=== UPDATE DEVLOG API CALLED (Supabase) ===');
console.log('Method:', req.method);
console.log('Body:', JSON.stringify(req.body, null, 2));
if (req.method === 'POST') {
try {
// Validiere eingehende Daten
if (!req.body) {
console.error('Fehler: Kein Request Body vorhanden');
return res.status(400).json({ message: 'Kein Request Body vorhanden' });
}
if (!Array.isArray(req.body)) {
console.error('Fehler: Request Body ist kein Array');
return res.status(400).json({ message: 'Request Body muss ein Array sein' });
}
console.log('Anzahl der Einträge:', req.body.length);
// Prüfe Dateiberechtigungen
try {
fs.accessSync(path.dirname(filePath), fs.constants.W_OK);
console.log('Schreibberechtigung für Verzeichnis vorhanden');
} catch (permError) {
console.error('Fehler: Keine Schreibberechtigung für Verzeichnis:', permError.message);
return res.status(500).json({ message: 'Keine Schreibberechtigung für Verzeichnis', error: permError.message });
}
// Stelle sicher, dass jeder Eintrag ein content-Array hat und entferne unerwünschte Felder
const updatedData = req.body.map((entry, index) => {
console.log(`Verarbeite Eintrag ${index}:`, entry);
// Validiere erforderliche Felder
if (!entry.title || !entry.date) {
console.error(`Fehler: Eintrag ${index} fehlen erforderliche Felder (title oder date)`);
throw new Error(`Eintrag ${index} fehlen erforderliche Felder`);
}
// Entferne unerwünschte Felder wie editIndex, index etc.
const { editIndex, index: entryIndex, ...cleanEntry } = entry;
return {
...cleanEntry,
content: cleanEntry.content || []
};
});
console.log('Verarbeitete Daten:', JSON.stringify(updatedData, null, 2));
// Schreibe Datei
fs.writeFileSync(filePath, JSON.stringify(updatedData, null, 2), 'utf8');
console.log('Datei erfolgreich geschrieben');
res.status(200).json({ message: 'DevLog updated successfully!' });
} catch (error) {
console.error('Fehler beim Verarbeiten der Anfrage:', error);
console.error('Error stack:', error.stack);
res.status(500).json({
message: 'Error updating DevLog',
error: error.message,
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
if (req.method !== 'POST') {
console.log('Method not allowed:', req.method);
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const data = req.body;
console.log('Received data type:', typeof data);
console.log('Is array:', Array.isArray(data));
// Validate that data is an array
if (!Array.isArray(data)) {
console.log('Invalid data format - not an array');
return res.status(400).json({ error: 'Data must be an array' });
}
} else {
console.log('Methode nicht erlaubt:', req.method);
res.status(405).json({ message: 'Method not allowed' });
// Process each entry for Supabase
const results = [];
for (const entry of data) {
// Ensure content is an array
const content = Array.isArray(entry.content) ? entry.content : [];
// Create clean entry with required fields
const cleanEntry = {
title: entry.title || '',
date: entry.date || '',
description: entry.description || '',
slug: entry.slug || '',
image: entry.image || '',
content: content,
updated_at: new Date().toISOString()
};
console.log('Processing entry:', cleanEntry);
// Use UPSERT (insert or update based on slug)
const { data: result, error } = await supabaseAdmin
.from('devlog_entries')
.upsert(cleanEntry, {
onConflict: 'slug',
returning: 'minimal'
});
if (error) {
console.error('Supabase upsert error:', error);
throw error;
}
results.push(result);
console.log('Entry upserted successfully:', cleanEntry.slug);
}
console.log('All entries processed successfully');
res.status(200).json({
message: 'DevLog updated successfully in Supabase',
count: data.length
});
} catch (error) {
console.error('Error updating devlog in Supabase:', error);
res.status(500).json({
error: 'Failed to update devlog in Supabase',
details: error.message,
stack: error.stack
});
}
}

View File

@ -1,9 +1,15 @@
import { motion } from 'framer-motion';
import Link from 'next/link';
import devlogData from '../data/devlog.json';
import Image from 'next/image';
import { supabase } from '../lib/supabase';
export default function DevLog() {
// Funktion zur Validierung von Bildpfaden
const isValidImagePath = (imagePath) => {
if (!imagePath || typeof imagePath !== 'string') return false;
return imagePath.startsWith('/') || imagePath.startsWith('http://') || imagePath.startsWith('https://');
};
export default function DevLog({ devlogData }) {
return (
<motion.div
initial={{ opacity: 0 }}
@ -24,7 +30,7 @@ export default function DevLog() {
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{devlogData.map((log, index) => (
{devlogData && devlogData.map((log, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
@ -32,12 +38,13 @@ export default function DevLog() {
transition={{ delay: index * 0.2 }}
className="relative p-8 bg-white before:content-[''] before:absolute before:top-0 before:left-0 before:w-8 before:h-8 before:border-t-2 before:border-l-2 before:border-pb-turquoise after:content-[''] after:absolute after:bottom-0 after:right-0 after:w-8 after:h-8 after:border-b-2 after:border-r-2 after:border-pb-turquoise"
>
{log.image && (
{log.image && isValidImagePath(log.image) && (
<div className="relative w-full h-48 mb-4 overflow-hidden">
<Image
src={log.image}
alt={log.title}
fill
priority={index < 2}
className="object-cover"
/>
</div>
@ -58,3 +65,34 @@ export default function DevLog() {
</motion.div>
);
}
export async function getServerSideProps() {
try {
const { data: devlogData, error } = await supabase
.from('devlog_entries')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching devlog data:', error);
return {
props: {
devlogData: []
}
};
}
return {
props: {
devlogData: devlogData || []
}
};
} catch (error) {
console.error('Error in getServerSideProps:', error);
return {
props: {
devlogData: []
}
};
}
}

View File

@ -0,0 +1,34 @@
-- Create devlog_entries table
CREATE TABLE devlog_entries (
id SERIAL PRIMARY KEY,
date TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,
slug TEXT UNIQUE NOT NULL,
image TEXT,
content JSONB DEFAULT '[]'::jsonb,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Enable Row Level Security
ALTER TABLE devlog_entries ENABLE ROW LEVEL SECURITY;
-- Create policies for public read access
CREATE POLICY "Allow public read access" ON devlog_entries
FOR SELECT USING (true);
-- Create policies for authenticated users to insert/update
CREATE POLICY "Allow authenticated insert" ON devlog_entries
FOR INSERT WITH CHECK (true);
CREATE POLICY "Allow authenticated update" ON devlog_entries
FOR UPDATE USING (true);
CREATE POLICY "Allow authenticated delete" ON devlog_entries
FOR DELETE USING (true);
-- Grant permissions to anon and authenticated roles
GRANT SELECT ON devlog_entries TO anon;
GRANT ALL PRIVILEGES ON devlog_entries TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE devlog_entries_id_seq TO authenticated;