hata düzeltme 1

This commit is contained in:
2026-01-13 22:37:50 +03:00
parent 32009b4886
commit dc1b6f1359
11 changed files with 658 additions and 30 deletions

130
lib/sms/actions.ts Normal file
View File

@@ -0,0 +1,130 @@
"use server"
import { createClient } from "@/lib/supabase-server"
import { createClient as createSupabaseClient } from "@supabase/supabase-js"
import { revalidatePath } from "next/cache"
import { NetGsmService } from "./netgsm"
// Admin client for privileged operations (accessing sms_settings)
const supabaseAdmin = createSupabaseClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false
}
}
)
async function assertAdmin() {
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) throw new Error("Oturum açmanız gerekiyor.")
const { data: profile } = await supabase.from('profiles').select('role').eq('id', user.id).single()
if (profile?.role !== 'admin') throw new Error("Yetkisiz işlem.")
return user
}
export async function getSmsSettings() {
try {
await assertAdmin()
const { data, error } = await supabaseAdmin
.from('sms_settings')
.select('*')
.single()
if (error && error.code !== 'PGRST116') { // PGRST116 is 'not found', which is fine initially
throw error
}
return { data }
} catch (error) {
return { error: (error as Error).message }
}
}
export async function updateSmsSettings(data: {
username: string
password?: string // Optional if not changing
header: string
}) {
try {
await assertAdmin()
// Check if exists
const { data: existing } = await supabaseAdmin.from('sms_settings').select('id').single()
const updates: any = {
username: data.username,
header: data.header,
updated_at: new Date().toISOString()
}
// Only update password if provided
if (data.password && data.password.trim() !== '') {
updates.password = data.password
}
if (existing) {
const { error } = await supabaseAdmin
.from('sms_settings')
.update(updates)
.eq('id', existing.id)
if (error) throw error
} else {
// First time setup, password is mandatory if not exists, but we can't easily check 'locally'
// We assume if new, password must be in updates.
if (!data.password) throw new Error("Yeni kurulum için şifre gereklidir.")
const { error } = await supabaseAdmin
.from('sms_settings')
.insert({ ...updates, password: data.password })
if (error) throw error
}
revalidatePath("/dashboard/settings")
return { success: true }
} catch (error) {
return { error: (error as Error).message }
}
}
export async function sendTestSms(phone: string) {
try {
await assertAdmin()
// Fetch credentials
const { data: settings } = await supabaseAdmin.from('sms_settings').select('*').single()
if (!settings) throw new Error("SMS ayarları yapılmamış.")
const mobileService = new NetGsmService({
username: settings.username,
password: settings.password,
header: settings.header,
apiUrl: settings.api_url
})
const result = await mobileService.sendSms(phone, "ParaKasa Test SMS: Entegrasyon basarili.")
// Log the result
await supabaseAdmin.from('sms_logs').insert({
phone,
message: "ParaKasa Test SMS: Entegrasyon basarili.",
status: result.success ? 'success' : 'error',
response_code: result.code || result.error
})
if (!result.success) {
throw new Error(result.error || "SMS gönderilemedi.")
}
return { success: true, jobId: result.jobId }
} catch (error) {
return { error: (error as Error).message }
}
}

88
lib/sms/netgsm.ts Normal file
View File

@@ -0,0 +1,88 @@
export interface NetGsmConfig {
username?: string;
password?: string;
header?: string;
apiUrl?: string;
}
export interface SmsResult {
success: boolean;
jobId?: string;
error?: string;
code?: string;
}
export class NetGsmService {
private config: NetGsmConfig;
constructor(config: NetGsmConfig) {
this.config = config;
}
/**
* Send SMS using NetGSM GET API
* Refer: https://www.netgsm.com.tr/dokuman/#http-get-servisi
*/
async sendSms(phone: string, message: string): Promise<SmsResult> {
if (!this.config.username || !this.config.password || !this.config.header) {
return { success: false, error: "NetGSM konfigürasyonu eksik." };
}
// Clean phone number (remove spaces, parentheses, etc)
// NetGSM expects 905xxxxxxxxx or just 5xxxxxxxxx, we'll ensure format
let cleanPhone = phone.replace(/\D/g, '');
if (cleanPhone.startsWith('90')) {
cleanPhone = cleanPhone.substring(0); // keep it
} else if (cleanPhone.startsWith('0')) {
cleanPhone = '9' + cleanPhone;
} else if (cleanPhone.length === 10) {
cleanPhone = '90' + cleanPhone;
}
try {
// Encode parameters
const params = new URLSearchParams({
usercode: this.config.username,
password: this.config.password,
gsmno: cleanPhone,
message: message,
msgheader: this.config.header,
dil: 'TR' // Turkish characters support
});
const url = `${this.config.apiUrl || 'https://api.netgsm.com.tr/sms/send/get'}?${params.toString()}`;
const response = await fetch(url);
const textResponse = await response.text();
// NetGSM returns a code (e.g. 00 123456789) or error code (e.g. 20)
// Codes starting with 00, 01, 02 indicate success
const code = textResponse.split(' ')[0];
if (['00', '01', '02'].includes(code)) {
return { success: true, jobId: textResponse.split(' ')[1] || code, code };
} else {
const errorMap: Record<string, string> = {
'20': 'Mesaj metni ya da karakter sınırını (1.000) aştı veya mesaj boş.',
'30': 'Geçersiz kullanıcı adı , şifre veya kullanıcınızın API erişim izni yok.',
'40': 'Gönderici adı (Başlık) sistemde tanımlı değil.',
'70': 'Hatalı sorgu.',
'50': 'Kendi numaranıza veya Rehberden SMS gönderiyorsanız; Abone kendi numarasını veya rehberindeki bir numarayı gönderici kimliği (MsgHeader) olarak kullanamaz.',
'51': 'Aboneliğinizin süresi dolmuş.',
'52': 'Aboneliğiniz bulunmamaktadır.',
'60': 'Bakiyeniz yetersiz.',
'71': 'Gönderim yapmak istediğiniz gsm numarası/numaraları hatalı.'
};
return {
success: false,
code,
error: errorMap[code] || `Bilinmeyen hata kodu: ${code} - Yanıt: ${textResponse}`
};
}
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
}