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
This commit is contained in:
90
prisma/schema.prisma
Normal file
90
prisma/schema.prisma
Normal file
@@ -0,0 +1,90 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
password String
|
||||
name String?
|
||||
role Role @default(ADMIN)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Client {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String @unique
|
||||
apiKey String @unique @default(cuid())
|
||||
isActive Boolean @default(true)
|
||||
semaphoreKey String?
|
||||
senderName String? @default("NFC-HUB")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
smsLogs SmsLog[]
|
||||
smsQueue SmsQueue[]
|
||||
}
|
||||
|
||||
model SmsQueue {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
studentName String
|
||||
studentId String
|
||||
parentPhone String
|
||||
event EventType
|
||||
timestamp DateTime
|
||||
message String?
|
||||
status SmsStatus @default(PENDING)
|
||||
attempts Int @default(0)
|
||||
lastError String?
|
||||
scheduledAt DateTime @default(now())
|
||||
processedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
smsLog SmsLog?
|
||||
}
|
||||
|
||||
model SmsLog {
|
||||
id String @id @default(cuid())
|
||||
clientId String
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
queueId String @unique
|
||||
queue SmsQueue @relation(fields: [queueId], references: [id])
|
||||
phone String
|
||||
message String
|
||||
status LogStatus
|
||||
semaphoreId String?
|
||||
errorReason String?
|
||||
sentAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
VIEWER
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
time_in
|
||||
time_out
|
||||
}
|
||||
|
||||
enum SmsStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
SENT
|
||||
FAILED
|
||||
RETRYING
|
||||
}
|
||||
|
||||
enum LogStatus {
|
||||
SUCCESS
|
||||
FAILED
|
||||
}
|
||||
Reference in New Issue
Block a user