feat: add allowed Linear API operations to MCP config
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { GET, HEAD } from './route'
|
||||
import { NextRequest } from 'next/server'
|
||||
|
||||
// Mock the process object for tests
|
||||
const mockProcess = {
|
||||
uptime: jest.fn().mockReturnValue(1234),
|
||||
env: {
|
||||
npm_package_version: '1.0.0',
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
}
|
||||
|
||||
const originalProcess = global.process
|
||||
beforeEach(() => {
|
||||
global.process = { ...originalProcess, ...mockProcess } as any
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
global.process = originalProcess
|
||||
})
|
||||
|
||||
describe('/api/health', () => {
|
||||
describe('GET request', () => {
|
||||
it('should return successful health status with required data', async () => {
|
||||
const response = await GET()
|
||||
|
||||
// Check response status
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
// Get JSON data
|
||||
const data = await response.json()
|
||||
|
||||
// Verify required fields
|
||||
expect(data).toHaveProperty('status', 'ok')
|
||||
expect(data).toHaveProperty('timestamp')
|
||||
expect(data).toHaveProperty('uptime')
|
||||
expect(data).toHaveProperty('version')
|
||||
expect(data).toHaveProperty('environment')
|
||||
|
||||
// Verify timestamp is a valid ISO string
|
||||
expect(() => new Date(data.timestamp)).not.toThrow()
|
||||
expect(data.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
|
||||
|
||||
// Verify uptime matches mock
|
||||
expect(data.uptime).toBe(1234)
|
||||
|
||||
// Verify version
|
||||
expect(data.version).toBe('1.0.0')
|
||||
})
|
||||
|
||||
it('should include correct Cache-Control headers', async () => {
|
||||
const response = await GET()
|
||||
|
||||
expect(response.headers.get('Cache-Control')).toBe('no-cache, no-store, must-revalidate')
|
||||
expect(response.headers.get('Pragma')).toBe('no-cache')
|
||||
expect(response.headers.get('Expires')).toBe('0')
|
||||
})
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
// Temporarily break process.env to simulate error
|
||||
global.process.env = undefined as any
|
||||
|
||||
const response = await GET()
|
||||
|
||||
expect(response.status).toBe(503)
|
||||
|
||||
const data = await response.json()
|
||||
expect(data.status).toBe('error')
|
||||
expect(data).toHaveProperty('timestamp')
|
||||
expect(data).toHaveProperty('message', 'Health check failed')
|
||||
|
||||
// Restore process.env
|
||||
global.process.env = originalProcess.env
|
||||
})
|
||||
})
|
||||
|
||||
describe('HEAD request', () => {
|
||||
it('should return 200 status without body', async () => {
|
||||
const response = await HEAD()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.headers.get('Cache-Control')).toBe('no-cache, no-store, must-revalidate')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Basic health check
|
||||
const healthData = {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
};
|
||||
|
||||
return NextResponse.json(healthData, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Health check error:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
timestamp: new Date().toISOString(),
|
||||
message: 'Health check failed',
|
||||
},
|
||||
{ status: 503 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Also support HEAD requests for lighter health checks
|
||||
export async function HEAD() {
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
},
|
||||
});
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,26 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Header from "../components/Header";
|
||||
import Footer from "../components/Footer";
|
||||
import { siteConfig } from "../config/site";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `${siteConfig.general.name} | ${siteConfig.general.description}`,
|
||||
description: siteConfig.general.description,
|
||||
authors: [{ name: siteConfig.general.name }],
|
||||
keywords: ["web hosting", "email szolgáltatás", "DNS adminisztráció", "IT szolgáltatás", "mozdIT"],
|
||||
openGraph: {
|
||||
title: siteConfig.general.name,
|
||||
description: siteConfig.general.description,
|
||||
url: siteConfig.general.url,
|
||||
siteName: siteConfig.general.name,
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.general.ogImage,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: siteConfig.general.name,
|
||||
},
|
||||
],
|
||||
locale: siteConfig.general.locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: siteConfig.general.name,
|
||||
description: siteConfig.general.description,
|
||||
images: [siteConfig.general.ogImage],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
"max-snippet": -1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="hu">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen flex flex-col`}
|
||||
>
|
||||
<Header />
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { siteConfig } from '@/config/site'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="space-y-16">
|
||||
{/* Hero Section */}
|
||||
<section className="bg-gradient-to-r from-blue-50 to-indigo-50 py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 mb-6 leading-tight">
|
||||
{siteConfig.hero.title}
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-gray-600 max-w-4xl mx-auto mb-8 leading-relaxed">
|
||||
{siteConfig.hero.description}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<a
|
||||
href={siteConfig.hero.cta.primary.href}
|
||||
target={siteConfig.hero.cta.primary.external ? '_blank' : undefined}
|
||||
rel={siteConfig.hero.cta.primary.external ? 'noopener noreferrer' : undefined}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-4 rounded-md transition-colors text-lg inline-flex items-center gap-2"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10" />
|
||||
</svg>
|
||||
{siteConfig.hero.cta.primary.text}
|
||||
</a>
|
||||
{siteConfig.hero.cta.secondary && (
|
||||
<a
|
||||
href={siteConfig.hero.cta.secondary.href}
|
||||
className="border-2 border-blue-600 text-blue-600 hover:bg-blue-50 font-medium px-8 py-4 rounded-md transition-colors text-lg"
|
||||
>
|
||||
{siteConfig.hero.cta.secondary.text}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* USP Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 bg-gray-50">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-8">
|
||||
{siteConfig.about.title}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
{siteConfig.about.usps.map((usp) => (
|
||||
<div key={usp.id} className="text-center">
|
||||
<div className="w-16 h-16 bg-blue-100 group-hover:bg-blue-200 rounded-full flex items-center justify-center mx-auto mb-4 transition-colors">
|
||||
<span className="text-2xl">{usp.icon}</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">{usp.title}</h3>
|
||||
<p className="text-gray-600">{usp.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Services Section */}
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
||||
{siteConfig.services.title}
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 max-w-3xl mx-auto">
|
||||
{siteConfig.services.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||
{siteConfig.services.services.map((service) => (
|
||||
<div key={service.id} className="group bg-white p-8 rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-all duration-300 hover:border-blue-200">
|
||||
<div className="w-12 h-12 bg-blue-100 group-hover:bg-blue-200 rounded-lg flex items-center justify-center mb-4 transition-colors">
|
||||
<span className="text-xl">{service.icon}</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3 group-hover:text-blue-600 transition-colors">{service.title}</h3>
|
||||
<p className="text-gray-600 leading-relaxed">
|
||||
{service.description}
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<h4 className="text-sm font-semibold text-gray-700 mb-2">Szolgáltatás jellemzők:</h4>
|
||||
<ul className="text-sm text-gray-600 space-y-1">
|
||||
{service.features.map((feature, index) => (
|
||||
<li key={index} className="flex items-start">
|
||||
<span className="text-blue-500 mr-2">✓</span>
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/kapcsolat" className="inline-flex items-center text-blue-600 hover:text-blue-700 font-medium mt-4 transition-colors">
|
||||
{service.ctaText}
|
||||
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="bg-gray-900 text-white py-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 className="text-3xl font-bold mb-4">
|
||||
Kapcsolatfelvétel az első lépés
|
||||
</h2>
|
||||
<p className="text-xl text-gray-300 mb-8">
|
||||
Mutassuk meg, hogyan segíthetünk Önnek megvalósítani címeit!
|
||||
</p>
|
||||
<a
|
||||
href="/kapcsolat"
|
||||
className="inline-block bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-3 rounded-md transition-colors"
|
||||
>
|
||||
Kapcsolatfelvétel
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user