Files
nfc-attendance-hub/lib/semaphore.ts
Nemo b273f1a573 feat: initial NFC Attendance Hub implementation
- Multi-tenant client management with unique API keys
- Semaphore SMS integration (per-client key + sender name + credit balance)
- DB-based SMS queue with 3-attempt retry and exponential backoff
- Failed SMS dashboard with manual retry button
- Reports page with date/client filter and CSV export
- Admin auth via NextAuth (email/password)
- Docker Compose setup (app + PostgreSQL 16)
- Prisma 5 schema with SmsQueue, SmsLog, Client, User models

Tech: Next.js 14 App Router + TypeScript + Tailwind CSS + Prisma + PostgreSQL
2026-03-12 08:49:13 +00:00

59 lines
1.4 KiB
TypeScript

import axios from 'axios'
const SEMAPHORE_API_URL = 'https://api.semaphore.co/api/v4'
export interface SemaphoreResponse {
success: boolean
messageId?: string
error?: string
}
export async function sendSms(
apiKey: string,
senderName: string,
phone: string,
message: string
): Promise<SemaphoreResponse> {
try {
const response = await axios.post(
`${SEMAPHORE_API_URL}/messages`,
{
apikey: apiKey,
number: phone,
message,
sendername: senderName,
},
{ timeout: 15000 }
)
const data = response.data
if (Array.isArray(data) && data.length > 0) {
const msg = data[0]
if (msg.status === 'Queued' || msg.status === 'Sent') {
return { success: true, messageId: String(msg.message_id) }
}
return { success: false, error: msg.status || 'Unknown status' }
}
return { success: false, error: 'Empty response from Semaphore' }
} catch (err: any) {
const errorMsg =
err.response?.data?.message ||
err.response?.data ||
err.message ||
'SMS send failed'
return { success: false, error: String(errorMsg) }
}
}
export async function getBalance(apiKey: string): Promise<number | null> {
try {
const response = await axios.get(`${SEMAPHORE_API_URL}/account`, {
params: { apikey: apiKey },
timeout: 10000,
})
return response.data?.credit_balance ?? null
} catch {
return null
}
}