Implementierung - SEO - 01.08.2025
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const generateGitHubReadme = () => {
|
||||
const readme = `# 👋 Hi, I'm Damjan Savić - Fullstack Developer
|
||||
|
||||
🚀 **Specializing in:** Python | JavaScript | React | Next.js | TypeScript | AI/ML with OLLAMA
|
||||
|
||||
## 🎯 Current Focus
|
||||
- 🤖 Building AI-powered applications with OLLAMA
|
||||
- 🏢 Developing ERP integration solutions
|
||||
- 🛒 Creating scalable e-commerce platforms
|
||||
- ⚡ Automating business processes with Python
|
||||
|
||||
## 💻 Tech Stack
|
||||
|
||||
### Languages
|
||||

|
||||

|
||||

|
||||
|
||||
### Frontend
|
||||

|
||||

|
||||

|
||||
|
||||
### Backend
|
||||

|
||||

|
||||

|
||||
|
||||
### AI/ML
|
||||

|
||||

|
||||
|
||||
### Databases
|
||||

|
||||

|
||||
|
||||
### DevOps
|
||||

|
||||

|
||||
|
||||
## 📊 GitHub Stats
|
||||

|
||||
|
||||

|
||||
|
||||
## 🏆 Featured Projects
|
||||
|
||||
### 🤖 [OLLAMA Chat Interface](https://github.com/damjansavic/ollama-chat)
|
||||
Local AI chat application with privacy-first approach
|
||||
- **Tech:** Python, FastAPI, React, OLLAMA
|
||||
- **Features:** Stream responses, conversation history, multiple models
|
||||
|
||||
### 🛒 [E-Commerce Automation Suite](https://github.com/damjansavic/ecommerce-automation)
|
||||
Automated inventory and order management system
|
||||
- **Tech:** Python, Django, React, PostgreSQL
|
||||
- **Impact:** 60% reduction in manual processing time
|
||||
|
||||
### 📊 [ERP Integration Platform](https://github.com/damjansavic/erp-integration)
|
||||
Seamless connection between multiple ERP systems
|
||||
- **Tech:** Python, FastAPI, Docker, RabbitMQ
|
||||
- **Scale:** Processing 1M+ transactions daily
|
||||
|
||||
## 💡 What I Offer
|
||||
|
||||
- **🚀 Performance:** Applications optimized for speed and scalability
|
||||
- **🔒 Security:** Privacy-first AI solutions with OLLAMA
|
||||
- **📈 Business Impact:** Solutions that deliver measurable ROI
|
||||
- **🛠️ Full Stack:** From database design to user interface
|
||||
|
||||
## 📫 Connect With Me
|
||||
- 🌐 [Portfolio](https://damjan-savic.com)
|
||||
- 💼 [LinkedIn](https://linkedin.com/in/damjansavic)
|
||||
- 📧 [Email](mailto:info@damjan-savic.com)
|
||||
|
||||
## 🎯 Open for Opportunities
|
||||
I'm interested in projects involving:
|
||||
- AI/ML integration with business applications
|
||||
- High-performance web applications
|
||||
- Process automation and optimization
|
||||
- E-commerce and ERP solutions
|
||||
|
||||
---
|
||||
|
||||
*"Building the future, one line of code at a time"* 🚀
|
||||
`;
|
||||
|
||||
return readme;
|
||||
};
|
||||
|
||||
// Generate and save the README
|
||||
const readme = generateGitHubReadme();
|
||||
const outputPath = path.join(__dirname, '..', 'github-profile-README.md');
|
||||
|
||||
fs.writeFileSync(outputPath, readme, 'utf-8');
|
||||
console.log(`✅ GitHub Profile README generated successfully at ${outputPath}`);
|
||||
|
||||
// Also generate a template for project READMEs
|
||||
const projectReadmeTemplate = `# Project Name
|
||||
|
||||
[](LICENSE)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://reactjs.org/)
|
||||
|
||||
## 🚀 Overview
|
||||
|
||||
Brief description of what this project does and why it's useful.
|
||||
|
||||
### 🎯 Key Features
|
||||
- Feature 1: Description
|
||||
- Feature 2: Description
|
||||
- Feature 3: Description
|
||||
|
||||
### 💡 Use Cases
|
||||
- Use case 1
|
||||
- Use case 2
|
||||
- Use case 3
|
||||
|
||||
## 🛠️ Tech Stack
|
||||
|
||||
- **Backend:** Python, FastAPI/Django
|
||||
- **Frontend:** React, Next.js, TypeScript
|
||||
- **Database:** PostgreSQL/MongoDB
|
||||
- **AI/ML:** OLLAMA, LangChain
|
||||
- **DevOps:** Docker, AWS
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
\`\`\`bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/damjansavic/project-name.git
|
||||
|
||||
# Navigate to project directory
|
||||
cd project-name
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
npm install
|
||||
|
||||
# Set up environment variables
|
||||
cp .env.example .env
|
||||
\`\`\`
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
\`\`\`bash
|
||||
# Start the backend
|
||||
python main.py
|
||||
|
||||
# Start the frontend (in another terminal)
|
||||
npm run dev
|
||||
\`\`\`
|
||||
|
||||
## 📊 Performance
|
||||
|
||||
- **Response Time:** < 100ms
|
||||
- **Throughput:** 1000+ requests/second
|
||||
- **Uptime:** 99.9%
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
Key configuration options:
|
||||
|
||||
\`\`\`python
|
||||
# config.py
|
||||
DATABASE_URL = "postgresql://..."
|
||||
OLLAMA_MODEL = "llama2"
|
||||
API_KEY = "your-api-key"
|
||||
\`\`\`
|
||||
|
||||
## 📱 API Documentation
|
||||
|
||||
API documentation is available at \`/docs\` when running the application.
|
||||
|
||||
### Example Request
|
||||
|
||||
\`\`\`bash
|
||||
curl -X POST "http://localhost:8000/api/v1/process" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{"data": "example"}'
|
||||
\`\`\`
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
\`\`\`bash
|
||||
# Run tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=app tests/
|
||||
\`\`\`
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 👨💻 Author
|
||||
|
||||
**Damjan Savić**
|
||||
- Website: [damjan-savic.com](https://damjan-savic.com)
|
||||
- LinkedIn: [@damjansavic](https://linkedin.com/in/damjansavic)
|
||||
- GitHub: [@damjansavic](https://github.com/damjansavic)
|
||||
|
||||
---
|
||||
|
||||
⭐️ If you find this project useful, please consider giving it a star!
|
||||
`;
|
||||
|
||||
const templatePath = path.join(__dirname, '..', 'project-readme-template.md');
|
||||
fs.writeFileSync(templatePath, projectReadmeTemplate, 'utf-8');
|
||||
console.log(`✅ Project README template generated at ${templatePath}`);
|
||||
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OG Image Generator</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
.og-container {
|
||||
width: 1200px;
|
||||
height: 630px;
|
||||
background: linear-gradient(135deg, #18181B 0%, #27272A 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0.05;
|
||||
background-image:
|
||||
repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(255,255,255,.1) 35px, rgba(255,255,255,.1) 70px);
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
}
|
||||
.logo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: #3B82F6;
|
||||
border-radius: 30px;
|
||||
margin: 0 auto 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 72px;
|
||||
margin: 0 0 20px 0;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.subtitle {
|
||||
color: #A1A1AA;
|
||||
font-size: 36px;
|
||||
margin: 0 0 40px 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
.skills {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.skill {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border: 2px solid #3B82F6;
|
||||
color: #93BBFC;
|
||||
padding: 12px 24px;
|
||||
border-radius: 50px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.bottom-line {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: #71717A;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="og-container" id="og-image">
|
||||
<div class="pattern"></div>
|
||||
<div class="content">
|
||||
<div class="logo">DS</div>
|
||||
<h1>Damjan Savić</h1>
|
||||
<p class="subtitle">Fullstack Developer & KI Spezialist</p>
|
||||
<div class="skills">
|
||||
<span class="skill">Python</span>
|
||||
<span class="skill">JavaScript</span>
|
||||
<span class="skill">React</span>
|
||||
<span class="skill">TypeScript</span>
|
||||
<span class="skill">OLLAMA</span>
|
||||
<span class="skill">AI/ML</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-line">damjan-savic.com</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// This HTML can be used with a headless browser to generate the OG image
|
||||
// Example: Use Puppeteer or Playwright to screenshot this page
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,162 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const SITE_URL = 'https://damjan-savic.com';
|
||||
const LANGUAGES = ['de', 'en', 'sr'];
|
||||
const DEFAULT_LANGUAGE = 'de';
|
||||
|
||||
// Static pages that exist in your application
|
||||
const staticPages = [
|
||||
'/',
|
||||
'/about',
|
||||
'/portfolio',
|
||||
'/blog',
|
||||
'/contact',
|
||||
'/privacy',
|
||||
'/terms'
|
||||
];
|
||||
|
||||
// Blog posts (you can dynamically read these from your MDX files)
|
||||
const blogPosts = [
|
||||
'erp-integration-breuninger',
|
||||
'fullstack-development-timetracking',
|
||||
'rfid-automation'
|
||||
];
|
||||
|
||||
// Projects (you can dynamically read these from your project data)
|
||||
const projects = [
|
||||
'ai-data-reader',
|
||||
'bi-vision',
|
||||
'business-intelligence-ai',
|
||||
'claude-personal-assistant',
|
||||
'e-commerce-integration',
|
||||
'local-llm-training',
|
||||
'rfid-automation',
|
||||
'timetracking-software'
|
||||
];
|
||||
|
||||
function generateUrl(path, language) {
|
||||
const langPrefix = language === DEFAULT_LANGUAGE ? '' : `/${language}`;
|
||||
return `${SITE_URL}${langPrefix}${path}`;
|
||||
}
|
||||
|
||||
function generateSitemapXML() {
|
||||
const currentDate = new Date().toISOString().split('T')[0];
|
||||
|
||||
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||||
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n';
|
||||
xml += ' xmlns:xhtml="http://www.w3.org/1999/xhtml">\n';
|
||||
|
||||
// Generate entries for static pages
|
||||
staticPages.forEach(page => {
|
||||
LANGUAGES.forEach(lang => {
|
||||
const url = generateUrl(page, lang);
|
||||
const priority = page === '/' ? '1.0' : page === '/portfolio' || page === '/blog' ? '0.9' : '0.8';
|
||||
const changefreq = page === '/' || page === '/portfolio' || page === '/blog' ? 'weekly' : 'monthly';
|
||||
|
||||
xml += ' <url>\n';
|
||||
xml += ` <loc>${url}</loc>\n`;
|
||||
xml += ` <lastmod>${currentDate}</lastmod>\n`;
|
||||
xml += ` <changefreq>${changefreq}</changefreq>\n`;
|
||||
xml += ` <priority>${priority}</priority>\n`;
|
||||
|
||||
// Add alternate language links
|
||||
LANGUAGES.forEach(altLang => {
|
||||
if (altLang !== lang) {
|
||||
const altUrl = generateUrl(page, altLang);
|
||||
xml += ` <xhtml:link rel="alternate" hreflang="${altLang}" href="${altUrl}"/>\n`;
|
||||
}
|
||||
});
|
||||
xml += ` <xhtml:link rel="alternate" hreflang="x-default" href="${generateUrl(page, DEFAULT_LANGUAGE)}"/>\n`;
|
||||
|
||||
xml += ' </url>\n';
|
||||
});
|
||||
});
|
||||
|
||||
// Generate entries for blog posts
|
||||
blogPosts.forEach(post => {
|
||||
LANGUAGES.forEach(lang => {
|
||||
const url = generateUrl(`/blog/${post}`, lang);
|
||||
|
||||
xml += ' <url>\n';
|
||||
xml += ` <loc>${url}</loc>\n`;
|
||||
xml += ` <lastmod>${currentDate}</lastmod>\n`;
|
||||
xml += ' <changefreq>monthly</changefreq>\n';
|
||||
xml += ' <priority>0.7</priority>\n';
|
||||
|
||||
// Add alternate language links
|
||||
LANGUAGES.forEach(altLang => {
|
||||
if (altLang !== lang) {
|
||||
const altUrl = generateUrl(`/blog/${post}`, altLang);
|
||||
xml += ` <xhtml:link rel="alternate" hreflang="${altLang}" href="${altUrl}"/>\n`;
|
||||
}
|
||||
});
|
||||
xml += ` <xhtml:link rel="alternate" hreflang="x-default" href="${generateUrl(`/blog/${post}`, DEFAULT_LANGUAGE)}"/>\n`;
|
||||
|
||||
xml += ' </url>\n';
|
||||
});
|
||||
});
|
||||
|
||||
// Generate entries for projects
|
||||
projects.forEach(project => {
|
||||
LANGUAGES.forEach(lang => {
|
||||
const url = generateUrl(`/portfolio/${project}`, lang);
|
||||
|
||||
xml += ' <url>\n';
|
||||
xml += ` <loc>${url}</loc>\n`;
|
||||
xml += ` <lastmod>${currentDate}</lastmod>\n`;
|
||||
xml += ' <changefreq>monthly</changefreq>\n';
|
||||
xml += ' <priority>0.8</priority>\n';
|
||||
|
||||
// Add alternate language links
|
||||
LANGUAGES.forEach(altLang => {
|
||||
if (altLang !== lang) {
|
||||
const altUrl = generateUrl(`/portfolio/${project}`, altLang);
|
||||
xml += ` <xhtml:link rel="alternate" hreflang="${altLang}" href="${altUrl}"/>\n`;
|
||||
}
|
||||
});
|
||||
xml += ` <xhtml:link rel="alternate" hreflang="x-default" href="${generateUrl(`/portfolio/${project}`, DEFAULT_LANGUAGE)}"/>\n`;
|
||||
|
||||
xml += ' </url>\n';
|
||||
});
|
||||
});
|
||||
|
||||
xml += '</urlset>';
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
// Generate and save the sitemap
|
||||
const sitemapXML = generateSitemapXML();
|
||||
const outputPath = path.join(__dirname, '..', 'public', 'sitemap.xml');
|
||||
|
||||
fs.writeFileSync(outputPath, sitemapXML, 'utf-8');
|
||||
console.log(`✅ Sitemap generated successfully at ${outputPath}`);
|
||||
|
||||
// Also generate a robots.txt if it doesn't exist or update it
|
||||
const robotsPath = path.join(__dirname, '..', 'public', 'robots.txt');
|
||||
const robotsContent = `# Allow all crawlers
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Crawl-delay: 1
|
||||
|
||||
# Disallow admin or sensitive paths if any
|
||||
Disallow: /admin
|
||||
Disallow: /api/
|
||||
Disallow: /.env
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: ${SITE_URL}/sitemap.xml
|
||||
|
||||
# Language specific sitemaps
|
||||
Sitemap: ${SITE_URL}/sitemap-de.xml
|
||||
Sitemap: ${SITE_URL}/sitemap-en.xml
|
||||
Sitemap: ${SITE_URL}/sitemap-sr.xml
|
||||
`;
|
||||
|
||||
fs.writeFileSync(robotsPath, robotsContent, 'utf-8');
|
||||
console.log(`✅ robots.txt updated successfully at ${robotsPath}`);
|
||||
Reference in New Issue
Block a user