423 lines
14 KiB
TypeScript
423 lines
14 KiB
TypeScript
'use server';
|
||
|
||
import sql from '@/lib/db';
|
||
import { cookies } from 'next/headers';
|
||
import { redirect } from 'next/navigation';
|
||
import { uploadToCloudinary } from '@/lib/cloudinary';
|
||
import crypto from 'crypto';
|
||
|
||
function hashPassword(password: string): string {
|
||
return crypto.createHash('sha256').update(password).digest('hex');
|
||
}
|
||
|
||
export async function ensureAdminsTable() {
|
||
try {
|
||
await sql`
|
||
CREATE TABLE IF NOT EXISTS admins (
|
||
id SERIAL PRIMARY KEY,
|
||
username VARCHAR(100) UNIQUE NOT NULL,
|
||
password VARCHAR(255) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
`;
|
||
|
||
const result = await sql`SELECT count(*) FROM admins`;
|
||
if (Number(result[0].count) === 0) {
|
||
const defaultPass = hashPassword(process.env.ADMIN_PASSWORD || 'admin123');
|
||
await sql`
|
||
INSERT INTO admins (username, password)
|
||
VALUES ('admin', ${defaultPass})
|
||
`;
|
||
console.log("Admins table seeded with default user 'admin'");
|
||
}
|
||
|
||
// Add is_featured column to partners table if it doesn't exist
|
||
await sql`
|
||
ALTER TABLE partners ADD COLUMN IF NOT EXISTS is_featured BOOLEAN DEFAULT false
|
||
`;
|
||
} catch (e) {
|
||
console.error("Error ensuring admins table:", e);
|
||
}
|
||
}
|
||
|
||
export async function login(prevState: any, formData: FormData) {
|
||
try {
|
||
await ensureAdminsTable();
|
||
|
||
const username = (formData.get('username') as string || 'admin').trim();
|
||
const password = formData.get('password') as string;
|
||
|
||
const hashed = hashPassword(password);
|
||
|
||
const users = await sql`
|
||
SELECT * FROM admins
|
||
WHERE username = ${username} AND password = ${hashed}
|
||
`;
|
||
|
||
if (users.length > 0) {
|
||
const cookieStore = await cookies();
|
||
cookieStore.set('admin_session', 'authenticated', {
|
||
httpOnly: true,
|
||
secure: process.env.NODE_ENV === 'production',
|
||
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||
path: '/',
|
||
});
|
||
cookieStore.set('admin_user', username, {
|
||
httpOnly: true,
|
||
secure: process.env.NODE_ENV === 'production',
|
||
maxAge: 60 * 60 * 24 * 7,
|
||
path: '/',
|
||
});
|
||
redirect('/admin');
|
||
}
|
||
return { error: 'Hatalı kullanıcı adı veya şifre' };
|
||
} catch (e: any) {
|
||
console.error("Login error:", e);
|
||
return { error: 'Giriş sırasında hata oluştu: ' + (e.message || '') };
|
||
}
|
||
}
|
||
|
||
export async function logout() {
|
||
const cookieStore = await cookies();
|
||
cookieStore.delete('admin_session');
|
||
cookieStore.delete('admin_user');
|
||
redirect('/admin/login');
|
||
}
|
||
|
||
export async function getDashboardStats() {
|
||
try {
|
||
const [leadsCount] = await sql`SELECT count(*) FROM leads`;
|
||
const [projectsCount] = await sql`SELECT count(*) FROM projects`;
|
||
const [servicesCount] = await sql`SELECT count(*) FROM services`;
|
||
|
||
return {
|
||
leads: Number(leadsCount.count),
|
||
projects: Number(projectsCount.count),
|
||
services: Number(servicesCount.count),
|
||
};
|
||
} catch (e) {
|
||
return { leads: 0, projects: 0, services: 0 };
|
||
}
|
||
}
|
||
|
||
export async function getLeadsAdmin() {
|
||
try {
|
||
return await sql`SELECT * FROM leads ORDER BY created_at DESC`;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export async function deleteLead(id: number) {
|
||
try {
|
||
await sql`DELETE FROM leads WHERE id = ${id}`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
return { error: 'Silinemedi' };
|
||
}
|
||
}
|
||
|
||
export async function updateSettings(formData: FormData) {
|
||
try {
|
||
const data = {
|
||
site_name: formData.get('site_name') as string,
|
||
site_description: formData.get('site_description') as string,
|
||
office_address: formData.get('office_address') as string,
|
||
contact_email: formData.get('contact_email') as string,
|
||
contact_phone: formData.get('contact_phone') as string,
|
||
instagram_url: formData.get('instagram_url') as string,
|
||
twitter_url: formData.get('twitter_url') as string,
|
||
linkedin_url: formData.get('linkedin_url') as string,
|
||
status_badge_text: formData.get('status_badge_text') as string,
|
||
};
|
||
|
||
await sql`
|
||
UPDATE settings SET
|
||
site_name = ${data.site_name},
|
||
site_description = ${data.site_description},
|
||
office_address = ${data.office_address},
|
||
contact_email = ${data.contact_email},
|
||
contact_phone = ${data.contact_phone},
|
||
instagram_url = ${data.instagram_url},
|
||
twitter_url = ${data.twitter_url},
|
||
linkedin_url = ${data.linkedin_url},
|
||
status_badge_text = ${data.status_badge_text},
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
return { error: 'Güncellenemedi' };
|
||
}
|
||
}
|
||
|
||
export async function getProjectsAdmin() {
|
||
try {
|
||
return await sql`SELECT * FROM projects ORDER BY created_at DESC`;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export async function getProjectByIdAdmin(id: number) {
|
||
try {
|
||
const result = await sql`SELECT * FROM projects WHERE id = ${id}`;
|
||
return result[0];
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export async function deleteProject(id: number) {
|
||
try {
|
||
await sql`DELETE FROM projects WHERE id = ${id}`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
return { error: 'Silinemedi' };
|
||
}
|
||
}
|
||
|
||
export async function createProjectAdmin(formData: FormData) {
|
||
try {
|
||
const slug = formData.get('slug') as string;
|
||
const title = formData.get('title') as string;
|
||
const subtitle = formData.get('subtitle') as string;
|
||
const year = formData.get('year') as string;
|
||
const hero_image = formData.get('hero_image') as string;
|
||
const client = formData.get('client') as string;
|
||
const role = formData.get('role') as string;
|
||
const location = formData.get('location') as string;
|
||
const narrative_title = formData.get('narrative_title') as string;
|
||
const narrative_desc = formData.get('narrative_desc') as string;
|
||
const is_featured = formData.get('is_featured') === 'on';
|
||
|
||
const categoriesRaw = formData.getAll('category') as string[];
|
||
const techStackRaw = formData.get('tech_stack') as string;
|
||
const galleryRaw = formData.get('gallery') as string;
|
||
|
||
const category = JSON.stringify(categoriesRaw.filter(Boolean));
|
||
const tech_stack = JSON.stringify(techStackRaw ? techStackRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
|
||
const gallery = JSON.stringify(galleryRaw ? galleryRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
|
||
|
||
await sql`
|
||
INSERT INTO projects (
|
||
slug, title, subtitle, category, year, hero_image, client, role, location,
|
||
narrative_title, narrative_desc, is_featured, tech_stack, gallery
|
||
) VALUES (
|
||
${slug}, ${title}, ${subtitle}, ${category}::jsonb, ${year}, ${hero_image}, ${client}, ${role}, ${location},
|
||
${narrative_title}, ${narrative_desc}, ${is_featured}, ${tech_stack}::jsonb, ${gallery}::jsonb
|
||
)
|
||
`;
|
||
return { success: true };
|
||
} catch (e: any) {
|
||
console.error('Error creating project:', e);
|
||
return { error: 'Ekleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
|
||
}
|
||
}
|
||
|
||
export async function updateProjectAdmin(id: number, formData: FormData) {
|
||
try {
|
||
const slug = formData.get('slug') as string;
|
||
const title = formData.get('title') as string;
|
||
const subtitle = formData.get('subtitle') as string;
|
||
const year = formData.get('year') as string;
|
||
const hero_image = formData.get('hero_image') as string;
|
||
const client = formData.get('client') as string;
|
||
const role = formData.get('role') as string;
|
||
const location = formData.get('location') as string;
|
||
const narrative_title = formData.get('narrative_title') as string;
|
||
const narrative_desc = formData.get('narrative_desc') as string;
|
||
const is_featured = formData.get('is_featured') === 'on';
|
||
|
||
const categoriesRaw = formData.getAll('category') as string[];
|
||
const techStackRaw = formData.get('tech_stack') as string;
|
||
const galleryRaw = formData.get('gallery') as string;
|
||
|
||
const category = JSON.stringify(categoriesRaw.filter(Boolean));
|
||
const tech_stack = JSON.stringify(techStackRaw ? techStackRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
|
||
const gallery = JSON.stringify(galleryRaw ? galleryRaw.split(/[\n,]/).map(s => s.trim()).filter(Boolean) : []);
|
||
|
||
await sql`
|
||
UPDATE projects SET
|
||
slug = ${slug}, title = ${title}, subtitle = ${subtitle}, category = ${category}::jsonb,
|
||
year = ${year}, hero_image = ${hero_image}, client = ${client}, role = ${role},
|
||
location = ${location}, narrative_title = ${narrative_title},
|
||
narrative_desc = ${narrative_desc}, is_featured = ${is_featured},
|
||
tech_stack = ${tech_stack}::jsonb, gallery = ${gallery}::jsonb
|
||
WHERE id = ${id}
|
||
`;
|
||
return { success: true };
|
||
} catch (e: any) {
|
||
console.error('Error updating project:', e);
|
||
return { error: 'Güncelleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
|
||
}
|
||
}
|
||
|
||
export async function getServicesAdmin() {
|
||
try {
|
||
return await sql`SELECT * FROM services ORDER BY display_order ASC`;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export async function deleteService(id: number) {
|
||
try {
|
||
await sql`DELETE FROM services WHERE id = ${id}`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
return { error: 'Silinemedi' };
|
||
}
|
||
}
|
||
|
||
export async function getPartnersAdmin() {
|
||
try {
|
||
return await sql`SELECT * FROM partners ORDER BY display_order ASC`;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export async function deletePartner(id: number) {
|
||
try {
|
||
await sql`DELETE FROM partners WHERE id = ${id}`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
return { error: 'Silinemedi' };
|
||
}
|
||
}
|
||
|
||
export async function createPartnerAdmin(formData: FormData) {
|
||
try {
|
||
const name = formData.get('name') as string;
|
||
const logoFile = formData.get('logo') as File;
|
||
let logoUrl = '';
|
||
|
||
if (logoFile && logoFile.size > 0) {
|
||
logoUrl = await uploadToCloudinary(logoFile, 'partners');
|
||
}
|
||
|
||
const display_order = Number(formData.get('display_order')) || 0;
|
||
|
||
await sql`
|
||
INSERT INTO partners (name, logo, display_order)
|
||
VALUES (${name}, ${logoUrl}, ${display_order})
|
||
`;
|
||
return { success: true };
|
||
} catch (e: any) {
|
||
console.error('Error creating partner:', e);
|
||
return { error: 'Ekleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
|
||
}
|
||
}
|
||
|
||
export async function updatePartner(id: number, name: string, display_order: number) {
|
||
try {
|
||
await sql`
|
||
UPDATE partners
|
||
SET name = ${name}, display_order = ${display_order}
|
||
WHERE id = ${id}
|
||
`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
return { error: 'Güncellenemedi' };
|
||
}
|
||
}
|
||
|
||
export async function getAdminsAdmin() {
|
||
try {
|
||
await ensureAdminsTable();
|
||
return await sql`SELECT id, username, created_at FROM admins ORDER BY id ASC`;
|
||
} catch (e) {
|
||
console.error("Error getting admins:", e);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
export async function createAdminAdmin(formData: FormData) {
|
||
try {
|
||
await ensureAdminsTable();
|
||
const username = (formData.get('username') as string).trim();
|
||
const password = formData.get('password') as string;
|
||
|
||
if (!username || !password) {
|
||
return { error: 'Kullanıcı adı ve şifre gereklidir.' };
|
||
}
|
||
|
||
const hashed = hashPassword(password);
|
||
|
||
await sql`
|
||
INSERT INTO admins (username, password)
|
||
VALUES (${username}, ${hashed})
|
||
`;
|
||
return { success: true };
|
||
} catch (e: any) {
|
||
console.error('Error creating admin:', e);
|
||
if (e.message?.includes('unique constraint')) {
|
||
return { error: 'Bu kullanıcı adı zaten alınmış.' };
|
||
}
|
||
return { error: 'Ekleme başarısız: ' + (e.message || 'Bilinmeyen hata') };
|
||
}
|
||
}
|
||
|
||
export async function updateAdminPassword(id: number, newPassword: string) {
|
||
try {
|
||
await ensureAdminsTable();
|
||
if (!newPassword || newPassword.length < 4) {
|
||
return { error: 'Şifre en az 4 karakter olmalıdır.' };
|
||
}
|
||
const hashed = hashPassword(newPassword);
|
||
await sql`
|
||
UPDATE admins
|
||
SET password = ${hashed}
|
||
WHERE id = ${id}
|
||
`;
|
||
return { success: true };
|
||
} catch (e: any) {
|
||
console.error('Error updating admin password:', e);
|
||
return { error: 'Şifre güncellenemedi.' };
|
||
}
|
||
}
|
||
|
||
export async function deleteAdminAdmin(id: number) {
|
||
try {
|
||
await ensureAdminsTable();
|
||
|
||
// Prevent deleting the last remaining admin
|
||
const adminsCount = await sql`SELECT count(*) FROM admins`;
|
||
if (Number(adminsCount[0].count) <= 1) {
|
||
return { error: 'Sistemde en az bir yönetici bulunmalıdır. Son yönetici silinemez!' };
|
||
}
|
||
|
||
await sql`
|
||
DELETE FROM admins
|
||
WHERE id = ${id}
|
||
`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
console.error('Error deleting admin:', e);
|
||
return { error: 'Silinemedi.' };
|
||
}
|
||
}
|
||
|
||
export async function getCurrentAdmin() {
|
||
const cookieStore = await cookies();
|
||
return cookieStore.get('admin_user')?.value || 'admin';
|
||
}
|
||
|
||
export async function togglePartnerFeatured(id: number, isFeatured: boolean) {
|
||
try {
|
||
await sql`
|
||
UPDATE partners
|
||
SET is_featured = ${isFeatured}
|
||
WHERE id = ${id}
|
||
`;
|
||
return { success: true };
|
||
} catch (e) {
|
||
console.error('Error toggling partner featured:', e);
|
||
return { error: 'Öne çıkarma durumu güncellenemedi' };
|
||
}
|
||
}
|
||
|
||
|
||
|