init: initial commit
This commit is contained in:
commit
e58b5c70c3
|
|
@ -0,0 +1,9 @@
|
|||
DATABASE_URL="dialect://username:password@host:port/db_name?ssl-mode=REQUIRED"
|
||||
DATABASE_URL_UTILITY="dialect://username:password@host:port/db_name?ssl-mode=REQUIRED"
|
||||
X_API_KEY="XYZ1234567890"
|
||||
AI_API_KEY="XXXXXYYYZZZZZ"
|
||||
MINIO_ENDPOINT="minio.example.com"
|
||||
MINIO_ACCESS_KEY="username"
|
||||
MINIO_SECRET_KEY="password"
|
||||
JWT_SECRET_KEY="YOURSECRETKEY"
|
||||
PORT=3000
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
name: Deploy Backend CIFO Superapps
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: npm
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Create Backend Deployment Files
|
||||
run: |
|
||||
mkdir -p backend-tar
|
||||
tar \
|
||||
--exclude=node_modules \
|
||||
--exclude=backend-tar \
|
||||
-czf backend-tar/backend.tar.gz .
|
||||
|
||||
- name: Upload To VPS
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USERNAME }}
|
||||
password: ${{ secrets.VPS_PASSWORD }}
|
||||
port: ${{ secrets.VPS_PORT }}
|
||||
source: "backend-tar/backend.tar.gz"
|
||||
target: "/tmp/"
|
||||
|
||||
- name: Deploy on VPS
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USERNAME }}
|
||||
password: ${{ secrets.VPS_PASSWORD }}
|
||||
port: ${{ secrets.VPS_PORT }}
|
||||
script: |
|
||||
set -e
|
||||
echo "🚀 Starting deployment..."
|
||||
|
||||
DEPLOY_DIR="/home/${{ secrets.VPS_USERNAME }}/backend-cifo-superapps"
|
||||
TAR_FILE="/tmp/backend-tar/backend.tar.gz"
|
||||
|
||||
echo "Using uploaded TAR file at $TAR_FILE"
|
||||
|
||||
cd /tmp
|
||||
rm -rf backend-temp
|
||||
mkdir backend-temp
|
||||
|
||||
echo "Extracting archive..."
|
||||
tar -xzf $TAR_FILE -C backend-temp
|
||||
|
||||
echo "Cleaning deploy directory safely (preserving .env & .git)..."
|
||||
echo '${{ secrets.VPS_PASSWORD }}' | sudo -S sh -c "
|
||||
cd $DEPLOY_DIR
|
||||
for item in * .*; do
|
||||
if [ \"\$item\" != \".\" ] && [ \"\$item\" != \"..\" ] && [ \"\$item\" != \".env\" ] && [ \"\$item\" != \".git\" ]; then
|
||||
rm -rf \"\$item\"
|
||||
fi
|
||||
done
|
||||
"
|
||||
|
||||
echo "Copying new backend..."
|
||||
echo '${{ secrets.VPS_PASSWORD }}' | sudo -S cp -r backend-temp/* "$DEPLOY_DIR"
|
||||
|
||||
echo '${{ secrets.VPS_PASSWORD }}' | sudo -S chown -R ${{ secrets.VPS_USERNAME }}:${{ secrets.VPS_USERNAME }} "$DEPLOY_DIR"
|
||||
|
||||
cd "$DEPLOY_DIR"
|
||||
npm install
|
||||
|
||||
# Prisma CMS
|
||||
npx prisma generate --schema=./prisma/schema.cms.prisma
|
||||
npx prisma migrate deploy --schema=./prisma/schema.cms.prisma
|
||||
|
||||
echo "🔄 Restarting backend service..."
|
||||
echo '${{ secrets.VPS_PASSWORD }}' | sudo systemctl restart cifosuperapps-backend.service
|
||||
|
||||
rm -rf $TAR_FILE
|
||||
rm -rf /tmp/backend-temp
|
||||
|
||||
- name: Healthcheck
|
||||
run: |
|
||||
echo "⏳ Waiting backend to boot..."
|
||||
sleep 10
|
||||
|
||||
STATUS=$(curl -o /dev/null -s -w "%{http_code}" http://103.14.20.74:3000/api-management/test)
|
||||
echo "HTTP Status: $STATUS"
|
||||
|
||||
if [ "$STATUS" -ne 200 ]; then
|
||||
echo "Deployment failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env.dev
|
||||
.env.staging
|
||||
.env.prod
|
||||
# Ignore application log files
|
||||
app.log
|
||||
# Ignore generated Prisma client
|
||||
/app/generated/prisma
|
||||
|
|
@ -0,0 +1,603 @@
|
|||
# Backend CSA - Campaign & Content Management System
|
||||
|
||||
Backend API untuk Campaign & Content Management System dengan Firebase Cloud Messaging integration dan advanced analytics.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Tech Stack](#tech-stack)
|
||||
- [Database Schema](#database-schema)
|
||||
- [API Endpoints](#api-endpoints)
|
||||
- [Installation](#installation)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Campaign Analytics](#campaign-analytics)
|
||||
- [Project Structure](#project-structure)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### Content Management
|
||||
- Multi-type content (splash, promo, article, banner, floating widget)
|
||||
- Multi-corporation support (Walanja, Simaya, CIFO)
|
||||
- File upload & management with MinIO
|
||||
- CRUD operations for all content types
|
||||
|
||||
### Campaign Management
|
||||
- Scheduled notification campaigns
|
||||
- Firebase Cloud Messaging integration
|
||||
- **Real-time delivery tracking per user**
|
||||
- **Comprehensive analytics & reporting**
|
||||
- Status management (pending, completed, failed, cancelled)
|
||||
- Automatic campaign execution via cron jobs
|
||||
|
||||
### User Management
|
||||
- User token management
|
||||
- Activity tracking (Hotel, Room, Retail, CCTV visits)
|
||||
- User-level notification delivery tracking
|
||||
|
||||
### Admin Features
|
||||
- Admin account management
|
||||
- API credential management
|
||||
- System statistics & analytics
|
||||
|
||||
### Advanced Analytics
|
||||
- Campaign performance metrics
|
||||
- Delivery rate tracking
|
||||
- Error analysis & breakdown
|
||||
- Timeline visualization data
|
||||
- Top performing campaigns
|
||||
- User engagement metrics
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Tech Stack
|
||||
|
||||
- **Runtime:** Node.js
|
||||
- **Framework:** Express.js
|
||||
- **Database:** MySQL (Prisma ORM)
|
||||
- **Storage:** MinIO
|
||||
- **Notifications:** Firebase Admin SDK
|
||||
- **Authentication:** JWT + API Key
|
||||
- **Logger:** Winston
|
||||
- **Scheduler:** node-cron
|
||||
|
||||
---
|
||||
|
||||
## 🗄 Database Schema
|
||||
|
||||
### Core Models
|
||||
|
||||
#### **AppCampaign**
|
||||
Campaign dengan tracking lengkap:
|
||||
```prisma
|
||||
model AppCampaign {
|
||||
UUID_ACP String
|
||||
Title_ACP String
|
||||
Content_ACP String
|
||||
Date_ACP DateTime
|
||||
Status_ACP CampaignStatus
|
||||
|
||||
// Analytics fields
|
||||
TargetUsers_ACP Int?
|
||||
SentCount_ACP Int?
|
||||
SuccessCount_ACP Int?
|
||||
FailureCount_ACP Int?
|
||||
DeliveryRate_ACP Float?
|
||||
SentAt_ACP DateTime?
|
||||
CompletedAt_ACP DateTime?
|
||||
ErrorMessage_ACP String?
|
||||
}
|
||||
```
|
||||
|
||||
#### **CampaignDelivery**
|
||||
Per-user delivery tracking:
|
||||
```prisma
|
||||
model CampaignDelivery {
|
||||
UUID_CD String
|
||||
Campaign_CD String
|
||||
UserID_CD String
|
||||
Token_CD String
|
||||
Status_CD DeliveryStatus
|
||||
SentAt_CD DateTime?
|
||||
DeliveredAt_CD DateTime?
|
||||
FailedAt_CD DateTime?
|
||||
ErrorMessage_CD String?
|
||||
ResponseData_CD String?
|
||||
}
|
||||
```
|
||||
|
||||
#### **AppContent**
|
||||
```prisma
|
||||
model AppContent {
|
||||
UUID_APC String
|
||||
Title_APC String
|
||||
Content_APC String
|
||||
Type_APC ContentType
|
||||
CorpType_APC CorpType
|
||||
Url_APC String?
|
||||
Filename_APC String?
|
||||
TargetUrl_APC String?
|
||||
}
|
||||
```
|
||||
|
||||
#### **UsersToken**
|
||||
```prisma
|
||||
model UsersToken {
|
||||
UUID_UT String
|
||||
UserID_UT String
|
||||
Token_UT String
|
||||
UsersActivity UsersActivity[]
|
||||
}
|
||||
```
|
||||
|
||||
### Enums
|
||||
|
||||
```prisma
|
||||
enum CampaignStatus {
|
||||
pending
|
||||
completed
|
||||
failed
|
||||
cancelled
|
||||
}
|
||||
|
||||
enum DeliveryStatus {
|
||||
pending
|
||||
sent
|
||||
delivered
|
||||
failed
|
||||
}
|
||||
|
||||
enum ContentType {
|
||||
splash
|
||||
promo
|
||||
article
|
||||
banner
|
||||
floatingWidget
|
||||
}
|
||||
|
||||
enum CorpType {
|
||||
walanja
|
||||
simaya
|
||||
cifo
|
||||
}
|
||||
|
||||
enum ActivityType {
|
||||
VisitHotel
|
||||
VisitRoom
|
||||
VisitRetail
|
||||
VisitCCTV
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API Endpoints
|
||||
|
||||
### Authentication
|
||||
All endpoints require `x-api-key` header (except `/api-management/test`)
|
||||
|
||||
### API Management
|
||||
- `GET /api-management/test` - Test API connection
|
||||
- `GET /api-management/test/secure` - Test secure API (requires API key)
|
||||
- `POST /api-management/token` - Create new API token
|
||||
- `DELETE /api-management/token` - Delete API token
|
||||
- `GET /api-management/tokens` - Get all API tokens
|
||||
|
||||
### Content Management (CMS)
|
||||
- `POST /cms/content` - Create content
|
||||
- `GET /cms/content/:type/:corp` - Get contents by type & corp
|
||||
- `PUT /cms/content/:id` - Update content
|
||||
- `DELETE /cms/content/:id` - Delete content
|
||||
- `GET /cms/stats` - Get CMS statistics with charts data
|
||||
|
||||
### Campaign Management
|
||||
- `POST /campaign-management/setup` - Create campaign
|
||||
- `GET /campaign-management/all` - Get all campaigns (filterable by status)
|
||||
- `PUT /campaign-management/:id` - Update campaign
|
||||
- `DELETE /campaign-management/:id` - Cancel campaign
|
||||
- `POST /campaign-management/send` - Send notification to specific user
|
||||
- `GET /campaign-management/analytics` - **Get campaign analytics & metrics**
|
||||
- `GET /campaign-management/report/:id` - **Get detailed campaign report**
|
||||
|
||||
### User Management
|
||||
- `POST /user-management/setup-token` - Setup user FCM token
|
||||
- `GET /user-management/get-all` - Get all users
|
||||
- `DELETE /user-management/delete` - Delete user
|
||||
|
||||
---
|
||||
|
||||
## 📊 Campaign Analytics
|
||||
|
||||
### Analytics Dashboard (`GET /campaign-management/analytics`)
|
||||
|
||||
Comprehensive analytics untuk monitoring campaign performance.
|
||||
|
||||
#### Response Structure:
|
||||
```javascript
|
||||
{
|
||||
summary: {
|
||||
campaigns: {
|
||||
total: 50,
|
||||
completed: 40,
|
||||
failed: 3,
|
||||
pending: 5,
|
||||
cancelled: 2,
|
||||
successRate: "93.02%",
|
||||
avgResponseTime: "5 minutes",
|
||||
upcomingCount: 5
|
||||
},
|
||||
delivery: {
|
||||
totalTargetUsers: 5000,
|
||||
totalSent: 5000,
|
||||
totalDelivered: 4750,
|
||||
totalFailed: 250,
|
||||
overallDeliveryRate: "95.00%",
|
||||
totalDeliveryRecords: 5000
|
||||
}
|
||||
},
|
||||
|
||||
charts: {
|
||||
// Pie Chart: Campaign status distribution
|
||||
statusDistribution: {
|
||||
labels: ["completed", "failed", "pending", "cancelled"],
|
||||
data: [40, 3, 5, 2]
|
||||
},
|
||||
|
||||
// Pie Chart: Delivery status
|
||||
deliveryStatusDistribution: {
|
||||
labels: ["delivered", "failed", "pending"],
|
||||
data: [4750, 250, 0]
|
||||
},
|
||||
|
||||
// Line Chart: Campaign timeline (30 days)
|
||||
campaignTimeline: {
|
||||
"2025-11-01": { total: 5, completed: 4, failed: 1 }
|
||||
},
|
||||
|
||||
// Line Chart: Delivery rate trend
|
||||
deliveryRateTrend: {
|
||||
labels: ["2025-11-01", "2025-11-02"],
|
||||
data: [95.5, 96.2]
|
||||
},
|
||||
|
||||
// Stacked Bar: Status over time (7 days)
|
||||
statusOverTime: { ... }
|
||||
},
|
||||
|
||||
// Top 10 best performing campaigns
|
||||
topPerforming: [
|
||||
{
|
||||
id: "uuid",
|
||||
title: "Campaign Title",
|
||||
targetUsers: 100,
|
||||
successCount: 98,
|
||||
deliveryRate: 98.00
|
||||
}
|
||||
],
|
||||
|
||||
upcoming: [...],
|
||||
recentActivity: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Individual Campaign Report (`GET /campaign-management/report/:id`)
|
||||
|
||||
Detailed report untuk campaign spesifik dengan per-user tracking.
|
||||
|
||||
#### Response Structure:
|
||||
```javascript
|
||||
{
|
||||
campaign: {
|
||||
id: "uuid",
|
||||
title: "Campaign Title",
|
||||
content: "Content",
|
||||
status: "completed",
|
||||
scheduledDate: "2025-11-20T10:00:00Z",
|
||||
errorMessage: null
|
||||
},
|
||||
|
||||
metrics: {
|
||||
leadTime: "19 hours", // Campaign preparation time
|
||||
executionTime: "5 minutes", // Actual sending time
|
||||
avgDeliveryTime: "2 seconds", // Avg time to deliver
|
||||
isScheduled: false,
|
||||
isOverdue: false
|
||||
},
|
||||
|
||||
delivery: {
|
||||
targetUsers: 100,
|
||||
sentCount: 100,
|
||||
successCount: 95,
|
||||
failureCount: 5,
|
||||
deliveryRate: "95.00%",
|
||||
sentAt: "2025-11-20T10:00:00Z",
|
||||
completedAt: "2025-11-20T10:05:00Z",
|
||||
|
||||
// Breakdown per status
|
||||
statusBreakdown: {
|
||||
pending: 0,
|
||||
sent: 0,
|
||||
delivered: 95,
|
||||
failed: 5
|
||||
},
|
||||
|
||||
// Error analysis
|
||||
errorBreakdown: {
|
||||
"Invalid token": 3,
|
||||
"Token not registered": 2
|
||||
}
|
||||
},
|
||||
|
||||
timeline: {
|
||||
created: "2025-11-19T15:00:00Z",
|
||||
scheduled: "2025-11-20T10:00:00Z",
|
||||
sent: "2025-11-20T10:00:00Z",
|
||||
completed: "2025-11-20T10:05:00Z"
|
||||
},
|
||||
|
||||
// Per-user delivery records (max 100)
|
||||
deliveryRecords: {
|
||||
total: 100,
|
||||
records: [
|
||||
{
|
||||
UUID_CD: "uuid",
|
||||
UserID_CD: "user123",
|
||||
Status_CD: "delivered",
|
||||
SentAt_CD: "2025-11-20T10:00:01Z",
|
||||
DeliveredAt_CD: "2025-11-20T10:00:03Z",
|
||||
ErrorMessage_CD: null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Key Metrics:
|
||||
- **Success Rate**: (Completed / Total Executed) × 100
|
||||
- **Delivery Rate**: (Success Count / Sent Count) × 100
|
||||
- **Lead Time**: Time between creation and scheduled date
|
||||
- **Execution Time**: Time taken to send all notifications
|
||||
- **Avg Delivery Time**: Average time from sent to delivered
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### Prerequisites
|
||||
- Node.js (v16+)
|
||||
- MySQL Database
|
||||
- MinIO Server
|
||||
- Firebase Project with Admin SDK
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Clone repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd backend-csa
|
||||
```
|
||||
|
||||
2. **Install dependencies**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Configure environment**
|
||||
Create `.env` file:
|
||||
```env
|
||||
DATABASE_URL="mysql://user:password@host:port/database?ssl-mode=REQUIRED"
|
||||
DATABASE_URL_UTILITY="mysql://user:password@host:port/database?ssl-mode=REQUIRED"
|
||||
X_API_KEY="your-api-key"
|
||||
AI_API_KEY="your-ai-key"
|
||||
MINIO_ENDPOINT="storage.example.com"
|
||||
MINIO_ACCESS_KEY="admin"
|
||||
MINIO_SECRET_KEY="secret"
|
||||
JWT_SECRET_KEY="your-jwt-secret"
|
||||
```
|
||||
|
||||
4. **Setup Firebase**
|
||||
Place Firebase service account key at:
|
||||
```
|
||||
app/config/serviceAccountKey.json
|
||||
```
|
||||
|
||||
5. **Run migrations**
|
||||
```bash
|
||||
npx prisma migrate deploy --schema=./prisma/schemas/schema.cms.prisma
|
||||
npx prisma generate --schema=./prisma/schemas/schema.cms.prisma
|
||||
```
|
||||
|
||||
6. **Start server**
|
||||
```bash
|
||||
npm start
|
||||
# or for development
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `DATABASE_URL` | MySQL connection string for CMS database |
|
||||
| `DATABASE_URL_UTILITY` | MySQL connection string for utility database |
|
||||
| `X_API_KEY` | Master API key for authentication |
|
||||
| `AI_API_KEY` | OpenAI API key for AI features |
|
||||
| `MINIO_ENDPOINT` | MinIO server endpoint |
|
||||
| `MINIO_ACCESS_KEY` | MinIO access key |
|
||||
| `MINIO_SECRET_KEY` | MinIO secret key |
|
||||
| `JWT_SECRET_KEY` | JWT signing secret |
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
backend-csa/
|
||||
├── app/
|
||||
│ ├── config/
|
||||
│ │ └── serviceAccountKey.json # Firebase config
|
||||
│ ├── controllers/
|
||||
│ │ ├── app.controller.js # API management
|
||||
│ │ ├── campaign.controller.js # Campaign with analytics
|
||||
│ │ ├── cms.controller.js # Content management
|
||||
│ │ ├── users.controller.js # User management
|
||||
│ │ └── ...
|
||||
│ ├── middleware/
|
||||
│ │ └── middleware.js # Auth & validation
|
||||
│ ├── routes/
|
||||
│ │ ├── app.route.js
|
||||
│ │ ├── campaign.route.js
|
||||
│ │ ├── cms.route.js
|
||||
│ │ ├── users.route.js
|
||||
│ │ └── ...
|
||||
│ ├── services/
|
||||
│ │ ├── firebase.services.js # FCM with tracking
|
||||
│ │ ├── minio.services.js # File storage
|
||||
│ │ ├── cronjob.services.js # Campaign scheduler
|
||||
│ │ ├── logger.services.js # Winston logger
|
||||
│ │ └── ...
|
||||
│ ├── res/
|
||||
│ │ └── responses.js # Response helpers
|
||||
│ └── static/
|
||||
│ └── prefix.js # Constants
|
||||
├── prisma/
|
||||
│ ├── schemas/
|
||||
│ │ ├── schema.cms.prisma # CMS database schema
|
||||
│ │ └── schema.utility.prisma # Utility database schema
|
||||
│ ├── clients/ # Generated Prisma clients
|
||||
│ └── migrations/ # Database migrations
|
||||
├── backups/ # Database backups
|
||||
├── index.js # Entry point
|
||||
├── package.json
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
- All endpoints protected with API key authentication
|
||||
- JWT token validation for user-specific operations
|
||||
- Input validation and sanitization
|
||||
- SQL injection prevention via Prisma ORM
|
||||
- File upload restrictions and validation
|
||||
|
||||
---
|
||||
|
||||
## 📝 API Response Format
|
||||
|
||||
### Success Response
|
||||
```javascript
|
||||
{
|
||||
success: true,
|
||||
message: "Operation successful",
|
||||
data: { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
```javascript
|
||||
{
|
||||
success: false,
|
||||
message: "Error message",
|
||||
error: "Error details"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Usage Examples
|
||||
|
||||
### Create Campaign
|
||||
```bash
|
||||
POST /campaign-management/setup
|
||||
Headers: { "x-api-key": "your-api-key" }
|
||||
Body: {
|
||||
"title": "New Campaign",
|
||||
"content": "Campaign message",
|
||||
"date": "2025-11-30T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Analytics
|
||||
```bash
|
||||
GET /campaign-management/analytics
|
||||
Headers: { "x-api-key": "your-api-key" }
|
||||
```
|
||||
|
||||
### Get Campaign Report
|
||||
```bash
|
||||
GET /campaign-management/report/campaign-uuid
|
||||
Headers: { "x-api-key": "your-api-key" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Campaign Flow
|
||||
|
||||
1. **Create** - Setup campaign with schedule
|
||||
2. **Schedule** - Cron job monitors scheduled campaigns
|
||||
3. **Execute** - Send notifications to all users
|
||||
4. **Track** - Record delivery status per user
|
||||
5. **Analyze** - View metrics and performance
|
||||
|
||||
---
|
||||
|
||||
## 📈 Analytics Features
|
||||
|
||||
### Campaign Metrics
|
||||
- Total campaigns by status
|
||||
- Success/failure rates
|
||||
- Average response time
|
||||
- Upcoming campaigns count
|
||||
|
||||
### Delivery Metrics
|
||||
- Target users vs actual sent
|
||||
- Delivery success rate
|
||||
- Failed delivery analysis
|
||||
- Per-user tracking
|
||||
|
||||
### Visualization Data
|
||||
- Status distribution (pie chart)
|
||||
- Campaign timeline (line chart)
|
||||
- Delivery rate trend (line chart)
|
||||
- Status over time (stacked bar)
|
||||
- Top performers (bar chart)
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Development
|
||||
|
||||
### Run Migrations
|
||||
```bash
|
||||
# Create migration
|
||||
npx prisma migrate dev --schema=./prisma/schemas/schema.cms.prisma --name migration_name
|
||||
|
||||
# Apply migration
|
||||
npx prisma migrate deploy --schema=./prisma/schemas/schema.cms.prisma
|
||||
|
||||
# Generate client
|
||||
npx prisma generate --schema=./prisma/schemas/schema.cms.prisma
|
||||
```
|
||||
|
||||
### View Database
|
||||
```bash
|
||||
npx prisma studio --schema=./prisma/schemas/schema.cms.prisma
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For issues or questions, please contact the development team.
|
||||
|
||||
---
|
||||
|
||||
**Version:** 2.0.0
|
||||
**Last Updated:** November 25, 2025
|
||||
**License:** Private
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "service_account",
|
||||
"project_id": "cifosuperapps",
|
||||
"private_key_id": "c428d46aa2124283ec0278068b9810455b85eae7",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCWbBlESWycu6EW\naj6ah8Zzl7D31f/rKqe+YrptnOQdtnEwjbZRJGe8HWDfY1sbU3YetJq9xBXMDuqw\nJ5HFMJL7sV5NZVJG3OJdmvAew6OPGg2HUuF+7Jy15N+IkVyikHMhtZK7XZhzA5Xk\nKYYPMFlCLqvsCHpRprzyhusyvjqqCGiho/2u2ouozLwP2V/ixOWteL3lhoggYPUK\n2p9soStJ2e4/Pg8lCdNiHhObZ5sbPq5oRVGtwDWbBEemMqK3LmkCUid0STlmrSgM\nuNDnVWWVuoNGnbu1SWnsJH7YALcTDvKYJyKsqmdO3LUYhfwOblD/Nk6vyhWpcCLN\nt3aiKqcXAgMBAAECgf9ck4BOv4xRzWEd+Lc5gFaizZyOeZgkQBxSLHECIYzuEcx7\narwfpg9OYeLLJ3dcr1Z05LJXpo+KzhUwjdR+a2a1SAJQEZnm5nxqrdQ24fg5b5Zy\nJFdOcPvIvb4qh73QwN03NzNQHS3u8H7d7VsGNvRGcchBCkwE2CfjVHszalIF4N+2\ngOzJVpBAEYqFZIm3Uttoar2nKcniBxMcS3TRWJlmC8z9OuqntpM2quDjqOyECagK\nvPcjc0yvgFrL6bBjqjKwL5x8g3rHUrhP7LgvzPzpA4rcP90r7luvSIPn5urBVl+V\nC/DmYp3tWhBaEZKXYPOdVQsnrzYtvRLzr34uDtECgYEAz2QwpZIIBzo/zCDG33pb\nSAC6AGzS85tR1exZ0PDSXOd5pUaaourIoqWd3TqSnmxWzs0/z+RPbDucs51+Pr4p\nBq6Auzaz4Ol6UrriyPa9ZN+HG1G5GSdB/jzi1owOiTCMzsDjWB8rqyf2efTGOstS\nVe7mt4NM2w8GO01lcaqZGZUCgYEAua2sFE0ju0LqyzrTCZ5v5/NAH5HFdTZIa59Y\n48jbTsyQ4uk+HVbW13118/Bi1K4QwYHT2QF0C/M51lkmbCXCtvd7XLSIw27lS58k\nJs+c9F0DXsXuSk+JFl+uYFELj8Ane4gMmYih0mRVGDxym7i3n4r5q1MGM5qs1Wiz\nJa6jyvsCgYBJJbJOw4nXWsEjsy/RpKtLYw43lip1R4P+qsUm/7mCkRYDqDpkWeD4\nFOfwHneWLuoTOKtYHNOyffgQFe1wHcwEkknPPkUFf/Pn+AiDDxvP11Mk3JcnewQ4\nsqrjNzTtSFVxmvDUpPHXBVpWu3GrTQk2S/POKB5UaSEui2bLR3uGXQKBgHttfF8+\nbAs2T3frUZAqVv9cTvrtXtIs1HVeRqucwFQgXgvIunasXEKA7uDKohf44cCQee/e\nLHMpKwBW4NixPT99Qe79P5CSbjAXyONXzWEPV4cvkdiqCFnsHrF+Dm5vcI+If7+r\n+M4vzfU/cXwcjUusCY2eshepsUxnvfbFobHzAoGAYcQ7cxspLMGpSy5iHVN2Ad1Z\njJsk1lWABuYKbHwnJQoaqQ7pd/WBPltbvnIsDW3Matblsu7unAxHlsWWrJBxfvW9\nqqET2b73xommIDuxNb4X/92hBsDxfqWN9+tQonK1I1Ak3LIxBPmYGx7d5WuTvzdB\n+yZL1ASAMc/xmlu9Z2I=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-fbsvc@cifosuperapps.iam.gserviceaccount.com",
|
||||
"client_id": "106921205757352133521",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40cifosuperapps.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient: CMSClient, ActivityType } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// RESPONSES
|
||||
const { badRequestResponse } = require("../res/responses.js");
|
||||
const { successResponse } = require("../res/responses.js");
|
||||
|
||||
// SERVICES
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
const logger = require("../services/logger.services.js");
|
||||
|
||||
const {
|
||||
manualTriggerNotification,
|
||||
processActivitiesAndSendNotifications,
|
||||
analyzeUserActivitiesForNotification,
|
||||
updateDailyAnalytics,
|
||||
getAINotificationAnalytics
|
||||
} = require("../services/notification.services.js");
|
||||
|
||||
|
||||
// CONTROLLER
|
||||
exports.create = async (req, res) => {
|
||||
try {
|
||||
const { userID, activityType, params } = req.body;
|
||||
|
||||
if (!userID || !activityType || !params) {
|
||||
throw new Error("Invalid user ID, activity type, or params!");
|
||||
}
|
||||
|
||||
if (!Object.values(ActivityType).includes(activityType)) {
|
||||
throw new Error(`Invalid activity type value, allowed values: ${Object.values(ActivityType).join(", ")}`);
|
||||
}
|
||||
|
||||
const userToken = await prisma.usersToken.findUnique({
|
||||
where: { UserID_UT: userID }
|
||||
});
|
||||
|
||||
if (!userToken) {
|
||||
throw new Error(`User token not found for userID: ${userID}. Please register user token first.`);
|
||||
}
|
||||
|
||||
let parsedParams = params;
|
||||
if (typeof params === 'string' && params.startsWith('{')) {
|
||||
try {
|
||||
parsedParams = params;
|
||||
} catch (e) {
|
||||
parsedParams = params;
|
||||
}
|
||||
}
|
||||
|
||||
const activity = await prisma.usersActivity.create({
|
||||
data: {
|
||||
UUID_UT: userToken.UUID_UT,
|
||||
ActivityType_UA: activityType,
|
||||
Params_UA: parsedParams,
|
||||
NotifyAt_UA: localTime(new Date(Date.now() + 1000 * 60 * 5)),
|
||||
CreatedAt_UA: localTime(new Date()),
|
||||
UpdatedAt_UA: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "User activity created successfully!", {
|
||||
activityID: activity.UUID_UA,
|
||||
userID: userID,
|
||||
activityType: activity.ActivityType_UA,
|
||||
createdAt: activity.CreatedAt_UA,
|
||||
willTriggerAIAnalysis: true
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error creating user activity", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.triggerNotificationForUser = async (req, res) => {
|
||||
try {
|
||||
const { userID, timeRangeMinutes = 60 } = req.body;
|
||||
|
||||
if (!userID) {
|
||||
throw new Error("userID is required");
|
||||
}
|
||||
|
||||
const result = await manualTriggerNotification(userID, timeRangeMinutes);
|
||||
|
||||
if (result.success) {
|
||||
return successResponse(res, "Notification sent successfully!", result);
|
||||
} else {
|
||||
return badRequestResponse(res, `Failed to send notification: ${result.reason || result.error}`, result);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Error triggering notification", { error: err.message, stack: err.stack });
|
||||
return badRequestResponse(res, "Error triggering notification", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.analyzeUserActivities = async (req, res) => {
|
||||
try {
|
||||
const { userID, timeRangeMinutes = 60 } = req.body;
|
||||
|
||||
if (!userID) {
|
||||
throw new Error("userID is required");
|
||||
}
|
||||
|
||||
const analysis = await analyzeUserActivitiesForNotification(userID, timeRangeMinutes);
|
||||
|
||||
return successResponse(res, "Activity analysis completed", analysis);
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error analyzing activities", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.processAllActivitiesAndNotifications = async (req, res) => {
|
||||
try {
|
||||
const { timeRangeMinutes = 30, minActivityCount = 1 } = req.body;
|
||||
|
||||
const result = await processActivitiesAndSendNotifications({
|
||||
timeRangeMinutes,
|
||||
minActivityCount
|
||||
});
|
||||
|
||||
if (result.notificationsSent > 0) {
|
||||
try {
|
||||
await updateDailyAnalytics();
|
||||
result.analyticsUpdated = true;
|
||||
} catch (analyticsErr) {
|
||||
result.analyticsUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
return successResponse(res, "Activity processing completed", {
|
||||
...result,
|
||||
message: `Processed ${result.totalUsersProcessed} users, sent ${result.notificationsSent} AI notifications`
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error processing activities", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.getAIInsights = async (req, res) => {
|
||||
try {
|
||||
const { userID, timeRange = 30 } = req.query;
|
||||
|
||||
// Get AI notification analytics
|
||||
const analytics = await getAINotificationAnalytics({
|
||||
startDate: new Date(Date.now() - timeRange * 24 * 60 * 60 * 1000),
|
||||
endDate: new Date(),
|
||||
limit: 50
|
||||
});
|
||||
|
||||
let userSpecificData = null;
|
||||
if (userID) {
|
||||
// Get user-specific AI notifications
|
||||
userSpecificData = await prisma.aINotification.findMany({
|
||||
where: {
|
||||
UserID_AIN: userID,
|
||||
CreatedAt_AIN: {
|
||||
gte: new Date(Date.now() - timeRange * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_AIN: 'desc'
|
||||
},
|
||||
take: 10
|
||||
});
|
||||
|
||||
// Get user's recent activities
|
||||
const userToken = await prisma.usersToken.findFirst({
|
||||
where: { UserID_UT: userID }
|
||||
});
|
||||
|
||||
if (userToken) {
|
||||
userSpecificData = {
|
||||
notifications: userSpecificData,
|
||||
recentActivities: await prisma.usersActivity.findMany({
|
||||
where: {
|
||||
UUID_UT: userToken.UUID_UT,
|
||||
CreatedAt_UA: {
|
||||
gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_UA: 'desc'
|
||||
},
|
||||
take: 20
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return successResponse(res, "AI insights retrieved successfully!", {
|
||||
analytics: analytics.summary,
|
||||
userSpecificData,
|
||||
integrationStatus: {
|
||||
aiNotificationsEnabled: true,
|
||||
analyticsTracking: true,
|
||||
activityMonitoring: true
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error retrieving AI insights", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
// ENVIRONMENT
|
||||
require("dotenv").config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient : CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const argon2 = require("argon2");
|
||||
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
|
||||
// CONSTANTS
|
||||
const { successResponse } = require("../res/responses.js");
|
||||
const { errorResponse } = require("../res/responses.js");
|
||||
const { notFoundResponse } = require("../res/responses.js");
|
||||
const { badRequestResponse } = require("../res/responses.js");
|
||||
|
||||
// ENV
|
||||
const { JWT_SECRET_KEY } = process.env;
|
||||
|
||||
// CONTROLLERS
|
||||
exports.adminLogin = async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return badRequestResponse(
|
||||
res,
|
||||
"Email and password are required",
|
||||
"Missing credentials"
|
||||
);
|
||||
}
|
||||
|
||||
const admin = await prisma.adminAccount.findFirst({
|
||||
where: {
|
||||
Email_AA: email,
|
||||
},
|
||||
});
|
||||
|
||||
if (!admin) {
|
||||
return notFoundResponse(res, "Admin not found!");
|
||||
}
|
||||
|
||||
if (await argon2.verify(admin.Password_AA, req.body.password)) {
|
||||
const accessToken = jwt.sign({ userID: admin.UUID_AA }, JWT_SECRET_KEY, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
|
||||
await prisma.adminAccount.update({
|
||||
where: {
|
||||
UUID_AA: admin.UUID_AA,
|
||||
},
|
||||
data: {
|
||||
UpdatedAt_AA: localTime(new Date()),
|
||||
LastLogin_AA: localTime(new Date()),
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse(res, "Authenticated!", {
|
||||
admin: {
|
||||
id: admin.UUID_AA,
|
||||
name: admin.Username_AA,
|
||||
email: admin.Email_AA,
|
||||
},
|
||||
token: accessToken,
|
||||
});
|
||||
} else {
|
||||
return errorResponse(res, "Invalid email or password!");
|
||||
}
|
||||
} catch (err) {
|
||||
return notFoundResponse(res, "Admin account not found", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.adminRegister = async (req, res) => {
|
||||
try {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
if (!username || !email || !password) {
|
||||
return badRequestResponse(
|
||||
res,
|
||||
"Username, email, and password are required",
|
||||
"Missing fields"
|
||||
);
|
||||
}
|
||||
|
||||
const hashedPassword = await argon2.hash(password);
|
||||
|
||||
const newAdmin = await prisma.adminAccount.create({
|
||||
data: {
|
||||
UUID_AA: uuidv4(),
|
||||
Username_AA: username,
|
||||
Email_AA: email,
|
||||
Password_AA: hashedPassword,
|
||||
CreatedAt_AA: localTime(new Date()),
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse(res, "Admin registered successfully!", {
|
||||
admin: {
|
||||
id: newAdmin.UUID_AA,
|
||||
name: newAdmin.Username_AA,
|
||||
email: newAdmin.Email_AA,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return errorResponse(res, "Error registering admin", err);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// SERVICES
|
||||
const {
|
||||
getAINotificationAnalytics,
|
||||
updateDailyAnalytics
|
||||
} = require("../services/notification.services.js");
|
||||
const {
|
||||
getScheduledNotificationsStats,
|
||||
cancelScheduledNotification
|
||||
} = require("../services/notification-scheduler.services.js");
|
||||
const {
|
||||
manualTriggerScheduledProcessor
|
||||
} = require("../services/scheduled-notification-processor.services.js");
|
||||
const logger = require("../services/logger.services");
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse, successResponse, notFoundResponse } = require("../res/responses.js");
|
||||
|
||||
// CONTROLLER
|
||||
exports.getAnalytics = async (req, res) => {
|
||||
try {
|
||||
const { startDate, endDate, limit } = req.query;
|
||||
|
||||
const options = {};
|
||||
if (startDate) options.startDate = new Date(startDate);
|
||||
if (endDate) options.endDate = new Date(endDate);
|
||||
if (limit) options.limit = parseInt(limit);
|
||||
|
||||
const analytics = await getAINotificationAnalytics(options);
|
||||
|
||||
const integrationInfo = {
|
||||
source: "AI notifications are triggered by user activities processed in activity-management",
|
||||
activityTriggerEndpoint: "/activity-management/trigger-notification",
|
||||
activityAnalysisEndpoint: "/activity-management/analyze",
|
||||
activityProcessingEndpoint: "/activity-management/process-all",
|
||||
lastUpdated: new Date().toISOString()
|
||||
};
|
||||
|
||||
return successResponse(res, "AI Notification analytics retrieved successfully!", {
|
||||
...analytics,
|
||||
integration: integrationInfo
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error getting AI notification analytics: ${err}`);
|
||||
return badRequestResponse(res, "Error retrieving AI notification analytics", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateAnalytics = async (req, res) => {
|
||||
try {
|
||||
const { date } = req.body;
|
||||
|
||||
const targetDate = date ? new Date(date) : new Date();
|
||||
const result = await updateDailyAnalytics(targetDate);
|
||||
|
||||
return successResponse(res, "AI Notification analytics updated successfully!", result);
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error updating AI notification analytics: ${err}`);
|
||||
return badRequestResponse(res, "Error updating AI notification analytics", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAllNotifications = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
status,
|
||||
userID,
|
||||
startDate,
|
||||
endDate
|
||||
} = req.query;
|
||||
|
||||
const skip = (parseInt(page) - 1) * parseInt(limit);
|
||||
const where = {};
|
||||
|
||||
if (status) {
|
||||
const statuses = Array.isArray(status)
|
||||
? status
|
||||
: String(status).split(",").map(s => s.trim()).filter(Boolean);
|
||||
|
||||
where.SentStatus_AIN = statuses.length > 1 ? { in: statuses } : statuses[0];
|
||||
}
|
||||
|
||||
if (userID) {
|
||||
where.UserID_AIN = userID;
|
||||
}
|
||||
|
||||
if (startDate || endDate) {
|
||||
where.CreatedAt_AIN = {};
|
||||
if (startDate) where.CreatedAt_AIN.gte = new Date(startDate);
|
||||
if (endDate) where.CreatedAt_AIN.lte = new Date(endDate);
|
||||
}
|
||||
|
||||
const [notifications, total] = await Promise.all([
|
||||
prisma.aINotification.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
CreatedAt_AIN: 'desc'
|
||||
},
|
||||
skip,
|
||||
take: parseInt(limit)
|
||||
}),
|
||||
prisma.aINotification.count({ where })
|
||||
]);
|
||||
|
||||
// Add activity correlation info for recent notifications
|
||||
const notificationsWithActivityInfo = notifications.map(notification => {
|
||||
let activityTypes = [];
|
||||
try {
|
||||
activityTypes = JSON.parse(notification.ActivityTypes_AIN || '[]');
|
||||
} catch (e) {
|
||||
activityTypes = [];
|
||||
}
|
||||
|
||||
return {
|
||||
...notification,
|
||||
activityTypes,
|
||||
activityContext: `Triggered by ${notification.AnalyzedActivities_AIN} activities over ${notification.ActivityTimeRange_AIN} minutes`,
|
||||
aiModel: notification.AIModel_AIN
|
||||
};
|
||||
});
|
||||
|
||||
return successResponse(res, "AI Notifications retrieved successfully!", {
|
||||
notifications: notificationsWithActivityInfo,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total,
|
||||
pages: Math.ceil(total / parseInt(limit))
|
||||
},
|
||||
activityIntegration: {
|
||||
description: "Each notification is generated from user activity analysis",
|
||||
activityManagementEndpoint: "/activity-management/create",
|
||||
aiAnalysisEndpoint: "/activity-management/analyze"
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error getting AI notifications: ${err}`);
|
||||
return badRequestResponse(res, "Error retrieving AI notifications", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getNotificationDetail = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const notification = await prisma.aINotification.findFirst({
|
||||
where: { UUID_AIN: id }
|
||||
});
|
||||
|
||||
if (!notification) {
|
||||
return notFoundResponse(res, "AI Notification not found", null);
|
||||
}
|
||||
|
||||
let activityTypes = [];
|
||||
try {
|
||||
activityTypes = JSON.parse(notification.ActivityTypes_AIN || '[]');
|
||||
} catch (e) {
|
||||
activityTypes = [];
|
||||
}
|
||||
|
||||
return successResponse(res, "AI Notification detail retrieved successfully!", {
|
||||
...notification,
|
||||
ActivityTypes_AIN: activityTypes
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error getting AI notification detail: ${err}`);
|
||||
return badRequestResponse(res, "Error retrieving AI notification detail", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getDashboardStats = async (req, res) => {
|
||||
try {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const last7Days = new Date(today);
|
||||
last7Days.setDate(last7Days.getDate() - 7);
|
||||
const last30Days = new Date(today);
|
||||
last30Days.setDate(last30Days.getDate() - 30);
|
||||
|
||||
const [
|
||||
todayStats,
|
||||
yesterdayStats,
|
||||
weeklyStats,
|
||||
monthlyStats,
|
||||
recentFailures,
|
||||
topActivityTypes
|
||||
] = await Promise.all([
|
||||
prisma.aINotification.groupBy({
|
||||
by: ['SentStatus_AIN'],
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: today
|
||||
}
|
||||
},
|
||||
_count: { SentStatus_AIN: true }
|
||||
}),
|
||||
|
||||
prisma.aINotification.groupBy({
|
||||
by: ['SentStatus_AIN'],
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: yesterday,
|
||||
lt: today
|
||||
}
|
||||
},
|
||||
_count: { SentStatus_AIN: true }
|
||||
}),
|
||||
|
||||
prisma.aINotification.groupBy({
|
||||
by: ['SentStatus_AIN'],
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: last7Days
|
||||
}
|
||||
},
|
||||
_count: { SentStatus_AIN: true }
|
||||
}),
|
||||
|
||||
prisma.aINotification.groupBy({
|
||||
by: ['SentStatus_AIN'],
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: last30Days
|
||||
}
|
||||
},
|
||||
_count: { SentStatus_AIN: true }
|
||||
}),
|
||||
prisma.aINotification.findMany({
|
||||
where: {
|
||||
SentStatus_AIN: 'failed',
|
||||
CreatedAt_AIN: {
|
||||
gte: last7Days
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_AIN: 'desc'
|
||||
},
|
||||
take: 10,
|
||||
select: {
|
||||
UUID_AIN: true,
|
||||
UserID_AIN: true,
|
||||
ErrorMessage_AIN: true,
|
||||
CreatedAt_AIN: true
|
||||
}
|
||||
}),
|
||||
prisma.aINotification.findMany({
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: last7Days
|
||||
},
|
||||
ActivityTypes_AIN: {
|
||||
not: null
|
||||
}
|
||||
},
|
||||
select: {
|
||||
ActivityTypes_AIN: true
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
const processStats = (stats) => {
|
||||
const result = { total: 0, sent: 0, failed: 0, delivered: 0 };
|
||||
stats.forEach(stat => {
|
||||
const count = stat._count.SentStatus_AIN;
|
||||
result.total += count;
|
||||
if (stat.SentStatus_AIN === 'sent') result.sent += count;
|
||||
if (stat.SentStatus_AIN === 'failed') result.failed += count;
|
||||
if (stat.SentStatus_AIN === 'delivered') result.delivered += count;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const activityTypeCount = {};
|
||||
topActivityTypes.forEach(record => {
|
||||
try {
|
||||
const types = JSON.parse(record.ActivityTypes_AIN || '[]');
|
||||
types.forEach(type => {
|
||||
activityTypeCount[type] = (activityTypeCount[type] || 0) + 1;
|
||||
});
|
||||
} catch (e) {
|
||||
}
|
||||
});
|
||||
|
||||
const topActivityTypesResult = Object.entries(activityTypeCount)
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([type, count]) => ({ type, count }));
|
||||
|
||||
return successResponse(res, "Dashboard stats retrieved successfully!", {
|
||||
today: processStats(todayStats),
|
||||
yesterday: processStats(yesterdayStats),
|
||||
last7Days: processStats(weeklyStats),
|
||||
last30Days: processStats(monthlyStats),
|
||||
recentFailures,
|
||||
topActivityTypes: topActivityTypesResult
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error getting dashboard stats: ${err}`);
|
||||
return badRequestResponse(res, "Error retrieving dashboard stats", err);
|
||||
}
|
||||
};
|
||||
|
||||
// NEW: Get scheduled notifications statistics
|
||||
exports.getScheduledStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await getScheduledNotificationsStats();
|
||||
|
||||
return successResponse(res, "Scheduled notifications stats retrieved successfully!", {
|
||||
...stats,
|
||||
info: {
|
||||
description: "AI-powered predictive timing schedules notifications at optimal times",
|
||||
processingInterval: "Every 5 minutes",
|
||||
features: [
|
||||
"Analyzes user activity patterns",
|
||||
"Predicts optimal delivery time",
|
||||
"Confidence scoring",
|
||||
"Engagement pattern detection"
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error getting scheduled stats: ${err}`);
|
||||
return badRequestResponse(res, "Error retrieving scheduled stats", err);
|
||||
}
|
||||
};
|
||||
|
||||
// NEW: Manually trigger scheduled notification processor
|
||||
exports.triggerScheduledProcessor = async (req, res) => {
|
||||
try {
|
||||
logger.info("Manual trigger for scheduled notification processor");
|
||||
|
||||
const results = await manualTriggerScheduledProcessor();
|
||||
|
||||
return successResponse(res, "Scheduled notifications processed!", {
|
||||
...results,
|
||||
message: `Processed ${results.processed} notifications, sent ${results.sent}, failed ${results.failed}`
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error triggering scheduled processor: ${err}`);
|
||||
return badRequestResponse(res, "Error processing scheduled notifications", err);
|
||||
}
|
||||
};
|
||||
|
||||
// NEW: Cancel a scheduled notification
|
||||
exports.cancelScheduled = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
throw new Error("Notification ID is required");
|
||||
}
|
||||
|
||||
const result = await cancelScheduledNotification(id);
|
||||
|
||||
return successResponse(res, "Scheduled notification cancelled successfully!", result);
|
||||
|
||||
} catch (err) {
|
||||
logger.error(`Error cancelling scheduled notification: ${err}`);
|
||||
return badRequestResponse(res, "Error cancelling scheduled notification", err);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient : CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse }= require("../res/responses.js");
|
||||
const { successResponse } = require("../res/responses.js");
|
||||
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
|
||||
|
||||
// CONTROLLER
|
||||
exports.test = async (req, res) => {
|
||||
try {
|
||||
return successResponse(res, "API Connected!", null);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error connecting to API", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.testSecure = async (req, res) => {
|
||||
try {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
|
||||
if (!apiKey) {
|
||||
return badRequestResponse(res, "API key is required", "Missing api-key header");
|
||||
}
|
||||
|
||||
const validCredential = await prisma.appCredential.findUniqueOrThrow({
|
||||
where: {
|
||||
TokenCredential_AC: apiKey
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "Secure API Connected!", {
|
||||
message: "Authentication successful",
|
||||
credentialId: validCredential.UUID_AC
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Invalid API key", "Unauthorized access");
|
||||
}
|
||||
}
|
||||
|
||||
exports.testToken = async (req, res) => {
|
||||
try {
|
||||
const user = req.locals.user;
|
||||
|
||||
return successResponse(res, "Token API Connected!", {
|
||||
userID: user
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error validating token", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.createToken = async (req, res) => {
|
||||
try {
|
||||
const apiKey = req.headers['target-x-api-key'];
|
||||
|
||||
const token = apiKey || require('crypto').randomBytes(32).toString('hex');
|
||||
|
||||
const newCredential = await prisma.appCredential.create({
|
||||
data: {
|
||||
TokenCredential_AC: token,
|
||||
CreatedAt_AC: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "API key created successfully!", {
|
||||
apiKey: newCredential.TokenCredential_AC,
|
||||
credentialId: newCredential.UUID_AC,
|
||||
createdAt: newCredential.CreatedAt_AC
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "API key already exists", "Duplicate token");
|
||||
}
|
||||
}
|
||||
|
||||
exports.deleteToken = async (req, res) => {
|
||||
try {
|
||||
const apiKey = req.headers['target-x-api-key'];
|
||||
|
||||
if (!apiKey) {
|
||||
return badRequestResponse(res, "API key is required", "Missing x-api-key header");
|
||||
}
|
||||
|
||||
const deletedCredential = await prisma.appCredential.delete({
|
||||
where: {
|
||||
TokenCredential_AC: apiKey
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "API key deleted successfully!", {
|
||||
apiKey: deletedCredential.TokenCredential_AC,
|
||||
credentialId: deletedCredential.UUID_AC,
|
||||
deletedAt: new Date().toISOString()
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === 'P2025') {
|
||||
return badRequestResponse(res, "API key not found", "No matching token to delete");
|
||||
}
|
||||
return badRequestResponse(res, "Error deleting API key", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.getAllTokens = async (req, res) => {
|
||||
try {
|
||||
const tokens = await prisma.appCredential.findMany({
|
||||
orderBy: {
|
||||
CreatedAt_AC: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "API tokens retrieved successfully!", {
|
||||
total: tokens.length,
|
||||
tokens: tokens
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error retrieving API tokens", err);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,732 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// SERVICES
|
||||
const { sendNotification } = require("../services/firebase.services.js");
|
||||
const logger = require("../services/logger.services");
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
const fileServices = require("../services/file.services.js");
|
||||
const { default: prefixes } = require("../static/prefix.js");
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse, successResponse, notFoundResponse } = require("../res/responses.js");
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
const deleteCampaignImage = async (imageUrl) => {
|
||||
try {
|
||||
if (!imageUrl) return;
|
||||
const urlParts = imageUrl.split('/');
|
||||
const fileName = urlParts[urlParts.length - 1];
|
||||
|
||||
await fileServices.delete(prefixes.bucketName, "notifications", fileName);
|
||||
logger.info(`Deleted campaign image: ${fileName}`);
|
||||
} catch (err) {
|
||||
logger.error(`Error deleting campaign image: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
// CONTROLLER
|
||||
exports.sendNotification = async (req, res) => {
|
||||
try {
|
||||
const { userID, title, body, data = {} } = req.body;
|
||||
const imageFile = req.files?.find(file => file.fieldname === 'image');
|
||||
let imageUrl = null;
|
||||
if (imageFile) {
|
||||
const [uploadedUrl] = await fileServices.upload(
|
||||
prefixes.bucketName,
|
||||
"notifications",
|
||||
imageFile.mimetype,
|
||||
[imageFile]
|
||||
);
|
||||
imageUrl = uploadedUrl;
|
||||
}
|
||||
|
||||
const userToken = await prisma.usersToken.findFirst({
|
||||
where: {
|
||||
UserID_UT: userID
|
||||
}
|
||||
});
|
||||
|
||||
let parsedData = data;
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (e) {
|
||||
parsedData = {};
|
||||
}
|
||||
}
|
||||
|
||||
await sendNotification(userToken.Token_UT, title, body, parsedData, imageUrl);
|
||||
|
||||
return successResponse(res, "Notification sent successfully!", null);
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error sending notification", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.setupCampaign = async (req, res) => {
|
||||
try {
|
||||
const { title, content, date, data = null } = req.body;
|
||||
const imageFile = req.files?.find(file => file.fieldname === 'image');
|
||||
|
||||
let imageUrl = null;
|
||||
let fileName = null;
|
||||
if (imageFile) {
|
||||
const [uploadedUrl, uploadedFileName] = await fileServices.upload(
|
||||
prefixes.bucketName,
|
||||
"notifications",
|
||||
imageFile.mimetype,
|
||||
[imageFile]
|
||||
);
|
||||
imageUrl = uploadedUrl;
|
||||
fileName = uploadedFileName;
|
||||
}
|
||||
|
||||
let parsedData = data;
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (e) {
|
||||
parsedData = null;
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.appCampaign.create({
|
||||
data: {
|
||||
Title_ACP: title,
|
||||
Content_ACP: content,
|
||||
Date_ACP: date,
|
||||
ImageUrl_ACP: imageUrl,
|
||||
Data_ACP: parsedData ? JSON.stringify(parsedData) : null,
|
||||
CreatedAt_ACP: localTime(new Date()),
|
||||
UpdatedAt_ACP: localTime(new Date()),
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "Campaign setup successfully!", null);
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return badRequestResponse(res, "Error setting up campaign", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.getAllCampaigns = async (req, res) => {
|
||||
try {
|
||||
const { status } = req.query;
|
||||
|
||||
const where = {};
|
||||
if (status) {
|
||||
const statuses = Array.isArray(status)
|
||||
? status
|
||||
: String(status)
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
where.Status_ACP = statuses.length > 1 ? { in: statuses } : statuses[0];
|
||||
}
|
||||
|
||||
const campaigns = await prisma.appCampaign.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
Date_ACP: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return successResponse(res, "Campaigns retrieved successfully!", campaigns);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error retrieving campaigns", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.updateCampaign = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { title, content, date, status, data } = req.body;
|
||||
const imageFile = req.files?.find(file => file.fieldname === 'image');
|
||||
|
||||
const exists = await prisma.appCampaign.findFirst({
|
||||
where: { UUID_ACP: id }
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
return notFoundResponse(res, "Campaign tidak ditemukan", null);
|
||||
}
|
||||
|
||||
const dataToUpdate = {};
|
||||
if (title !== undefined) dataToUpdate.Title_ACP = title;
|
||||
if (content !== undefined) dataToUpdate.Content_ACP = content;
|
||||
if (date !== undefined) dataToUpdate.Date_ACP = date;
|
||||
if (status !== undefined) dataToUpdate.Status_ACP = status;
|
||||
|
||||
if (imageFile) {
|
||||
if (exists.ImageUrl_ACP) {
|
||||
await deleteCampaignImage(exists.ImageUrl_ACP);
|
||||
}
|
||||
const [uploadedUrl] = await fileServices.upload(
|
||||
prefixes.bucketName,
|
||||
"notifications",
|
||||
imageFile.mimetype,
|
||||
[imageFile]
|
||||
);
|
||||
dataToUpdate.ImageUrl_ACP = uploadedUrl;
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
let parsedData = data;
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (e) {
|
||||
parsedData = null;
|
||||
}
|
||||
}
|
||||
dataToUpdate.Data_ACP = parsedData ? JSON.stringify(parsedData) : null;
|
||||
}
|
||||
|
||||
dataToUpdate.UpdatedAt_ACP = localTime(new Date());
|
||||
|
||||
if (Object.keys(dataToUpdate).length === 1) {
|
||||
return badRequestResponse(res, "Tidak ada field yang diupdate", null);
|
||||
}
|
||||
|
||||
const updated = await prisma.appCampaign.update({
|
||||
where: { UUID_ACP: id },
|
||||
data: dataToUpdate
|
||||
});
|
||||
|
||||
logger.info(`Campaign ${id} diupdate.`);
|
||||
return successResponse(res, "Campaign berhasil diupdate!", updated);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Terjadi kesalahan saat update campaign", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.deleteCampaign = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const campaign = await prisma.appCampaign.findFirst({
|
||||
where: { UUID_ACP: id }
|
||||
});
|
||||
|
||||
if (!campaign) {
|
||||
return notFoundResponse(res, "Campaign not found", null);
|
||||
}
|
||||
|
||||
if (campaign.ImageUrl_ACP) {
|
||||
await deleteCampaignImage(campaign.ImageUrl_ACP);
|
||||
}
|
||||
|
||||
await prisma.appCampaign.update({
|
||||
where: { UUID_ACP: id },
|
||||
data: { Status_ACP: "cancelled", UpdatedAt_ACP: localTime(new Date()) }
|
||||
});
|
||||
return successResponse(res, "Campaign cancelled successfully!", null);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error cancelling campaign", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.sendCampaign = async (campaignID) => {
|
||||
try {
|
||||
const campaign = await prisma.appCampaign.findFirst({
|
||||
where: {
|
||||
UUID_ACP: campaignID,
|
||||
Status_ACP: "pending"
|
||||
}
|
||||
});
|
||||
|
||||
if (!campaign) {
|
||||
logger.info(`No pending campaign found for id ${campaignID}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { Title_ACP, Content_ACP, ImageUrl_ACP, Data_ACP } = campaign;
|
||||
|
||||
let customData = { campaignId: campaignID };
|
||||
if (Data_ACP) {
|
||||
try {
|
||||
const parsedData = JSON.parse(Data_ACP);
|
||||
customData = { ...customData, ...parsedData };
|
||||
} catch (e) {
|
||||
logger.error(`Failed to parse Data_ACP for campaign ${campaignID}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await require("../services/firebase.services").sendCampaignWithTracking(
|
||||
campaignID,
|
||||
Title_ACP,
|
||||
Content_ACP,
|
||||
customData,
|
||||
ImageUrl_ACP
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
await prisma.appCampaign.update({
|
||||
where: {
|
||||
UUID_ACP: campaignID
|
||||
},
|
||||
data: {
|
||||
Status_ACP: "completed",
|
||||
TargetUsers_ACP: result.targetUsers,
|
||||
SentCount_ACP: result.targetUsers,
|
||||
SuccessCount_ACP: result.successCount,
|
||||
FailureCount_ACP: result.failureCount,
|
||||
DeliveryRate_ACP: parseFloat(result.deliveryRate),
|
||||
SentAt_ACP: result.sentAt,
|
||||
CompletedAt_ACP: localTime(new Date()),
|
||||
UpdatedAt_ACP: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Campaign ${campaignID} sent successfully! Delivered: ${result.successCount}/${result.targetUsers}`);
|
||||
} else {
|
||||
throw new Error(result.message || "Failed to send campaign");
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
const failedCampaign = await prisma.appCampaign.findFirst({
|
||||
where: { UUID_ACP: campaignID }
|
||||
});
|
||||
|
||||
await prisma.appCampaign.update({
|
||||
where: {
|
||||
UUID_ACP: campaignID
|
||||
},
|
||||
data: {
|
||||
Status_ACP: "failed",
|
||||
ErrorMessage_ACP: err.message || err.toString()
|
||||
}
|
||||
});
|
||||
|
||||
if (failedCampaign?.ImageUrl_ACP) {
|
||||
await deleteCampaignImage(failedCampaign.ImageUrl_ACP);
|
||||
}
|
||||
logger.error(`Error sending campaign ${campaignID}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.checkCampaign = async (datetime) => {
|
||||
try {
|
||||
const target = new Date(datetime);
|
||||
const startOfMinute = new Date(target);
|
||||
startOfMinute.setSeconds(0, 0);
|
||||
const endOfMinute = new Date(startOfMinute);
|
||||
endOfMinute.setMinutes(endOfMinute.getMinutes() + 1);
|
||||
|
||||
const campaign = await prisma.appCampaign.findFirst({
|
||||
where: {
|
||||
Date_ACP: {
|
||||
gte: startOfMinute,
|
||||
lt: endOfMinute
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return campaign ? campaign.UUID_ACP : null;
|
||||
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
exports.getCampaignAnalytics = async (req, res) => {
|
||||
try {
|
||||
const now = new Date();
|
||||
const last30Days = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
const last7Days = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
const [
|
||||
totalCampaigns,
|
||||
statusDistribution,
|
||||
allCampaigns,
|
||||
recentCampaigns,
|
||||
upcomingCampaigns,
|
||||
totalDeliveries,
|
||||
deliveryStatusDistribution
|
||||
] = await Promise.all([
|
||||
prisma.appCampaign.count(),
|
||||
prisma.appCampaign.groupBy({
|
||||
by: ['Status_ACP'],
|
||||
_count: {
|
||||
Status_ACP: true
|
||||
}
|
||||
}),
|
||||
prisma.appCampaign.findMany({
|
||||
select: {
|
||||
UUID_ACP: true,
|
||||
Title_ACP: true,
|
||||
Status_ACP: true,
|
||||
Date_ACP: true,
|
||||
CreatedAt_ACP: true,
|
||||
UpdatedAt_ACP: true,
|
||||
TargetUsers_ACP: true,
|
||||
SentCount_ACP: true,
|
||||
SuccessCount_ACP: true,
|
||||
FailureCount_ACP: true,
|
||||
DeliveryRate_ACP: true
|
||||
},
|
||||
orderBy: {
|
||||
Date_ACP: 'desc'
|
||||
}
|
||||
}),
|
||||
|
||||
prisma.appCampaign.findMany({
|
||||
where: {
|
||||
Date_ACP: {
|
||||
gte: last30Days,
|
||||
lte: now
|
||||
}
|
||||
},
|
||||
select: {
|
||||
Date_ACP: true,
|
||||
Status_ACP: true,
|
||||
Title_ACP: true,
|
||||
SuccessCount_ACP: true,
|
||||
FailureCount_ACP: true,
|
||||
TargetUsers_ACP: true
|
||||
},
|
||||
orderBy: {
|
||||
Date_ACP: 'asc'
|
||||
}
|
||||
}),
|
||||
|
||||
prisma.appCampaign.findMany({
|
||||
where: {
|
||||
Date_ACP: {
|
||||
gte: now
|
||||
},
|
||||
Status_ACP: 'pending'
|
||||
},
|
||||
select: {
|
||||
UUID_ACP: true,
|
||||
Title_ACP: true,
|
||||
Date_ACP: true
|
||||
},
|
||||
orderBy: {
|
||||
Date_ACP: 'asc'
|
||||
},
|
||||
take: 10
|
||||
}),
|
||||
|
||||
prisma.campaignDelivery.count(),
|
||||
|
||||
prisma.campaignDelivery.groupBy({
|
||||
by: ['Status_CD'],
|
||||
_count: {
|
||||
Status_CD: true
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
const statusStats = statusDistribution.reduce((acc, item) => {
|
||||
acc[item.Status_ACP] = item._count.Status_ACP;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
const completedCount = statusStats.completed || 0;
|
||||
const failedCount = statusStats.failed || 0;
|
||||
const totalExecuted = completedCount + failedCount;
|
||||
const successRate = totalExecuted > 0
|
||||
? ((completedCount / totalExecuted) * 100).toFixed(2)
|
||||
: 0;
|
||||
|
||||
const campaignTimeline = {};
|
||||
recentCampaigns.forEach(campaign => {
|
||||
const date = new Date(campaign.Date_ACP).toISOString().split('T')[0];
|
||||
if (!campaignTimeline[date]) {
|
||||
campaignTimeline[date] = {
|
||||
total: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
cancelled: 0
|
||||
};
|
||||
}
|
||||
campaignTimeline[date].total++;
|
||||
campaignTimeline[date][campaign.Status_ACP]++;
|
||||
});
|
||||
|
||||
const creationTrend = {};
|
||||
allCampaigns.forEach(campaign => {
|
||||
const date = new Date(campaign.CreatedAt_ACP).toISOString().split('T')[0];
|
||||
if (!creationTrend[date]) {
|
||||
creationTrend[date] = 0;
|
||||
}
|
||||
creationTrend[date]++;
|
||||
});
|
||||
|
||||
const executedCampaigns = allCampaigns.filter(c =>
|
||||
c.Status_ACP === 'completed' || c.Status_ACP === 'failed'
|
||||
);
|
||||
|
||||
let avgResponseTime = 0;
|
||||
if (executedCampaigns.length > 0) {
|
||||
const totalTime = executedCampaigns.reduce((sum, campaign) => {
|
||||
const responseTime = new Date(campaign.UpdatedAt_ACP) - new Date(campaign.Date_ACP);
|
||||
return sum + responseTime;
|
||||
}, 0);
|
||||
avgResponseTime = Math.round(totalTime / executedCampaigns.length / 1000 / 60); // in minutes
|
||||
}
|
||||
|
||||
const statusOverTime = {};
|
||||
const last7DaysCampaigns = allCampaigns.filter(c =>
|
||||
new Date(c.Date_ACP) >= last7Days
|
||||
);
|
||||
|
||||
last7DaysCampaigns.forEach(campaign => {
|
||||
const date = new Date(campaign.Date_ACP).toISOString().split('T')[0];
|
||||
if (!statusOverTime[date]) {
|
||||
statusOverTime[date] = {
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
cancelled: 0
|
||||
};
|
||||
}
|
||||
statusOverTime[date][campaign.Status_ACP]++;
|
||||
});
|
||||
|
||||
const totalTargetUsers = allCampaigns.reduce((sum, c) => sum + (c.TargetUsers_ACP || 0), 0);
|
||||
const totalSent = allCampaigns.reduce((sum, c) => sum + (c.SentCount_ACP || 0), 0);
|
||||
const totalSuccess = allCampaigns.reduce((sum, c) => sum + (c.SuccessCount_ACP || 0), 0);
|
||||
const totalFailure = allCampaigns.reduce((sum, c) => sum + (c.FailureCount_ACP || 0), 0);
|
||||
const overallDeliveryRate = totalSent > 0 ? ((totalSuccess / totalSent) * 100).toFixed(2) : 0;
|
||||
|
||||
const deliveryStats = deliveryStatusDistribution.reduce((acc, item) => {
|
||||
acc[item.Status_CD] = item._count.Status_CD;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const deliveryRateTrend = {};
|
||||
recentCampaigns.forEach(campaign => {
|
||||
if (campaign.TargetUsers_ACP > 0) {
|
||||
const date = new Date(campaign.Date_ACP).toISOString().split('T')[0];
|
||||
const rate = campaign.SuccessCount_ACP && campaign.TargetUsers_ACP
|
||||
? ((campaign.SuccessCount_ACP / campaign.TargetUsers_ACP) * 100).toFixed(2)
|
||||
: 0;
|
||||
deliveryRateTrend[date] = rate;
|
||||
}
|
||||
});
|
||||
|
||||
const campaignPerformance = allCampaigns
|
||||
.filter(c => c.Status_ACP === 'completed' && c.TargetUsers_ACP > 0)
|
||||
.map(c => ({
|
||||
id: c.UUID_ACP,
|
||||
title: c.Title_ACP,
|
||||
targetUsers: c.TargetUsers_ACP,
|
||||
successCount: c.SuccessCount_ACP,
|
||||
failureCount: c.FailureCount_ACP,
|
||||
deliveryRate: c.DeliveryRate_ACP,
|
||||
date: c.Date_ACP
|
||||
}))
|
||||
.sort((a, b) => b.deliveryRate - a.deliveryRate)
|
||||
.slice(0, 10);
|
||||
|
||||
return successResponse(res, "Campaign analytics retrieved successfully!", {
|
||||
summary: {
|
||||
campaigns: {
|
||||
total: totalCampaigns,
|
||||
completed: statusStats.completed || 0,
|
||||
failed: statusStats.failed || 0,
|
||||
pending: statusStats.pending || 0,
|
||||
cancelled: statusStats.cancelled || 0,
|
||||
successRate: `${successRate}%`,
|
||||
avgResponseTime: `${avgResponseTime} minutes`,
|
||||
upcomingCount: upcomingCampaigns.length
|
||||
},
|
||||
delivery: {
|
||||
totalTargetUsers: totalTargetUsers,
|
||||
totalSent: totalSent,
|
||||
totalDelivered: totalSuccess,
|
||||
totalFailed: totalFailure,
|
||||
overallDeliveryRate: `${overallDeliveryRate}%`,
|
||||
totalDeliveryRecords: totalDeliveries
|
||||
}
|
||||
},
|
||||
|
||||
charts: {
|
||||
statusDistribution: {
|
||||
labels: Object.keys(statusStats),
|
||||
data: Object.values(statusStats)
|
||||
},
|
||||
|
||||
deliveryStatusDistribution: {
|
||||
labels: Object.keys(deliveryStats),
|
||||
data: Object.values(deliveryStats)
|
||||
},
|
||||
|
||||
campaignTimeline: campaignTimeline,
|
||||
|
||||
creationTrend: {
|
||||
labels: Object.keys(creationTrend).sort(),
|
||||
data: Object.keys(creationTrend).sort().map(date => creationTrend[date])
|
||||
},
|
||||
|
||||
statusOverTime: statusOverTime,
|
||||
|
||||
deliveryRateTrend: {
|
||||
labels: Object.keys(deliveryRateTrend).sort(),
|
||||
data: Object.keys(deliveryRateTrend).sort().map(date => parseFloat(deliveryRateTrend[date]))
|
||||
}
|
||||
},
|
||||
|
||||
topPerforming: campaignPerformance,
|
||||
|
||||
upcoming: upcomingCampaigns,
|
||||
|
||||
recentActivity: recentCampaigns.slice(0, 10)
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error retrieving campaign analytics", err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.getCampaignReport = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const [campaign, deliveryRecords, deliveryStatusBreakdown] = await Promise.all([
|
||||
prisma.appCampaign.findFirst({
|
||||
where: { UUID_ACP: id }
|
||||
}),
|
||||
prisma.campaignDelivery.findMany({
|
||||
where: { Campaign_CD: id },
|
||||
select: {
|
||||
UUID_CD: true,
|
||||
UserID_CD: true,
|
||||
Status_CD: true,
|
||||
SentAt_CD: true,
|
||||
DeliveredAt_CD: true,
|
||||
FailedAt_CD: true,
|
||||
ErrorMessage_CD: true,
|
||||
CreatedAt_CD: true
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_CD: 'desc'
|
||||
}
|
||||
}),
|
||||
prisma.campaignDelivery.groupBy({
|
||||
by: ['Status_CD'],
|
||||
where: { Campaign_CD: id },
|
||||
_count: {
|
||||
Status_CD: true
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
if (!campaign) {
|
||||
return notFoundResponse(res, "Campaign not found", null);
|
||||
}
|
||||
|
||||
const createdAt = new Date(campaign.CreatedAt_ACP);
|
||||
const scheduledAt = new Date(campaign.Date_ACP);
|
||||
const updatedAt = new Date(campaign.UpdatedAt_ACP);
|
||||
const now = new Date();
|
||||
|
||||
const leadTime = Math.round((scheduledAt - createdAt) / 1000 / 60 / 60); // hours
|
||||
const executionTime = campaign.Status_ACP !== 'pending'
|
||||
? Math.round((updatedAt - scheduledAt) / 1000 / 60) // minutes
|
||||
: null;
|
||||
|
||||
const isScheduled = scheduledAt > now;
|
||||
const isOverdue = scheduledAt < now && campaign.Status_ACP === 'pending';
|
||||
const timeUntilExecution = isScheduled
|
||||
? Math.round((scheduledAt - now) / 1000 / 60 / 60) // hours
|
||||
: null;
|
||||
|
||||
const deliveryStats = deliveryStatusBreakdown.reduce((acc, item) => {
|
||||
acc[item.Status_CD] = item._count.Status_CD;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const failedDeliveries = deliveryRecords.filter(d => d.Status_CD === 'failed');
|
||||
const errorBreakdown = {};
|
||||
failedDeliveries.forEach(delivery => {
|
||||
const errorKey = delivery.ErrorMessage_CD || 'Unknown error';
|
||||
if (!errorBreakdown[errorKey]) {
|
||||
errorBreakdown[errorKey] = 0;
|
||||
}
|
||||
errorBreakdown[errorKey]++;
|
||||
});
|
||||
|
||||
const deliveredRecords = deliveryRecords.filter(d => d.DeliveredAt_CD);
|
||||
let avgDeliveryTime = 0;
|
||||
if (deliveredRecords.length > 0 && campaign.SentAt_ACP) {
|
||||
const sentTime = new Date(campaign.SentAt_ACP);
|
||||
const totalDeliveryTime = deliveredRecords.reduce((sum, record) => {
|
||||
const deliveryTime = new Date(record.DeliveredAt_CD) - sentTime;
|
||||
return sum + deliveryTime;
|
||||
}, 0);
|
||||
avgDeliveryTime = Math.round(totalDeliveryTime / deliveredRecords.length / 1000); // in seconds
|
||||
}
|
||||
|
||||
return successResponse(res, "Campaign report retrieved successfully!", {
|
||||
campaign: {
|
||||
id: campaign.UUID_ACP,
|
||||
title: campaign.Title_ACP,
|
||||
content: campaign.Content_ACP,
|
||||
status: campaign.Status_ACP,
|
||||
scheduledDate: campaign.Date_ACP,
|
||||
createdAt: campaign.CreatedAt_ACP,
|
||||
updatedAt: campaign.UpdatedAt_ACP,
|
||||
errorMessage: campaign.ErrorMessage_ACP
|
||||
},
|
||||
|
||||
metrics: {
|
||||
leadTime: `${leadTime} hours`,
|
||||
executionTime: executionTime ? `${executionTime} minutes` : 'Not executed yet',
|
||||
isScheduled: isScheduled,
|
||||
isOverdue: isOverdue,
|
||||
timeUntilExecution: timeUntilExecution ? `${timeUntilExecution} hours` : null,
|
||||
status: campaign.Status_ACP,
|
||||
avgDeliveryTime: avgDeliveryTime ? `${avgDeliveryTime} seconds` : 'N/A'
|
||||
},
|
||||
|
||||
delivery: {
|
||||
targetUsers: campaign.TargetUsers_ACP || 0,
|
||||
sentCount: campaign.SentCount_ACP || 0,
|
||||
successCount: campaign.SuccessCount_ACP || 0,
|
||||
failureCount: campaign.FailureCount_ACP || 0,
|
||||
deliveryRate: campaign.DeliveryRate_ACP ? `${campaign.DeliveryRate_ACP}%` : '0%',
|
||||
sentAt: campaign.SentAt_ACP,
|
||||
completedAt: campaign.CompletedAt_ACP,
|
||||
|
||||
statusBreakdown: {
|
||||
pending: deliveryStats.pending || 0,
|
||||
sent: deliveryStats.sent || 0,
|
||||
delivered: deliveryStats.delivered || 0,
|
||||
failed: deliveryStats.failed || 0
|
||||
},
|
||||
|
||||
errorBreakdown: errorBreakdown
|
||||
},
|
||||
|
||||
timeline: {
|
||||
created: createdAt.toISOString(),
|
||||
scheduled: scheduledAt.toISOString(),
|
||||
sent: campaign.SentAt_ACP ? new Date(campaign.SentAt_ACP).toISOString() : null,
|
||||
completed: campaign.CompletedAt_ACP ? new Date(campaign.CompletedAt_ACP).toISOString() : null,
|
||||
executed: campaign.Status_ACP !== 'pending' ? updatedAt.toISOString() : null
|
||||
},
|
||||
|
||||
deliveryRecords: {
|
||||
total: deliveryRecords.length,
|
||||
records: deliveryRecords.slice(0, 100)
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error retrieving campaign report", err);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// ENVIRONMENT
|
||||
require("dotenv").config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient : UtilityClient } = require("../../prisma/clients/utility");
|
||||
|
||||
const prisma = new UtilityClient();
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse, successResponse } = require("../res/responses.js");
|
||||
|
||||
// CONTROLLER
|
||||
exports.getAll = async (req, res) => {
|
||||
try {
|
||||
const cctvData = await prisma.cameras.findMany({});
|
||||
|
||||
return successResponse(res, "Data retrieved successfully!", cctvData);
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error retrieving cctv data", err);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient : CMSClient, ContentType, CorpType } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse, successResponse, notFoundResponse } = require("../res/responses.js");
|
||||
|
||||
// SERVICES
|
||||
const fileServices = require('../services/file.services.js');
|
||||
const { getAllBuckets } = require('../services/minio.services.js');
|
||||
const logger = require('../services/logger.services.js');
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
|
||||
const { default: prefixes } = require('../static/prefix.js');
|
||||
|
||||
|
||||
exports.createContent = async (req, res) => {
|
||||
try {
|
||||
const { title, description, type, corp } = req.body;
|
||||
|
||||
if (!Object.values(CorpType).includes(corp)) {
|
||||
return badRequestResponse(res, "Invalid corp value", `Allowed values: ${Object.values(CorpType).join(", ")}`);
|
||||
}
|
||||
|
||||
const imageFile = req.files;
|
||||
|
||||
if (!imageFile) {
|
||||
return badRequestResponse(res, "Image file is required", "Missing image file in request");
|
||||
}
|
||||
|
||||
if (!title || !description || !type || !corp) {
|
||||
return badRequestResponse(res, "Title, description, type, and corp are required", null);
|
||||
}
|
||||
|
||||
if (!Object.values(ContentType).includes(type)) {
|
||||
return badRequestResponse(res, "Invalid type value", `Allowed values: ${Object.values(ContentType).join(", ")}`);
|
||||
}
|
||||
|
||||
const files = await fileServices.upload(prefixes.bucketName, type, (imageFile[0]).mimetype, imageFile);
|
||||
|
||||
await prisma.appContent.create({
|
||||
data: {
|
||||
Title_APC: title,
|
||||
Content_APC: description,
|
||||
Type_APC: type,
|
||||
CorpType_APC: corp,
|
||||
Url_APC: files[0],
|
||||
TargetUrl_APC: req.body.targetUrl || null,
|
||||
Filename_APC: files[1],
|
||||
CreatedAt_APC: localTime(new Date()),
|
||||
UpdatedAt_APC: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, `${type} item created successfully`, null);
|
||||
|
||||
} catch (err) {
|
||||
logger.info(err);
|
||||
return badRequestResponse(res, "Internal Server Error", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getContents = async (req, res) => {
|
||||
try {
|
||||
|
||||
const { type, corp } = req.params;
|
||||
|
||||
if (!Object.values(ContentType).includes(type)) {
|
||||
return badRequestResponse(res, "Invalid type value", `Allowed values: ${Object.values(ContentType).join(", ")}`);
|
||||
}
|
||||
|
||||
if (corp != "all" && !Object.values(CorpType).includes(corp)) {
|
||||
return badRequestResponse(res, "Invalid corp value", `Allowed values: ${Object.values(CorpType).join(", ")}`);
|
||||
}
|
||||
|
||||
var data = [];
|
||||
|
||||
if (corp == "all") {
|
||||
data = await prisma.appContent.findMany({
|
||||
where: {
|
||||
Type_APC: type,
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_APC: 'desc'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
data = await prisma.appContent.findMany({
|
||||
where: {
|
||||
Type_APC: type,
|
||||
CorpType_APC: corp,
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_APC: 'desc'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return successResponse(res, `${type} with corp ${corp} items retrieved successfully`, data);
|
||||
|
||||
} catch (err) {
|
||||
logger.info(err);
|
||||
return badRequestResponse(res, "Internal Server Error", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateContent = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { title, description, type, corp } = req.body;
|
||||
const imageFile = req.files;
|
||||
|
||||
if (!imageFile) {
|
||||
return badRequestResponse(res, "Image file is required", "Missing image file in request");
|
||||
}
|
||||
|
||||
if (!title || !description || !type || !corp) {
|
||||
return badRequestResponse(res, "Title, description, type, and corp are required", null);
|
||||
}
|
||||
|
||||
if (!Object.values(ContentType).includes(type)) {
|
||||
return badRequestResponse(res, "Invalid type value", `Allowed values: ${Object.values(ContentType).join(", ")}`);
|
||||
}
|
||||
|
||||
if (corp != "all" && !Object.values(CorpType).includes(corp)) {
|
||||
return badRequestResponse(res, "Invalid corp value", `Allowed values: ${Object.values(CorpType).join(", ")}`);
|
||||
}
|
||||
|
||||
const existingItem = await prisma.appContent.findUnique({
|
||||
where: { UUID_APC: id }
|
||||
});
|
||||
|
||||
await fileServices.delete(prefixes.bucketName, existingItem.Type_APC, existingItem.Filename_APC);
|
||||
|
||||
const files = await fileServices.upload(prefixes.bucketName, type, (imageFile[0]).mimetype, imageFile);
|
||||
|
||||
await prisma.appContent.update({
|
||||
where: { UUID_APC: id },
|
||||
data: {
|
||||
Title_APC: title || existingItem.Title_APC,
|
||||
Content_APC: description || existingItem.Content_APC,
|
||||
Type_APC: type || existingItem.Type_APC,
|
||||
CorpType_APC: corp || existingItem.CorpType_APC,
|
||||
Url_APC: files[0] || existingItem.Url_APC,
|
||||
TargetUrl_APC: req.body.targetUrl || existingItem.TargetUrl_APC,
|
||||
Filename_APC: files[1] || existingItem.Filename_APC,
|
||||
UpdatedAt_APC: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, `${type} with corp ${corp} item updated successfully`, null);
|
||||
} catch (err) {
|
||||
logger.info(err);
|
||||
return badRequestResponse(res, "Internal Server Error", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteContent = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const existingItem = await prisma.appContent.findFirst({
|
||||
where: { UUID_APC: id }
|
||||
});
|
||||
|
||||
if (!existingItem) {
|
||||
return badRequestResponse(res, `${existingItem.Type_APC} item not found`, "Invalid ID");
|
||||
}
|
||||
|
||||
await fileServices.delete(prefixes.bucketName, existingItem.Type_APC, existingItem.Filename_APC);
|
||||
|
||||
await prisma.appContent.delete({
|
||||
where: { UUID_APC: id }
|
||||
});
|
||||
|
||||
return successResponse(res, `${existingItem.Type_APC} item deleted successfully`, null);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getStats = async (req, res) => {
|
||||
try {
|
||||
const now = new Date();
|
||||
const last30Days = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
const last7Days = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
const [
|
||||
splashCount,
|
||||
promoCount,
|
||||
articleCount,
|
||||
bannerCount,
|
||||
floatingWidgetCount,
|
||||
apiKeysCount,
|
||||
usersCount,
|
||||
buckets,
|
||||
allContents,
|
||||
campaignStats,
|
||||
userActivities,
|
||||
recentContents,
|
||||
recentUsers
|
||||
] = await Promise.all([
|
||||
prisma.appContent.count({ where: { Type_APC: ContentType.splash } }),
|
||||
prisma.appContent.count({ where: { Type_APC: ContentType.promo } }),
|
||||
prisma.appContent.count({ where: { Type_APC: ContentType.article } }),
|
||||
prisma.appContent.count({ where: { Type_APC: ContentType.banner } }),
|
||||
prisma.appContent.count({ where: { Type_APC: ContentType.floatingWidget } }),
|
||||
prisma.appCredential.count(),
|
||||
prisma.usersToken.count(),
|
||||
getAllBuckets(),
|
||||
prisma.appContent.findMany({
|
||||
select: {
|
||||
Type_APC: true,
|
||||
CorpType_APC: true,
|
||||
CreatedAt_APC: true,
|
||||
}
|
||||
}),
|
||||
prisma.appCampaign.groupBy({
|
||||
by: ['Status_ACP'],
|
||||
_count: {
|
||||
Status_ACP: true
|
||||
}
|
||||
}),
|
||||
prisma.usersActivity.groupBy({
|
||||
by: ['ActivityType_UA'],
|
||||
_count: {
|
||||
ActivityType_UA: true
|
||||
}
|
||||
}),
|
||||
prisma.appContent.findMany({
|
||||
where: {
|
||||
CreatedAt_APC: {
|
||||
gte: last30Days
|
||||
}
|
||||
},
|
||||
select: {
|
||||
CreatedAt_APC: true,
|
||||
Type_APC: true,
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_APC: 'asc'
|
||||
}
|
||||
}),
|
||||
|
||||
prisma.usersToken.findMany({
|
||||
where: {
|
||||
CreatedAt_UT: {
|
||||
gte: last7Days
|
||||
}
|
||||
},
|
||||
select: {
|
||||
CreatedAt_UT: true
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_UT: 'asc'
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
const bucketCount = Array.isArray(buckets) ? buckets.length : 0;
|
||||
|
||||
const contentByCorpType = {};
|
||||
allContents.forEach(content => {
|
||||
if (!contentByCorpType[content.CorpType_APC]) {
|
||||
contentByCorpType[content.CorpType_APC] = 0;
|
||||
}
|
||||
contentByCorpType[content.CorpType_APC]++;
|
||||
});
|
||||
|
||||
const contentMatrix = {};
|
||||
allContents.forEach(content => {
|
||||
const key = `${content.Type_APC}_${content.CorpType_APC}`;
|
||||
if (!contentMatrix[key]) {
|
||||
contentMatrix[key] = {
|
||||
type: content.Type_APC,
|
||||
corp: content.CorpType_APC,
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
contentMatrix[key].count++;
|
||||
});
|
||||
|
||||
const contentTimeline = {};
|
||||
recentContents.forEach(content => {
|
||||
const date = new Date(content.CreatedAt_APC).toISOString().split('T')[0];
|
||||
if (!contentTimeline[date]) {
|
||||
contentTimeline[date] = {};
|
||||
}
|
||||
if (!contentTimeline[date][content.Type_APC]) {
|
||||
contentTimeline[date][content.Type_APC] = 0;
|
||||
}
|
||||
contentTimeline[date][content.Type_APC]++;
|
||||
});
|
||||
|
||||
const userRegistrationTrend = {};
|
||||
recentUsers.forEach(user => {
|
||||
const date = new Date(user.CreatedAt_UT).toISOString().split('T')[0];
|
||||
if (!userRegistrationTrend[date]) {
|
||||
userRegistrationTrend[date] = 0;
|
||||
}
|
||||
userRegistrationTrend[date]++;
|
||||
});
|
||||
|
||||
const campaignStatusDistribution = campaignStats.reduce((acc, item) => {
|
||||
acc[item.Status_ACP] = item._count.Status_ACP;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const activityTypeDistribution = userActivities.reduce((acc, item) => {
|
||||
acc[item.ActivityType_UA] = item._count.ActivityType_UA;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return successResponse(res, "CMS stats retrieved successfully", {
|
||||
summary: {
|
||||
content: {
|
||||
splash: splashCount,
|
||||
promo: promoCount,
|
||||
article: articleCount,
|
||||
banner: bannerCount,
|
||||
floatingWidget: floatingWidgetCount,
|
||||
total: splashCount + promoCount + articleCount + bannerCount + floatingWidgetCount
|
||||
},
|
||||
infra: {
|
||||
buckets: bucketCount,
|
||||
apiKeys: apiKeysCount,
|
||||
users: usersCount,
|
||||
},
|
||||
campaigns: await prisma.appCampaign.count(),
|
||||
activities: await prisma.usersActivity.count()
|
||||
},
|
||||
|
||||
charts: {
|
||||
contentByType: {
|
||||
labels: ['Splash', 'Promo', 'Article', 'Banner', 'Floating Widget'],
|
||||
data: [splashCount, promoCount, articleCount, bannerCount, floatingWidgetCount]
|
||||
},
|
||||
|
||||
contentByCorpType: {
|
||||
labels: Object.keys(contentByCorpType),
|
||||
data: Object.values(contentByCorpType)
|
||||
},
|
||||
|
||||
contentMatrix: Object.values(contentMatrix),
|
||||
|
||||
contentCreationTimeline: contentTimeline,
|
||||
|
||||
campaignStatus: {
|
||||
labels: Object.keys(campaignStatusDistribution),
|
||||
data: Object.values(campaignStatusDistribution)
|
||||
},
|
||||
|
||||
activityTypes: {
|
||||
labels: Object.keys(activityTypeDistribution),
|
||||
data: Object.values(activityTypeDistribution)
|
||||
},
|
||||
|
||||
userRegistrationTrend: {
|
||||
labels: Object.keys(userRegistrationTrend).sort(),
|
||||
data: Object.keys(userRegistrationTrend).sort().map(date => userRegistrationTrend[date])
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,320 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse, successResponse, notFoundResponse } = require("../res/responses.js");
|
||||
|
||||
// SERVICES
|
||||
const fileServices = require('../services/file.services.js');
|
||||
const logger = require('../services/logger.services.js');
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
|
||||
const { default: prefixes } = require('../static/prefix.js');
|
||||
|
||||
|
||||
exports.getMenus = async (req, res) => {
|
||||
try {
|
||||
const { isActive } = req.query;
|
||||
|
||||
const filter = {};
|
||||
if (isActive !== undefined) {
|
||||
filter.IsActive_AM = isActive === 'true';
|
||||
}
|
||||
|
||||
const menus = await prisma.appMenu.findMany({
|
||||
where: filter,
|
||||
orderBy: {
|
||||
Order_AM: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
const formattedMenus = menus.map(menu => ({
|
||||
id: menu.UUID_AM,
|
||||
name: menu.Name_AM,
|
||||
route: menu.Route_AM,
|
||||
icon: menu.Icon_AM,
|
||||
isActive: menu.IsActive_AM,
|
||||
badge: menu.Badge_AM,
|
||||
order: menu.Order_AM,
|
||||
type: menu.Type_AM,
|
||||
createdAt: menu.CreatedAt_AM,
|
||||
updatedAt: menu.UpdatedAt_AM
|
||||
}));
|
||||
|
||||
logger.info(`Retrieved ${formattedMenus.length} menus`);
|
||||
return successResponse(res, "Menus retrieved successfully", formattedMenus);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error getting menus: ${error.message}`);
|
||||
return badRequestResponse(res, "Failed to retrieve menus", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getMenuById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const menu = await prisma.appMenu.findUnique({
|
||||
where: {
|
||||
UUID_AM: id
|
||||
}
|
||||
});
|
||||
|
||||
if (!menu) {
|
||||
logger.warn(`Menu not found with id: ${id}`);
|
||||
return notFoundResponse(res, "Menu not found", null);
|
||||
}
|
||||
|
||||
const formattedMenu = {
|
||||
id: menu.UUID_AM,
|
||||
name: menu.Name_AM,
|
||||
route: menu.Route_AM,
|
||||
icon: menu.Icon_AM,
|
||||
isActive: menu.IsActive_AM,
|
||||
badge: menu.Badge_AM,
|
||||
order: menu.Order_AM,
|
||||
createdAt: menu.CreatedAt_AM,
|
||||
updatedAt: menu.UpdatedAt_AM
|
||||
};
|
||||
|
||||
logger.info(`Retrieved menu: ${menu.Name_AM}`);
|
||||
return successResponse(res, "Menu retrieved successfully", formattedMenu);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error getting menu by id: ${error.message}`);
|
||||
return badRequestResponse(res, "Failed to retrieve menu", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
exports.createMenu = async (req, res) => {
|
||||
try {
|
||||
const { name, route, isActive, badge, order, type } = req.body;
|
||||
const iconFile = req.files;
|
||||
|
||||
if (!name || !route) {
|
||||
return badRequestResponse(res, "Name and route are required", null);
|
||||
}
|
||||
|
||||
if (type && !['IN_APP_ROUTE', 'WEB_OPEN'].includes(type)) {
|
||||
return badRequestResponse(res, "Type must be either IN_APP_ROUTE or WEB_OPEN", null);
|
||||
}
|
||||
|
||||
const parsedIsActive = isActive === 'true' || isActive === true;
|
||||
const parsedOrder = order !== undefined ? parseInt(order, 10) : 0;
|
||||
|
||||
if (!iconFile || iconFile.length === 0) {
|
||||
return badRequestResponse(res, "Icon file is required", null);
|
||||
}
|
||||
|
||||
const existingMenu = await prisma.appMenu.findFirst({
|
||||
where: {
|
||||
Route_AM: route
|
||||
}
|
||||
});
|
||||
|
||||
if (existingMenu) {
|
||||
return badRequestResponse(res, "Menu with this route already exists", null);
|
||||
}
|
||||
|
||||
const [iconUrl, fileName] = await fileServices.upload(
|
||||
prefixes.bucketName,
|
||||
'menu-icons',
|
||||
iconFile[0].mimetype,
|
||||
iconFile
|
||||
);
|
||||
|
||||
const menu = await prisma.appMenu.create({
|
||||
data: {
|
||||
Name_AM: name,
|
||||
Route_AM: route,
|
||||
Icon_AM: iconUrl,
|
||||
IsActive_AM: parsedIsActive,
|
||||
Badge_AM: badge || null,
|
||||
Order_AM: parsedOrder,
|
||||
Type_AM: type || 'IN_APP_ROUTE',
|
||||
CreatedAt_AM: localTime(new Date()),
|
||||
UpdatedAt_AM: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
const formattedMenu = {
|
||||
id: menu.UUID_AM,
|
||||
name: menu.Name_AM,
|
||||
route: menu.Route_AM,
|
||||
icon: menu.Icon_AM,
|
||||
isActive: menu.IsActive_AM,
|
||||
badge: menu.Badge_AM,
|
||||
order: menu.Order_AM,
|
||||
type: menu.Type_AM,
|
||||
createdAt: menu.CreatedAt_AM,
|
||||
updatedAt: menu.UpdatedAt_AM
|
||||
};
|
||||
|
||||
logger.info(`Menu created: ${menu.Name_AM} with icon: ${fileName}`);
|
||||
return successResponse(res, "Menu created successfully", formattedMenu);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error creating menu: ${error.message}`);
|
||||
return badRequestResponse(res, "Failed to create menu", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.updateMenu = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, route, isActive, badge, order, type } = req.body;
|
||||
const iconFile = req.files;
|
||||
|
||||
// Parse FormData values to correct types
|
||||
const parsedIsActive = isActive !== undefined ? (isActive === 'true' || isActive === true) : undefined;
|
||||
const parsedOrder = order !== undefined ? parseInt(order, 10) : undefined;
|
||||
|
||||
if (type && !['IN_APP_ROUTE', 'WEB_OPEN'].includes(type)) {
|
||||
return badRequestResponse(res, "Type must be either IN_APP_ROUTE or WEB_OPEN", null);
|
||||
}
|
||||
|
||||
if (type && !['IN_APP_ROUTE', 'WEB_OPEN'].includes(type)) {
|
||||
return badRequestResponse(res, "Type must be either IN_APP_ROUTE or WEB_OPEN", null);
|
||||
}
|
||||
|
||||
const existingMenu = await prisma.appMenu.findUnique({
|
||||
where: {
|
||||
UUID_AM: id
|
||||
}
|
||||
});
|
||||
|
||||
if (!existingMenu) {
|
||||
logger.warn(`Menu not found with id: ${id}`);
|
||||
return notFoundResponse(res, "Menu not found", null);
|
||||
}
|
||||
|
||||
if (route && route !== existingMenu.Route_AM) {
|
||||
const duplicateRoute = await prisma.appMenu.findFirst({
|
||||
where: {
|
||||
Route_AM: route,
|
||||
UUID_AM: {
|
||||
not: id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (duplicateRoute) {
|
||||
return badRequestResponse(res, "Menu with this route already exists", null);
|
||||
}
|
||||
}
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.Name_AM = name;
|
||||
if (route !== undefined) updateData.Route_AM = route;
|
||||
if (parsedIsActive !== undefined) updateData.IsActive_AM = parsedIsActive;
|
||||
if (badge !== undefined) updateData.Badge_AM = badge;
|
||||
if (parsedOrder !== undefined) updateData.Order_AM = parsedOrder;
|
||||
if (type !== undefined) updateData.Type_AM = type;
|
||||
updateData.UpdatedAt_AM = localTime(new Date());
|
||||
|
||||
if (iconFile && iconFile.length > 0) {
|
||||
const [iconUrl, fileName] = await fileServices.upload(
|
||||
prefixes.bucketName,
|
||||
'menu-icons',
|
||||
iconFile[0].mimetype,
|
||||
iconFile
|
||||
);
|
||||
updateData.Icon_AM = iconUrl;
|
||||
logger.info(`New icon uploaded: ${fileName}`);
|
||||
}
|
||||
|
||||
const menu = await prisma.appMenu.update({
|
||||
where: {
|
||||
UUID_AM: id
|
||||
},
|
||||
data: updateData
|
||||
});
|
||||
|
||||
const formattedMenu = {
|
||||
id: menu.UUID_AM,
|
||||
name: menu.Name_AM,
|
||||
route: menu.Route_AM,
|
||||
icon: menu.Icon_AM,
|
||||
isActive: menu.IsActive_AM,
|
||||
badge: menu.Badge_AM,
|
||||
order: menu.Order_AM,
|
||||
type: menu.Type_AM,
|
||||
createdAt: menu.CreatedAt_AM,
|
||||
updatedAt: menu.UpdatedAt_AM
|
||||
};
|
||||
|
||||
logger.info(`Menu updated: ${menu.Name_AM}`);
|
||||
return successResponse(res, "Menu updated successfully", formattedMenu);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error updating menu: ${error.message}`);
|
||||
return badRequestResponse(res, "Failed to update menu", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteMenu = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const existingMenu = await prisma.appMenu.findUnique({
|
||||
where: {
|
||||
UUID_AM: id
|
||||
}
|
||||
});
|
||||
|
||||
if (!existingMenu) {
|
||||
logger.warn(`Menu not found with id: ${id}`);
|
||||
return notFoundResponse(res, "Menu not found", null);
|
||||
}
|
||||
|
||||
await prisma.appMenu.delete({
|
||||
where: {
|
||||
UUID_AM: id
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Menu deleted: ${existingMenu.Name_AM}`);
|
||||
return successResponse(res, "Menu deleted successfully", null);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting menu: ${error.message}`);
|
||||
return badRequestResponse(res, "Failed to delete menu", error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.reorderMenus = async (req, res) => {
|
||||
try {
|
||||
const { menuOrders } = req.body;
|
||||
|
||||
if (!Array.isArray(menuOrders) || menuOrders.length === 0) {
|
||||
return badRequestResponse(res, "menuOrders array is required", null);
|
||||
}
|
||||
|
||||
const updatePromises = menuOrders.map(({ id, order }) =>
|
||||
prisma.appMenu.update({
|
||||
where: {
|
||||
UUID_AM: id
|
||||
},
|
||||
data: {
|
||||
Order_AM: order
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
|
||||
logger.info(`Reordered ${menuOrders.length} menus`);
|
||||
return successResponse(res, "Menus reordered successfully", null);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error reordering menus: ${error.message}`);
|
||||
return badRequestResponse(res, "Failed to reorder menus", error.message);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,455 @@
|
|||
const { PrismaClient: MSGClient } = require("../../prisma/clients/msg");
|
||||
const logger = require("../services/logger.services.js");
|
||||
const { successResponse, errorResponse, notFoundResponse, badRequestResponse } = require("../res/responses.js");
|
||||
const fileService = require("../services/file.services.js");
|
||||
|
||||
const msgPrisma = new MSGClient();
|
||||
|
||||
class MessagesController {
|
||||
async getConversations(req, res) {
|
||||
try {
|
||||
const { userId } = req.query;
|
||||
const { userType = 'user' } = req.query;
|
||||
const { page = 1, limit = 20 } = req.query;
|
||||
|
||||
const skip = (parseInt(page) - 1) * parseInt(limit);
|
||||
|
||||
let whereCondition;
|
||||
if (userType === 'admin') {
|
||||
whereCondition = { adminId: userId };
|
||||
} else {
|
||||
whereCondition = { userId: userId };
|
||||
}
|
||||
|
||||
const conversations = await msgPrisma.conversations.findMany({
|
||||
where: whereCondition,
|
||||
include: {
|
||||
messages: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1,
|
||||
select: {
|
||||
content: true,
|
||||
senderType: true,
|
||||
senderName: true,
|
||||
createdAt: true,
|
||||
messageType: true
|
||||
}
|
||||
},
|
||||
_count: {
|
||||
select: { messages: true }
|
||||
}
|
||||
},
|
||||
orderBy: { lastMessageAt: 'desc' },
|
||||
skip: skip,
|
||||
take: parseInt(limit)
|
||||
});
|
||||
|
||||
const totalCount = await msgPrisma.conversations.count({
|
||||
where: whereCondition
|
||||
});
|
||||
|
||||
const formattedConversations = conversations.map(conv => ({
|
||||
id: conv.id,
|
||||
subject: conv.subject,
|
||||
status: conv.status,
|
||||
priority: conv.priority,
|
||||
userId: conv.userId,
|
||||
adminId: conv.adminId,
|
||||
createdAt: conv.createdAt,
|
||||
updatedAt: conv.updatedAt,
|
||||
lastMessageAt: conv.lastMessageAt,
|
||||
messageCount: conv._count.messages,
|
||||
lastMessage: conv.messages[0] || null
|
||||
}));
|
||||
|
||||
successResponse(res, 'Conversations retrieved successfully', {
|
||||
conversations: formattedConversations,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalCount,
|
||||
pages: Math.ceil(totalCount / parseInt(limit))
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching conversations:', error);
|
||||
errorResponse(res, 'Failed to fetch conversations');
|
||||
}
|
||||
}
|
||||
|
||||
async getMessages(req, res) {
|
||||
try {
|
||||
const { conversationId } = req.params;
|
||||
const { limit = 50, offset = 0 } = req.query;
|
||||
|
||||
const conversation = await msgPrisma.conversations.findUnique({
|
||||
where: { id: conversationId }
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return notFoundResponse(res, 'Conversation not found');
|
||||
}
|
||||
|
||||
const messages = await msgPrisma.messages.findMany({
|
||||
where: { conversationId: conversationId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
skip: parseInt(offset),
|
||||
take: parseInt(limit),
|
||||
});
|
||||
|
||||
const totalCount = await msgPrisma.messages.count({
|
||||
where: { conversationId: conversationId }
|
||||
});
|
||||
|
||||
const messageIds = messages.map(m => m.id);
|
||||
let attachmentsByMessage = {};
|
||||
if (messageIds.length > 0) {
|
||||
const attachments = await msgPrisma.messageAttachments.findMany({
|
||||
where: { messageId: { in: messageIds } }
|
||||
});
|
||||
|
||||
attachmentsByMessage = attachments.reduce((acc, att) => {
|
||||
if (!acc[att.messageId]) acc[att.messageId] = [];
|
||||
acc[att.messageId].push({
|
||||
id: att.id,
|
||||
fileName: att.fileName,
|
||||
fileSize: att.fileSize,
|
||||
mimeType: att.mimeType,
|
||||
fileUrl: att.fileUrl,
|
||||
createdAt: att.createdAt
|
||||
});
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const formattedMessages = messages.map(msg => ({
|
||||
id: msg.id,
|
||||
conversationId: msg.conversationId,
|
||||
content: msg.content,
|
||||
messageType: msg.messageType,
|
||||
senderId: msg.senderId,
|
||||
senderType: msg.senderType,
|
||||
senderName: msg.senderName,
|
||||
status: msg.status,
|
||||
readAt: msg.readAt,
|
||||
deliveredAt: msg.deliveredAt,
|
||||
createdAt: msg.createdAt,
|
||||
updatedAt: msg.updatedAt,
|
||||
metadata: msg.metadata,
|
||||
attachments: attachmentsByMessage[msg.id] || []
|
||||
}));
|
||||
|
||||
successResponse(res, 'Messages retrieved successfully', {
|
||||
messages: formattedMessages,
|
||||
pagination: {
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
total: totalCount,
|
||||
hasMore: parseInt(offset) + parseInt(limit) < totalCount
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error fetching messages:', error);
|
||||
errorResponse(res, 'Failed to fetch messages');
|
||||
}
|
||||
}
|
||||
|
||||
async createConversation(req, res) {
|
||||
try {
|
||||
const { userId, adminId, subject, initialMessage, priority = 'normal' } = req.body;
|
||||
|
||||
if (!userId) {
|
||||
return badRequestResponse(res, 'userId is required');
|
||||
}
|
||||
|
||||
const conversation = await msgPrisma.conversations.create({
|
||||
data: {
|
||||
userId,
|
||||
adminId,
|
||||
subject: subject || 'New Conversation',
|
||||
priority,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
let initialMessageData = null;
|
||||
if (initialMessage) {
|
||||
const message = await msgPrisma.messages.create({
|
||||
data: {
|
||||
conversationId: conversation.id,
|
||||
content: initialMessage.content || initialMessage,
|
||||
messageType: initialMessage.messageType || 'text',
|
||||
senderId: initialMessage.senderId || userId,
|
||||
senderType: initialMessage.senderType || 'user',
|
||||
senderName: initialMessage.senderName,
|
||||
status: 'sent'
|
||||
}
|
||||
});
|
||||
|
||||
initialMessageData = {
|
||||
id: message.id,
|
||||
content: message.content,
|
||||
senderId: message.senderId,
|
||||
senderType: message.senderType,
|
||||
createdAt: message.createdAt
|
||||
};
|
||||
|
||||
await msgPrisma.conversations.update({
|
||||
where: { id: conversation.id },
|
||||
data: { lastMessageAt: new Date() }
|
||||
});
|
||||
}
|
||||
|
||||
successResponse(res, 'Conversation created successfully', {
|
||||
...conversation,
|
||||
initialMessage: initialMessageData
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error creating conversation:', error);
|
||||
errorResponse(res, 'Failed to create conversation');
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage(req, res) {
|
||||
try {
|
||||
const { conversationId, message, senderId, senderType, messageType = 'text', senderName, metadata } = req.body;
|
||||
|
||||
if (!conversationId || !message || !senderId || !senderType) {
|
||||
return badRequestResponse(res, 'conversationId, message, senderId, and senderType are required');
|
||||
}
|
||||
|
||||
const conversation = await msgPrisma.conversations.findUnique({
|
||||
where: { id: conversationId }
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return notFoundResponse(res, 'Conversation not found');
|
||||
}
|
||||
|
||||
const savedMessage = await msgPrisma.messages.create({
|
||||
data: {
|
||||
conversationId,
|
||||
content: message,
|
||||
messageType,
|
||||
senderId,
|
||||
senderType,
|
||||
senderName,
|
||||
status: 'sent',
|
||||
metadata: metadata || {}
|
||||
}
|
||||
});
|
||||
|
||||
let attachments = [];
|
||||
if (req.files && req.files.length > 0) {
|
||||
const bucketName = 'cifosuperapps';
|
||||
const folderName = 'messages/attachments';
|
||||
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
const [fileUrl, fileName] = await fileService.upload(bucketName, folderName, file.mimetype, [file]);
|
||||
|
||||
const attachment = await msgPrisma.messageAttachments.create({
|
||||
data: {
|
||||
messageId: savedMessage.id,
|
||||
fileName: file.originalname,
|
||||
fileSize: file.size,
|
||||
mimeType: file.mimetype,
|
||||
fileUrl: fileUrl,
|
||||
metadata: {
|
||||
originalName: file.originalname,
|
||||
uploadedName: fileName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
attachments.push({
|
||||
id: attachment.id,
|
||||
fileName: attachment.fileName,
|
||||
fileSize: attachment.fileSize,
|
||||
mimeType: attachment.mimeType,
|
||||
fileUrl: attachment.fileUrl,
|
||||
createdAt: attachment.createdAt
|
||||
});
|
||||
} catch (uploadError) {
|
||||
|
||||
logger.error('Error uploading attachment:', uploadError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await msgPrisma.conversations.update({
|
||||
where: { id: conversationId },
|
||||
data: { lastMessageAt: new Date() }
|
||||
});
|
||||
|
||||
const responseMessage = {
|
||||
id: savedMessage.id,
|
||||
conversationId: savedMessage.conversationId,
|
||||
content: savedMessage.content,
|
||||
messageType: savedMessage.messageType,
|
||||
senderId: savedMessage.senderId,
|
||||
senderType: savedMessage.senderType,
|
||||
senderName: savedMessage.senderName,
|
||||
status: savedMessage.status,
|
||||
createdAt: savedMessage.createdAt,
|
||||
updatedAt: savedMessage.updatedAt,
|
||||
metadata: savedMessage.metadata,
|
||||
attachments: attachments
|
||||
};
|
||||
|
||||
successResponse(res, 'Message sent successfully', responseMessage);
|
||||
} catch (error) {
|
||||
logger.error('Error sending message:', error);
|
||||
errorResponse(res, 'Failed to send message');
|
||||
}
|
||||
}
|
||||
|
||||
async markMessagesAsRead(req, res) {
|
||||
try {
|
||||
const { conversationId } = req.params;
|
||||
const { userId, userType } = req.body;
|
||||
|
||||
if (!userId || !userType) {
|
||||
return badRequestResponse(res, 'userId and userType are required');
|
||||
}
|
||||
|
||||
const conversation = await msgPrisma.conversations.findUnique({
|
||||
where: { id: conversationId }
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
return notFoundResponse(res, 'Conversation not found');
|
||||
}
|
||||
|
||||
const result = await msgPrisma.messages.updateMany({
|
||||
where: {
|
||||
conversationId: conversationId,
|
||||
senderId: { not: userId },
|
||||
readAt: null
|
||||
},
|
||||
data: {
|
||||
readAt: new Date(),
|
||||
status: 'read'
|
||||
}
|
||||
});
|
||||
|
||||
successResponse(res, `${result.count} messages marked as read`, {
|
||||
conversationId,
|
||||
messagesMarkedAsRead: result.count
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error marking messages as read:', error);
|
||||
errorResponse(res, 'Failed to mark messages as read');
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAttachment(req, res) {
|
||||
try {
|
||||
const { attachmentId } = req.params;
|
||||
|
||||
if (!attachmentId) {
|
||||
return badRequestResponse(res, 'attachmentId is required');
|
||||
}
|
||||
|
||||
const attachment = await msgPrisma.messageAttachments.findUnique({
|
||||
where: { id: attachmentId }
|
||||
});
|
||||
|
||||
if (!attachment) {
|
||||
return notFoundResponse(res, 'Attachment not found');
|
||||
}
|
||||
|
||||
// Delete file from storage
|
||||
try {
|
||||
const bucketName = 'messages';
|
||||
const folderName = 'attachments';
|
||||
const fileName = attachment.metadata?.uploadedName || attachment.fileName;
|
||||
|
||||
await fileService.delete(bucketName, folderName, fileName);
|
||||
} catch (fileError) {
|
||||
logger.error('Error deleting file from storage:', fileError);
|
||||
// Continue with database deletion even if file deletion fails
|
||||
}
|
||||
|
||||
// Delete attachment record from database
|
||||
await msgPrisma.messageAttachments.delete({
|
||||
where: { id: attachmentId }
|
||||
});
|
||||
|
||||
successResponse(res, 'Attachment deleted successfully');
|
||||
} catch (error) {
|
||||
logger.error('Error deleting attachment:', error);
|
||||
errorResponse(res, 'Failed to delete attachment');
|
||||
}
|
||||
}
|
||||
|
||||
async getStats(req, res) {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const { userType = 'user' } = req.query;
|
||||
let whereCondition;
|
||||
if (userType === 'admin') {
|
||||
whereCondition = { adminId: userId };
|
||||
} else {
|
||||
whereCondition = { userId: userId };
|
||||
}
|
||||
|
||||
const totalConversations = await msgPrisma.conversations.count({
|
||||
where: whereCondition
|
||||
});
|
||||
|
||||
const activeConversations = await msgPrisma.conversations.count({
|
||||
where: {
|
||||
...whereCondition,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
let conversationIds = [];
|
||||
if (userType === 'admin') {
|
||||
const conversations = await msgPrisma.conversations.findMany({
|
||||
where: { adminId: userId },
|
||||
select: { id: true }
|
||||
});
|
||||
conversationIds = conversations.map(c => c.id);
|
||||
} else {
|
||||
const conversations = await msgPrisma.conversations.findMany({
|
||||
where: { userId: userId },
|
||||
select: { id: true }
|
||||
});
|
||||
conversationIds = conversations.map(c => c.id);
|
||||
}
|
||||
|
||||
let totalMessages = 0;
|
||||
let unreadMessages = 0;
|
||||
|
||||
if (conversationIds.length > 0) {
|
||||
totalMessages = await msgPrisma.messages.count({
|
||||
where: { conversationId: { in: conversationIds } }
|
||||
});
|
||||
|
||||
unreadMessages = await msgPrisma.messages.count({
|
||||
where: {
|
||||
conversationId: { in: conversationIds },
|
||||
senderId: { not: userId },
|
||||
readAt: null
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const stats = {
|
||||
totalConversations,
|
||||
activeConversations,
|
||||
unreadMessages,
|
||||
totalMessages
|
||||
};
|
||||
|
||||
successResponse(res, 'Statistics retrieved successfully', stats);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching stats:', error);
|
||||
errorResponse(res, 'Failed to fetch statistics');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MessagesController();
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
const { badRequestResponse, successResponse } = require("../res/responses.js");
|
||||
|
||||
const {
|
||||
deleteBucket,
|
||||
createBucket,
|
||||
getAllBuckets,
|
||||
renameBucket,
|
||||
setBucketPolicy
|
||||
} = require('../services/minio.services.js');
|
||||
|
||||
// ================= CONTROLLERS ================= //
|
||||
|
||||
exports.deleteOneBucket = async (req, res) => {
|
||||
try {
|
||||
const { bucketName } = req.body;
|
||||
if (!bucketName) {
|
||||
return badRequestResponse(res, "bucketName is required");
|
||||
}
|
||||
|
||||
await deleteBucket(bucketName);
|
||||
return successResponse(res, "Bucket deleted successfully", null);
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err.message || err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.createOneBucket = async (req, res) => {
|
||||
try {
|
||||
const { bucketName } = req.body;
|
||||
if (!bucketName) {
|
||||
return badRequestResponse(res, "bucketName is required");
|
||||
}
|
||||
|
||||
await createBucket(bucketName);
|
||||
return successResponse(res, "Bucket created successfully", null);
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err.message || err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAllBuckets = async (req, res) => {
|
||||
try {
|
||||
const buckets = await getAllBuckets();
|
||||
return successResponse(res, "Buckets fetched successfully", buckets);
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err.message || err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateBucket = async (req, res) => {
|
||||
try {
|
||||
const { oldBucket, newBucket, policy } = req.body;
|
||||
if (!oldBucket || !newBucket) {
|
||||
return badRequestResponse(res, "oldBucket and newBucket are required");
|
||||
}
|
||||
|
||||
await renameBucket(oldBucket, newBucket);
|
||||
|
||||
if (policy) {
|
||||
await setBucketPolicy(newBucket, policy);
|
||||
}
|
||||
|
||||
return successResponse(res, "Bucket updated successfully", { oldBucket, newBucket, policy });
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err.message || err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateBucketPolicy = async (req, res) => {
|
||||
try {
|
||||
const { bucketName, policy } = req.body;
|
||||
if (!bucketName || !policy) {
|
||||
return badRequestResponse(res, "bucketName and policy are required");
|
||||
}
|
||||
|
||||
await setBucketPolicy(bucketName, policy);
|
||||
return successResponse(res, "Bucket policy updated successfully", { bucketName, policy });
|
||||
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Internal Server Error", err.message || err);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient : CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
// CONSTANTS
|
||||
const { badRequestResponse, successResponse } = require("../res/responses.js");
|
||||
|
||||
const { localTime } = require("../services/time.services.js");
|
||||
|
||||
exports.setupUserToken = async (req, res) => {
|
||||
try {
|
||||
const { userID, token } = req.body;
|
||||
|
||||
if (!userID || !token) {
|
||||
throw new Error("Invalid user ID or token!");
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.usersToken.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ UserID_UT: userID },
|
||||
{ Token_UT: token },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await tx.usersToken.create({
|
||||
data: {
|
||||
UserID_UT: userID,
|
||||
Token_UT: token,
|
||||
CreatedAt_UT: localTime(new Date()),
|
||||
UpdatedAt_UT: localTime(new Date()),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return successResponse(res, "User token setup successfully!", null);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error connecting to API", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAllUsers = async (req, res) => {
|
||||
try {
|
||||
const users = await prisma.usersToken.findMany({
|
||||
orderBy: {
|
||||
UserID_UT: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "Users retrieved successfully!", users);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error connecting to API", err);
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteUser = async (req, res) => {
|
||||
try {
|
||||
const { userID } = req.body;
|
||||
|
||||
if (!userID) {
|
||||
throw new Error("Invalid user ID!");
|
||||
}
|
||||
|
||||
await prisma.usersToken.delete({
|
||||
where: {
|
||||
UserID_UT: userID
|
||||
}
|
||||
});
|
||||
|
||||
return successResponse(res, "User deleted successfully!", null);
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error connecting to API", err);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// ENVIRONMENT
|
||||
require('dotenv').config();
|
||||
|
||||
// LIBRARY
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
// CONSTANT
|
||||
const { JWT_SECRET_KEY } = process.env;
|
||||
|
||||
// RESPONSES
|
||||
const { badRequestResponse} = require("../res/responses");
|
||||
const {expiredTokenResponse } = require("../res/responses");
|
||||
const { invalidTokenResponse } = require("../res/responses");
|
||||
|
||||
// PRISMA
|
||||
const prisma = new CMSClient();
|
||||
|
||||
|
||||
// MIDDLEWARE
|
||||
const validateApiKey = async (req, res, next) => {
|
||||
try {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
|
||||
if (!apiKey) {
|
||||
return badRequestResponse(res, "API key is required", "Missing x-api-key header");
|
||||
}
|
||||
|
||||
const validCredential = await prisma.appCredential.findUnique({
|
||||
where: {
|
||||
TokenCredential_AC: apiKey
|
||||
}
|
||||
});
|
||||
|
||||
if (!validCredential) {
|
||||
return badRequestResponse(res, "Invalid API key", "Unauthorized access");
|
||||
}
|
||||
return next();
|
||||
} catch (err) {
|
||||
return badRequestResponse(res, "Error validating API key", err);
|
||||
}
|
||||
};
|
||||
|
||||
const authenticateToken = async (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
let token = null;
|
||||
|
||||
if (authHeader) {
|
||||
token = authHeader.split(' ')[1];
|
||||
}
|
||||
|
||||
if (!authHeader) {
|
||||
return badRequestResponse(res, "Unauthorized");
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET_KEY);
|
||||
|
||||
const isUserExist = await prisma.adminAccount.findFirst({
|
||||
where: {
|
||||
UUID_AA: decoded.userID,
|
||||
}
|
||||
});
|
||||
|
||||
if (isUserExist) {
|
||||
const currentTime = Math.floor(new Date().getTime() / 1000);
|
||||
const tokenIssuedAt = decoded.iat;
|
||||
|
||||
const expiryTime = 60 * 60 * 24 * 7;
|
||||
|
||||
if (currentTime - tokenIssuedAt > expiryTime) {
|
||||
return expiredTokenResponse(res, "Token expired!");
|
||||
}
|
||||
|
||||
req.locals = { user: decoded.userID };
|
||||
|
||||
return next();
|
||||
|
||||
} else {
|
||||
return invalidTokenResponse(res, "Invalid token!");
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
return invalidTokenResponse(res, "Invalid token!");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = { validateApiKey, authenticateToken };
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
module.exports = {
|
||||
successResponse: (res, message, data = null) => {
|
||||
res.status(200).json({
|
||||
status: 'success',
|
||||
message,
|
||||
data
|
||||
});
|
||||
},
|
||||
|
||||
errorResponse: (res, message, data = null) => {
|
||||
res.status(400).json({
|
||||
status: 'error',
|
||||
message,
|
||||
data
|
||||
});
|
||||
},
|
||||
|
||||
notFoundResponse: (res, message, data = null) => {
|
||||
res.status(404).json({
|
||||
status: 'error',
|
||||
message,
|
||||
data
|
||||
});
|
||||
},
|
||||
|
||||
badRequestResponse: (res, message, data = null) => {
|
||||
res.status(400).json({
|
||||
status: 'error',
|
||||
message,
|
||||
data
|
||||
});
|
||||
},
|
||||
|
||||
expiredTokenResponse: (res, message, data = null) => {
|
||||
res.status(401).json({
|
||||
status: 'error',
|
||||
message,
|
||||
data
|
||||
});
|
||||
},
|
||||
|
||||
invalidTokenResponse: (res, message, data = null) => {
|
||||
res.status(403).json({
|
||||
status: 'Invalid Token!',
|
||||
message,
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router();
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const {
|
||||
create,
|
||||
triggerNotificationForUser,
|
||||
analyzeUserActivities,
|
||||
processAllActivitiesAndNotifications,
|
||||
getAIInsights
|
||||
} = require("../controllers/activity.controller");
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware");
|
||||
|
||||
// ROUTES
|
||||
|
||||
router.post("/activity-management/create", validateApiKey, create);
|
||||
|
||||
router.post("/activity-management/trigger-notification", validateApiKey, triggerNotificationForUser);
|
||||
|
||||
router.post("/activity-management/analyze", validateApiKey, analyzeUserActivities);
|
||||
|
||||
router.post("/activity-management/process-all", validateApiKey, processAllActivitiesAndNotifications);
|
||||
|
||||
router.get("/activity-management/ai-insights", validateApiKey, getAIInsights);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const admin_controller = require("../controllers/admin.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { authenticateToken } = require("../middleware/middleware.js");
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.post("/admin-management/auth", validateApiKey, admin_controller.adminLogin);
|
||||
|
||||
router.post("/admin-management/register", validateApiKey, admin_controller.adminRegister);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const aiNotificationController = require("../controllers/ai-notification.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
|
||||
// ANALYTICS ROUTES
|
||||
router.get("/ai-notifications/analytics", validateApiKey, aiNotificationController.getAnalytics);
|
||||
|
||||
router.post("/ai-notifications/analytics/update", validateApiKey, aiNotificationController.updateAnalytics);
|
||||
|
||||
router.get("/ai-notifications/dashboard-stats", validateApiKey, aiNotificationController.getDashboardStats);
|
||||
|
||||
// NOTIFICATIONS ROUTES
|
||||
router.get("/ai-notifications/all", validateApiKey, aiNotificationController.getAllNotifications);
|
||||
|
||||
router.get("/ai-notifications/:id", validateApiKey, aiNotificationController.getNotificationDetail);
|
||||
|
||||
// SCHEDULED NOTIFICATIONS ROUTES (NEW)
|
||||
router.get("/ai-notifications/scheduled/stats", validateApiKey, aiNotificationController.getScheduledStats);
|
||||
|
||||
router.post("/ai-notifications/scheduled/process", validateApiKey, aiNotificationController.triggerScheduledProcessor);
|
||||
|
||||
router.post("/ai-notifications/scheduled/:id/cancel", validateApiKey, aiNotificationController.cancelScheduled);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const app_controller = require("../controllers/app.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey, authenticateToken } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.get("/api-management/test", app_controller.test);
|
||||
|
||||
router.get("/api-management/test/secure", validateApiKey, app_controller.testSecure);
|
||||
|
||||
router.get("/api-management/test/token", authenticateToken, app_controller.testToken);
|
||||
|
||||
router.post("/api-management/token", validateApiKey, app_controller.createToken);
|
||||
|
||||
router.delete("/api-management/token", validateApiKey, app_controller.deleteToken);
|
||||
|
||||
router.get("/api-management/tokens", validateApiKey, app_controller.getAllTokens);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const minio_controller = require("../controllers/minio.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.get("/bucket-management/", validateApiKey, minio_controller.getAllBuckets);
|
||||
|
||||
router.put("/bucket-management/", validateApiKey, minio_controller.updateBucket);
|
||||
|
||||
router.post("/bucket-management/", validateApiKey, minio_controller.createOneBucket);
|
||||
|
||||
router.delete("/bucket-management/", validateApiKey, minio_controller.deleteOneBucket);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const campaign_controller = require("../controllers/campaign.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.get("/campaign-management/all", validateApiKey, campaign_controller.getAllCampaigns);
|
||||
|
||||
router.post("/campaign-management/send", validateApiKey, campaign_controller.sendNotification);
|
||||
|
||||
router.post("/campaign-management/setup", validateApiKey, campaign_controller.setupCampaign);
|
||||
|
||||
router.put("/campaign-management/:id", validateApiKey, campaign_controller.updateCampaign);
|
||||
|
||||
router.delete("/campaign-management/:id", validateApiKey, campaign_controller.deleteCampaign);
|
||||
|
||||
router.get("/campaign-management/analytics", validateApiKey, campaign_controller.getCampaignAnalytics);
|
||||
|
||||
router.get("/campaign-management/report/:id", validateApiKey, campaign_controller.getCampaignReport);
|
||||
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const cctv_controller = require("../controllers/cctv.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.get("/cctv-management/get/all", validateApiKey, cctv_controller.getAll);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const cms_controller = require("../controllers/cms.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.post("/cms-management/create", validateApiKey, cms_controller.createContent);
|
||||
|
||||
router.get("/cms-management/get/:type/:corp", validateApiKey, cms_controller.getContents);
|
||||
|
||||
router.put("/cms-management/update/:id", validateApiKey, cms_controller.updateContent);
|
||||
|
||||
router.delete("/cms-management/delete/:id", validateApiKey, cms_controller.deleteContent);
|
||||
|
||||
router.get("/cms-management/stats", validateApiKey, cms_controller.getStats);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const crash_controller = require("../controllers/crash.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
router.post("/crash/report", validateApiKey, crash_controller.reportCrash);
|
||||
|
||||
router.post("/crash/session/start", validateApiKey, crash_controller.startSession);
|
||||
|
||||
router.post("/crash/session/end", validateApiKey, crash_controller.endSession);
|
||||
|
||||
router.get("/crash/reports", validateApiKey, crash_controller.getCrashReports);
|
||||
|
||||
router.get("/crash/reports/:crashId", validateApiKey, crash_controller.getCrashDetails);
|
||||
|
||||
router.put("/crash/reports/:crashId/status", validateApiKey, crash_controller.updateCrashStatus);
|
||||
|
||||
router.get("/crash/analytics", validateApiKey, crash_controller.getCrashAnalytics);
|
||||
|
||||
router.get("/crash/top", validateApiKey, crash_controller.getTopCrashes);
|
||||
|
||||
// CHART & VISUALIZATION ROUTES
|
||||
router.get("/crash/charts/trends", validateApiKey, crash_controller.getCrashTrends);
|
||||
|
||||
router.get("/crash/charts/device", validateApiKey, crash_controller.getCrashByDevice);
|
||||
|
||||
router.get("/crash/charts/os-version", validateApiKey, crash_controller.getCrashByOSVersion);
|
||||
|
||||
router.get("/crash/charts/exception-type", validateApiKey, crash_controller.getCrashByExceptionType);
|
||||
|
||||
router.get("/crash/charts/severity", validateApiKey, crash_controller.getCrashBySeverity);
|
||||
|
||||
router.get("/crash/charts/app-version", validateApiKey, crash_controller.getCrashByAppVersion);
|
||||
|
||||
router.get("/crash/charts/heatmap", validateApiKey, crash_controller.getCrashHeatmap);
|
||||
|
||||
router.get("/crash/charts/hourly", validateApiKey, crash_controller.getCrashByHour);
|
||||
|
||||
router.get("/crash/charts/overview", validateApiKey, crash_controller.getCrashOverview);
|
||||
|
||||
router.get("/crash/charts/affected-users", validateApiKey, crash_controller.getAffectedUsersTimeline);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router();
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const menu_controller = require("../controllers/menu.controller.js");
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.get("/menu-management/menus", validateApiKey, menu_controller.getMenus);
|
||||
|
||||
router.get("/menu-management/menu/:id", validateApiKey, menu_controller.getMenuById);
|
||||
|
||||
router.post("/menu-management/menu", validateApiKey, menu_controller.createMenu);
|
||||
|
||||
router.put("/menu-management/menu/:id", validateApiKey, menu_controller.updateMenu);
|
||||
|
||||
router.delete("/menu-management/menu/:id", validateApiKey, menu_controller.deleteMenu);
|
||||
|
||||
router.post("/menu-management/reorder", validateApiKey, menu_controller.reorderMenus);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const messagesController = require("../controllers/messages.controller.js");
|
||||
const multer = require('multer');
|
||||
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024,
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
const allowedTypes = [
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
||||
'application/pdf', 'text/plain', 'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'audio/mpeg', 'audio/wav', 'video/mp4', 'video/quicktime'
|
||||
];
|
||||
if (allowedTypes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Invalid file type'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/messages/conversations', messagesController.getConversations);
|
||||
|
||||
router.post('/messages/conversations', messagesController.createConversation);
|
||||
|
||||
router.get('/messages/:conversationId', messagesController.getMessages);
|
||||
|
||||
router.post('/messages', upload.array('attachments', 5), messagesController.sendMessage);
|
||||
|
||||
router.delete('/messages/attachments/:attachmentId', messagesController.deleteAttachment);
|
||||
|
||||
router.put('/messages/:conversationId/read', messagesController.markMessagesAsRead);
|
||||
|
||||
router.get('/messages/stats/:userId', messagesController.getStats);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// LIBRARY IMPORT
|
||||
const router = require("express").Router()
|
||||
|
||||
// CONTROLLER IMPORT
|
||||
const users_controller = require("../controllers/users.controller.js")
|
||||
|
||||
// MIDDLEWARE IMPORT
|
||||
const { validateApiKey } = require("../middleware/middleware.js");
|
||||
|
||||
// ROUTES
|
||||
router.post("/user-management/setup-token", validateApiKey, users_controller.setupUserToken);
|
||||
|
||||
router.get("/user-management/get-all", validateApiKey, users_controller.getAllUsers);
|
||||
|
||||
router.delete("/user-management/delete", validateApiKey, users_controller.deleteUser);
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
const axios = require('axios');
|
||||
require("dotenv").config();
|
||||
const logger = require('./logger.services');
|
||||
|
||||
const API_URL = process.env.GEMINI_API_URL;
|
||||
const MAX_RETRIES = 3;
|
||||
const INITIAL_RETRY_DELAY = 1000;
|
||||
const MAX_RETRY_DELAY = 10000;
|
||||
|
||||
exports.getAIResponse = async (prompt) => {
|
||||
let limitedPrompt = prompt;
|
||||
if (Array.isArray(prompt)) {
|
||||
limitedPrompt = prompt.slice(0, 7);
|
||||
}
|
||||
const promptString = typeof limitedPrompt === "string" ? limitedPrompt : JSON.stringify(limitedPrompt, null, 2);
|
||||
const systemInstruction = `
|
||||
Anda adalah AI marketing expert yang bertugas menganalisis data aktivitas pengguna dan membuat notifikasi persuasif untuk re-engagement.
|
||||
|
||||
Instruksi:
|
||||
1. Analisis pola dan minat pengguna dari data aktivitas JSON yang diberikan.
|
||||
2. Identifikasi kategori atau fitur yang paling sering diakses (hotel, restoran, transportasi, dll).
|
||||
3. Buat satu notifikasi yang MENARIK dan PERSUASIF agar pengguna kembali membuka aplikasi.
|
||||
4. Gunakan gaya bahasa Indonesia yang casual, friendly, dan menggugah rasa penasaran.
|
||||
5. Fokus pada benefit atau value proposition untuk pengguna, bukan sekadar ringkasan.
|
||||
6. Notifikasi harus fokus pada re-engagement.
|
||||
7. Notifikasi harus sesuai dengan skema JSON yang ditentukan (title maks 50 karakter, description maks 100 karakter). JANGAN tambahkan teks, markdown, atau penjelasan apapun di luar JSON murni.
|
||||
`;
|
||||
const responseSchema = {
|
||||
type: "OBJECT",
|
||||
properties: {
|
||||
"title": {
|
||||
"type": "STRING",
|
||||
"description": "Judul yang menggugah & menarik (maksimal 50 karakter)"
|
||||
},
|
||||
"description": {
|
||||
"type": "STRING",
|
||||
"description": "Deskripsi persuasif dengan CTA yang jelas (maksimal 100 karakter)"
|
||||
}
|
||||
},
|
||||
required: ["title", "description"]
|
||||
};
|
||||
const payload = {
|
||||
contents: [{
|
||||
role: "user",
|
||||
parts: [{ text: `Data aktivitas pengguna untuk dianalisis:\n${promptString}` }]
|
||||
}],
|
||||
systemInstruction: { parts: [{ text: systemInstruction }] },
|
||||
generationConfig: {
|
||||
responseMimeType: "application/json",
|
||||
responseSchema: responseSchema,
|
||||
temperature: 0.8,
|
||||
maxOutputTokens: 2048,
|
||||
}
|
||||
};
|
||||
|
||||
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const response = await axios.post(API_URL, payload, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const result = response.data;
|
||||
|
||||
const jsonText = result.candidates?.[0]?.content?.parts?.[0]?.text;
|
||||
|
||||
if (!jsonText) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Gemini API returned an unexpected response structure or no text content.",
|
||||
raw: result
|
||||
};
|
||||
}
|
||||
|
||||
let responseJson;
|
||||
try {
|
||||
responseJson = JSON.parse(jsonText);
|
||||
} catch (parseErr) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Failed to parse AI response as JSON.",
|
||||
raw: jsonText
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
typeof responseJson !== "object" ||
|
||||
!responseJson.title ||
|
||||
!responseJson.description ||
|
||||
typeof responseJson.title !== "string" ||
|
||||
typeof responseJson.description !== "string"
|
||||
) {
|
||||
return {
|
||||
error: true,
|
||||
message: "AI response does not match expected schema.",
|
||||
raw: responseJson
|
||||
};
|
||||
}
|
||||
|
||||
if (responseJson.title.length > 50 || responseJson.description.length > 100) {
|
||||
return {
|
||||
error: true,
|
||||
message: "AI response fields exceed max length.",
|
||||
raw: responseJson
|
||||
};
|
||||
}
|
||||
|
||||
return responseJson;
|
||||
} catch (error) {
|
||||
const isLastAttempt = attempt === MAX_RETRIES - 1;
|
||||
const status = error.response?.status;
|
||||
|
||||
const shouldRetry = !isLastAttempt && (
|
||||
!status ||
|
||||
status === 429 ||
|
||||
status === 503 ||
|
||||
status >= 500
|
||||
);
|
||||
|
||||
if (shouldRetry) {
|
||||
const exponentialDelay = Math.min(
|
||||
INITIAL_RETRY_DELAY * Math.pow(2, attempt),
|
||||
MAX_RETRY_DELAY
|
||||
);
|
||||
const jitter = Math.random() * 1000;
|
||||
const delay = exponentialDelay + jitter;
|
||||
|
||||
logger.warn(`AI Service: Attempt ${attempt + 1}/${MAX_RETRIES} failed${status ? ` (HTTP ${status})` : ''}. Retrying in ${Math.round(delay / 1000)}s...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
continue;
|
||||
}
|
||||
|
||||
const errorMessage = status
|
||||
? `API call failed with status ${status}: ${error.response?.data?.error?.message || 'Unknown error'}`
|
||||
: `Network error: ${error.message}`;
|
||||
|
||||
logger.error(`AI Service: Failed after ${attempt + 1} attempts: ${errorMessage}`);
|
||||
|
||||
return {
|
||||
error: true,
|
||||
message: errorMessage,
|
||||
raw: error.response?.data || error.message,
|
||||
attempts: attempt + 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: true,
|
||||
message: "Exhausted all retries without a successful API response.",
|
||||
raw: null,
|
||||
attempts: MAX_RETRIES
|
||||
};
|
||||
};
|
||||
|
||||
exports.predictOptimalDeliveryTime = async (userActivityData) => {
|
||||
const activityString = JSON.stringify(userActivityData, null, 2);
|
||||
|
||||
const systemInstruction = `
|
||||
Anda adalah AI specialist yang menganalisis pola perilaku pengguna untuk menentukan waktu OPTIMAL pengiriman notifikasi.
|
||||
|
||||
TUGAS ANDA:
|
||||
1. Analisis data aktivitas pengguna (waktu login, frekuensi penggunaan, pola harian/mingguan)
|
||||
2. Identifikasi jam-jam PEAK engagement pengguna (kapan user paling aktif)
|
||||
3. Prediksi waktu terbaik untuk mengirim notifikasi agar dibaca dan di-klik
|
||||
4. Pertimbangkan:
|
||||
- Pola aktivitas historis (jam berapa user biasa online)
|
||||
- Hari dalam seminggu (weekday vs weekend berbeda)
|
||||
- Interval ideal antar notifikasi (jangan spam)
|
||||
- Timezone dan kebiasaan lokal (pagi, siang, sore, malam)
|
||||
|
||||
5. Berikan rekomendasi waktu dalam format ISO 8601 dengan timezone
|
||||
6. Sertakan confidence score (0-100) dan reasoning singkat
|
||||
|
||||
ATURAN PENTING:
|
||||
- Jika user aktif pagi (06:00-10:00) → kirim pagi hari berikutnya
|
||||
- Jika user aktif siang (11:00-15:00) → kirim saat lunch break
|
||||
- Jika user aktif sore/malam (16:00-22:00) → kirim sore hari
|
||||
- Jika tidak ada pola jelas → default ke jam 09:00 atau 19:00 (high engagement hours)
|
||||
- Minimum delay: 30 menit dari sekarang
|
||||
- Maximum delay: 24 jam dari sekarang
|
||||
- Hindari jam tidur (23:00-05:00)
|
||||
|
||||
OUTPUT harus JSON murni tanpa markdown atau teks tambahan.
|
||||
`;
|
||||
|
||||
const currentTime = new Date();
|
||||
const responseSchema = {
|
||||
type: "OBJECT",
|
||||
properties: {
|
||||
"optimalDeliveryTime": {
|
||||
"type": "STRING",
|
||||
"description": "Waktu optimal pengiriman dalam ISO 8601 format (contoh: 2024-12-08T19:00:00+07:00)"
|
||||
},
|
||||
"confidenceScore": {
|
||||
"type": "NUMBER",
|
||||
"description": "Confidence score prediksi (0-100)"
|
||||
},
|
||||
"reasoning": {
|
||||
"type": "STRING",
|
||||
"description": "Penjelasan singkat mengapa waktu ini dipilih (maks 150 karakter)"
|
||||
},
|
||||
"userEngagementPattern": {
|
||||
"type": "STRING",
|
||||
"description": "Pola engagement user: 'morning_active', 'afternoon_active', 'evening_active', 'night_active', atau 'irregular'"
|
||||
},
|
||||
"delayMinutes": {
|
||||
"type": "NUMBER",
|
||||
"description": "Delay dalam menit dari sekarang (minimal 30, maksimal 1440)"
|
||||
}
|
||||
},
|
||||
required: ["optimalDeliveryTime", "confidenceScore", "reasoning", "userEngagementPattern", "delayMinutes"]
|
||||
};
|
||||
|
||||
const payload = {
|
||||
contents: [{
|
||||
role: "user",
|
||||
parts: [{
|
||||
text: `Waktu sekarang: ${currentTime.toISOString()}\n\nData aktivitas pengguna:\n${activityString}\n\nTentukan waktu OPTIMAL untuk mengirim notifikasi re-engagement.`
|
||||
}]
|
||||
}],
|
||||
systemInstruction: { parts: [{ text: systemInstruction }] },
|
||||
generationConfig: {
|
||||
responseMimeType: "application/json",
|
||||
responseSchema: responseSchema,
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 2048,
|
||||
}
|
||||
};
|
||||
|
||||
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const response = await axios.post(API_URL, payload, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
const result = response.data;
|
||||
const jsonText = result.candidates?.[0]?.content?.parts?.[0]?.text;
|
||||
|
||||
if (!jsonText) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Gemini API returned unexpected response for predictive timing.",
|
||||
raw: result
|
||||
};
|
||||
}
|
||||
|
||||
let responseJson;
|
||||
try {
|
||||
responseJson = JSON.parse(jsonText);
|
||||
} catch (parseErr) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Failed to parse predictive timing response as JSON.",
|
||||
raw: jsonText
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!responseJson.optimalDeliveryTime ||
|
||||
typeof responseJson.confidenceScore !== "number" ||
|
||||
!responseJson.reasoning ||
|
||||
!responseJson.userEngagementPattern ||
|
||||
typeof responseJson.delayMinutes !== "number"
|
||||
) {
|
||||
return {
|
||||
error: true,
|
||||
message: "Predictive timing response missing required fields.",
|
||||
raw: responseJson
|
||||
};
|
||||
}
|
||||
|
||||
if (responseJson.delayMinutes < 30 || responseJson.delayMinutes > 1440) {
|
||||
responseJson.delayMinutes = Math.max(30, Math.min(1440, responseJson.delayMinutes));
|
||||
}
|
||||
|
||||
const deliveryTime = new Date(responseJson.optimalDeliveryTime);
|
||||
if (deliveryTime <= currentTime) {
|
||||
const fallbackTime = new Date(currentTime.getTime() + 60 * 60 * 1000);
|
||||
responseJson.optimalDeliveryTime = fallbackTime.toISOString();
|
||||
responseJson.delayMinutes = 60;
|
||||
responseJson.reasoning = "Adjusted to future time (1 hour from now)";
|
||||
}
|
||||
|
||||
logger.info(`AI Predictive Timing: Scheduled for ${responseJson.optimalDeliveryTime} (${responseJson.delayMinutes} min delay, ${responseJson.confidenceScore}% confidence)`);
|
||||
|
||||
return responseJson;
|
||||
|
||||
} catch (error) {
|
||||
const isLastAttempt = attempt === MAX_RETRIES - 1;
|
||||
const status = error.response?.status;
|
||||
|
||||
const shouldRetry = !isLastAttempt && (
|
||||
!status ||
|
||||
status === 429 ||
|
||||
status === 503 ||
|
||||
status >= 500
|
||||
);
|
||||
|
||||
if (shouldRetry) {
|
||||
const delay = Math.min(
|
||||
INITIAL_RETRY_DELAY * Math.pow(2, attempt) + Math.random() * 1000,
|
||||
MAX_RETRY_DELAY
|
||||
);
|
||||
logger.warn(`Predictive Timing: Retry ${attempt + 1}/${MAX_RETRIES} in ${Math.round(delay/1000)}s`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.error(`Predictive Timing failed: ${error.message}`);
|
||||
|
||||
const fallbackTime = new Date(currentTime.getTime() + 2 * 60 * 60 * 1000);
|
||||
return {
|
||||
optimalDeliveryTime: fallbackTime.toISOString(),
|
||||
confidenceScore: 50,
|
||||
reasoning: "AI unavailable, using default 2-hour delay",
|
||||
userEngagementPattern: "unknown",
|
||||
delayMinutes: 120,
|
||||
aiError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackTime = new Date(currentTime.getTime() + 2 * 60 * 60 * 1000);
|
||||
return {
|
||||
optimalDeliveryTime: fallbackTime.toISOString(),
|
||||
confidenceScore: 50,
|
||||
reasoning: "Fallback scheduling after retries exhausted",
|
||||
userEngagementPattern: "unknown",
|
||||
delayMinutes: 120,
|
||||
aiError: true
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// LIBRARIES
|
||||
const cron = require("node-cron");
|
||||
|
||||
// SERVICES
|
||||
const logger = require("../services/logger.services");
|
||||
const { localTime } = require('../services/time.services.js');
|
||||
const { checkCampaign, sendCampaign } = require("../controllers/campaign.controller");
|
||||
const { processActivitiesAndSendNotifications, updateDailyAnalytics } = require("../services/notification.services");
|
||||
|
||||
// CONTROLLERS
|
||||
|
||||
function initializeCronJobs() {
|
||||
cron.schedule("* * * * *", async () => {
|
||||
logger.info("Running cron job to handle campaign");
|
||||
|
||||
try {
|
||||
const nowTime = localTime(new Date());
|
||||
|
||||
const idCampaign = await checkCampaign(nowTime);
|
||||
|
||||
if (idCampaign) {
|
||||
await sendCampaign(idCampaign);
|
||||
logger.info(`Campaign ${idCampaign} sent successfully`);
|
||||
} else {
|
||||
logger.info("No campaign to send at this time");
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Error handling campaign in cron job", { error: err });
|
||||
}
|
||||
});
|
||||
|
||||
cron.schedule("*/5 * * * *", async () => {
|
||||
logger.info("Running cron job to analyze activities and send notifications");
|
||||
|
||||
try {
|
||||
const result = await processActivitiesAndSendNotifications({
|
||||
timeRangeMinutes: 60,
|
||||
minActivityCount: 5
|
||||
});
|
||||
|
||||
logger.info("Activity notification job completed", {
|
||||
totalUsersProcessed: result.totalUsersProcessed,
|
||||
notificationsSent: result.notificationsSent,
|
||||
notificationsSkipped: result.notificationsSkipped,
|
||||
errors: result.errors,
|
||||
durationMs: result.processingTimeMs
|
||||
});
|
||||
|
||||
if (result.errorDetails.length > 0) {
|
||||
logger.warn("Some errors occurred during notification processing:", result.errorDetails);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Error handling activity notification cron job", { error: err });
|
||||
}
|
||||
});
|
||||
|
||||
cron.schedule("0 2 * * *", async () => {
|
||||
logger.info("Running cron job to update AI notification analytics");
|
||||
|
||||
try {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
|
||||
const result = await updateDailyAnalytics(yesterday);
|
||||
|
||||
logger.info("AI notification analytics updated successfully", {
|
||||
date: result.date.toISOString().split('T')[0],
|
||||
stats: result.stats
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error("Error updating AI notification analytics in cron job", { error: err });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initializeCronJobs
|
||||
};
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// LIBRARIES
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { minioClient } = require("./minio.services.js");
|
||||
const mime = require("mime-types");
|
||||
const path = require("path");
|
||||
|
||||
// ESSENTIALS
|
||||
exports.upload = async (bucketName, folderName, mimetype, file) => {
|
||||
try {
|
||||
if (!file?.length) {
|
||||
throw new Error("Invalid file!");
|
||||
}
|
||||
|
||||
const fileContent = file[0];
|
||||
|
||||
const ext = mime.extension(mimetype) || path.extname(fileContent.originalname);
|
||||
|
||||
const fileName = `${uuidv4()}.${ext}`;
|
||||
const fullPath = `${folderName}/${fileName}`;
|
||||
|
||||
const bucketExists = await minioClient.bucketExists(bucketName);
|
||||
|
||||
if (!bucketExists) {
|
||||
await minioClient.makeBucket(bucketName, 'us-east-1');
|
||||
|
||||
const bucketPolicy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: "*",
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${bucketName}/*`],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await minioClient.setBucketPolicy(bucketName, JSON.stringify(bucketPolicy));
|
||||
}
|
||||
|
||||
await minioClient.putObject(bucketName, fullPath, fileContent.buffer, {
|
||||
"Content-Type": mimetype,
|
||||
});
|
||||
|
||||
const fileUrl = `https://${minioClient.host}:${minioClient.port}/${bucketName}/${fullPath}`;
|
||||
|
||||
return [fileUrl, fileName];
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
exports.delete = async (bucketName, folderName, fileName) => {
|
||||
try {
|
||||
if (!fileName) {
|
||||
throw new Error("Invalid file name");
|
||||
}
|
||||
|
||||
const fullPath = `${folderName}/${fileName}`;
|
||||
|
||||
await minioClient.removeObject(bucketName, fullPath);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
const admin = require("firebase-admin");
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
const logger = require("./logger.services");
|
||||
const { localTime } = require("./time.services.js");
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
async function sendNotification(token, title, body, data = {}, imageUrl = null) {
|
||||
if (!token) throw new Error("Missing FCM token");
|
||||
|
||||
const stringData = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
stringData[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
stringData.image = imageUrl;
|
||||
}
|
||||
|
||||
const message = {
|
||||
notification: { title, body },
|
||||
data: stringData,
|
||||
token,
|
||||
};
|
||||
|
||||
if (imageUrl) {
|
||||
message.android = {
|
||||
notification: {
|
||||
imageUrl: imageUrl
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return await admin.messaging().send(message);
|
||||
}
|
||||
|
||||
async function sendToTopic(title, body, data = {}, imageUrl = null) {
|
||||
if (!title || !body) throw new Error("Missing notification title or body");
|
||||
|
||||
const tokens = await prisma.usersToken.findMany({
|
||||
select: {
|
||||
UserID_UT: true,
|
||||
Token_UT: true
|
||||
}
|
||||
});
|
||||
|
||||
const tokenList = tokens.filter(t => t.Token_UT);
|
||||
|
||||
if (tokenList.length === 0) {
|
||||
return {
|
||||
successCount: 0,
|
||||
failureCount: 0,
|
||||
targetUsers: 0,
|
||||
deliveryDetails: [],
|
||||
message: "No tokens registered"
|
||||
};
|
||||
}
|
||||
|
||||
const stringData = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
stringData[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
stringData.image = imageUrl;
|
||||
}
|
||||
|
||||
const chunkSize = 500;
|
||||
let successTotal = 0;
|
||||
let failureTotal = 0;
|
||||
const deliveryDetails = [];
|
||||
|
||||
for (let i = 0; i < tokenList.length; i += chunkSize) {
|
||||
const tokensChunk = tokenList.slice(i, i + chunkSize);
|
||||
const tokensOnly = tokensChunk.map(t => t.Token_UT);
|
||||
|
||||
const message = {
|
||||
notification: { title, body },
|
||||
data: stringData,
|
||||
tokens: tokensOnly,
|
||||
};
|
||||
|
||||
if (imageUrl) {
|
||||
message.android = {
|
||||
notification: {
|
||||
imageUrl: imageUrl
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const response = await admin.messaging().sendEachForMulticast(message);
|
||||
successTotal += response.successCount;
|
||||
failureTotal += response.failureCount;
|
||||
|
||||
response.responses.forEach((resp, index) => {
|
||||
const userToken = tokensChunk[index];
|
||||
deliveryDetails.push({
|
||||
userID: userToken.UserID_UT,
|
||||
token: userToken.Token_UT,
|
||||
success: resp.success,
|
||||
messageId: resp.messageId || null,
|
||||
error: resp.error ? {
|
||||
code: resp.error.code,
|
||||
message: resp.error.message
|
||||
} : null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Sent ${successTotal} notifications successfully and ${failureTotal} notifications failed`);
|
||||
|
||||
return {
|
||||
successCount: successTotal,
|
||||
failureCount: failureTotal,
|
||||
targetUsers: tokenList.length,
|
||||
deliveryRate: tokenList.length > 0 ? ((successTotal / tokenList.length) * 100).toFixed(2) : 0,
|
||||
deliveryDetails: deliveryDetails
|
||||
};
|
||||
}
|
||||
|
||||
async function sendCampaignWithTracking(campaignID, title, body, data = {}, imageUrl = null) {
|
||||
try {
|
||||
const sentAt = new Date();
|
||||
|
||||
const users = await prisma.usersToken.findMany({
|
||||
select: {
|
||||
UserID_UT: true,
|
||||
Token_UT: true
|
||||
}
|
||||
});
|
||||
|
||||
const validUsers = users.filter(u => u.Token_UT);
|
||||
|
||||
if (validUsers.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: "No valid tokens found",
|
||||
targetUsers: 0,
|
||||
successCount: 0,
|
||||
failureCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.campaignDelivery.createMany({
|
||||
data: validUsers.map(user => ({
|
||||
Campaign_CD: campaignID,
|
||||
UserID_CD: user.UserID_UT,
|
||||
Token_CD: user.Token_UT,
|
||||
Status_CD: 'pending',
|
||||
CreatedAt_CD: sentAt
|
||||
}))
|
||||
});
|
||||
|
||||
const result = await sendToTopic(title, body, { ...data, campaignID }, imageUrl);
|
||||
|
||||
for (const delivery of result.deliveryDetails) {
|
||||
const updateData = {
|
||||
SentAt_CD: sentAt,
|
||||
UpdatedAt_CD: localTime(new Date())
|
||||
};
|
||||
|
||||
if (delivery.success) {
|
||||
updateData.Status_CD = 'delivered';
|
||||
updateData.DeliveredAt_CD = localTime(new Date());
|
||||
updateData.ResponseData_CD = JSON.stringify({ messageId: delivery.messageId });
|
||||
} else {
|
||||
updateData.Status_CD = 'failed';
|
||||
updateData.FailedAt_CD = localTime(new Date());
|
||||
updateData.ErrorMessage_CD = delivery.error ? delivery.error.message : 'Unknown error';
|
||||
updateData.ResponseData_CD = JSON.stringify(delivery.error);
|
||||
}
|
||||
|
||||
await prisma.campaignDelivery.updateMany({
|
||||
where: {
|
||||
Campaign_CD: campaignID,
|
||||
UserID_CD: delivery.userID
|
||||
},
|
||||
data: updateData
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
targetUsers: validUsers.length,
|
||||
successCount: result.successCount,
|
||||
failureCount: result.failureCount,
|
||||
deliveryRate: result.deliveryRate,
|
||||
sentAt: sentAt
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error in sendCampaignWithTracking: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendNotification,
|
||||
sendToTopic,
|
||||
sendCampaignWithTracking,
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
const { createLogger, format, transports } = require("winston");
|
||||
|
||||
const logger = createLogger({
|
||||
level: "info",
|
||||
format: format.combine(
|
||||
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
format.colorize(),
|
||||
format.printf(({ timestamp, level, message, ...meta }) => {
|
||||
return `[${timestamp}] ${level}: ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ""}`;
|
||||
})
|
||||
),
|
||||
transports: [
|
||||
new transports.Console(),
|
||||
new transports.File({ filename: "app.log" })
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = logger;
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
// ENVIRONMENTS
|
||||
require('dotenv').config();
|
||||
|
||||
// SERVICES
|
||||
const logger = require("../services/logger.services.js");
|
||||
|
||||
// CONSTANTS
|
||||
const { MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY } = process.env;
|
||||
|
||||
const Minio = require("minio");
|
||||
|
||||
const minioClient = new Minio.Client({
|
||||
endPoint: MINIO_ENDPOINT,
|
||||
port: 443,
|
||||
useSSL: true,
|
||||
accessKey: MINIO_ACCESS_KEY,
|
||||
secretKey: MINIO_SECRET_KEY,
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a new bucket and set default public policy
|
||||
*/
|
||||
async function createBucket(bucketName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
minioClient.makeBucket(bucketName, "", function (err) {
|
||||
if (err) return reject(err);
|
||||
logger.info(`Bucket "${bucketName}" created successfully`);
|
||||
|
||||
const policy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: { AWS: "*" },
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${bucketName}/*`],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
minioClient.setBucketPolicy(bucketName, JSON.stringify(policy), (err) => {
|
||||
if (err) return reject(err);
|
||||
logger.info(`Public policy applied to bucket "${bucketName}"`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing bucket (must be empty)
|
||||
*/
|
||||
async function deleteBucket(bucketName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
minioClient.removeBucket(bucketName, function (err) {
|
||||
if (err) return reject(err);
|
||||
logger.info(`Bucket "${bucketName}" deleted successfully`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a bucket (create new, copy objects, delete old)
|
||||
*/
|
||||
async function renameBucket(oldBucket, newBucket) {
|
||||
try {
|
||||
await createBucket(newBucket);
|
||||
|
||||
const objects = [];
|
||||
const stream = minioClient.listObjects(oldBucket, "", true);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
stream.on("data", obj => objects.push(obj));
|
||||
stream.on("end", resolve);
|
||||
stream.on("error", reject);
|
||||
});
|
||||
|
||||
for (const obj of objects) {
|
||||
await new Promise((resolve, reject) => {
|
||||
minioClient.copyObject(
|
||||
newBucket,
|
||||
obj.name,
|
||||
`/${oldBucket}/${obj.name}`,
|
||||
function (err, etag) {
|
||||
if (err) return reject(err);
|
||||
logger.info(`Copied ${obj.name} to ${newBucket}`);
|
||||
resolve(etag);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (objects.length > 0) {
|
||||
await minioClient.removeObjects(
|
||||
oldBucket,
|
||||
objects.map(o => o.name)
|
||||
);
|
||||
}
|
||||
|
||||
await deleteBucket(oldBucket);
|
||||
|
||||
logger.info(`Bucket renamed from "${oldBucket}" to "${newBucket}"`);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all buckets
|
||||
*/
|
||||
async function getAllBuckets() {
|
||||
return new Promise((resolve, reject) => {
|
||||
minioClient.listBuckets((err, buckets) => {
|
||||
if (err) return reject(err);
|
||||
resolve(buckets);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bucket policy (public / private)
|
||||
*/
|
||||
async function setBucketPolicy(bucketName, policyType) {
|
||||
let policy = "";
|
||||
|
||||
if (policyType === "public") {
|
||||
policy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: { AWS: ["*"] },
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
||||
}
|
||||
]
|
||||
};
|
||||
} else if (policyType === "private") {
|
||||
policy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: []
|
||||
};
|
||||
} else {
|
||||
throw new Error("Invalid policy type, use 'public' or 'private'");
|
||||
}
|
||||
|
||||
await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
minioClient,
|
||||
createBucket,
|
||||
deleteBucket,
|
||||
renameBucket,
|
||||
getAllBuckets,
|
||||
setBucketPolicy
|
||||
};
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
/**
|
||||
* Notification Scheduler Service
|
||||
* Manages scheduled AI notifications with predictive timing
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
const prisma = new CMSClient();
|
||||
const logger = require('./logger.services');
|
||||
const { localTime } = require('./time.services');
|
||||
const { predictOptimalDeliveryTime } = require('./ai.services');
|
||||
|
||||
/**
|
||||
* Create a scheduled notification with AI-predicted optimal delivery time
|
||||
* @param {Object} params - Notification parameters
|
||||
* @param {string} params.userID - User ID
|
||||
* @param {Array} params.recentActivities - Recent user activities for pattern analysis
|
||||
* @param {Object} params.notificationContent - The generated notification {title, description}
|
||||
* @param {number} params.analyzedActivityCount - Number of activities analyzed
|
||||
* @param {string} params.activityTypes - Comma-separated activity types
|
||||
* @param {number} params.activityTimeRange - Time range in minutes
|
||||
* @param {string} params.aiModel - AI model used
|
||||
* @param {number} params.processingTime - Time taken to process (ms)
|
||||
* @returns {Promise<Object>} Created scheduled notification record
|
||||
*/
|
||||
exports.createScheduledNotification = async (params) => {
|
||||
try {
|
||||
const {
|
||||
userID,
|
||||
recentActivities,
|
||||
notificationContent,
|
||||
analyzedActivityCount,
|
||||
activityTypes,
|
||||
activityTimeRange,
|
||||
aiModel,
|
||||
processingTime
|
||||
} = params;
|
||||
|
||||
const userToken = await prisma.usersToken.findFirst({
|
||||
where: { UserID_UT: userID }
|
||||
});
|
||||
|
||||
if (!userToken) {
|
||||
throw new Error(`User token not found for userID: ${userID}`);
|
||||
}
|
||||
|
||||
const historicalActivities = await prisma.usersActivity.findMany({
|
||||
where: {
|
||||
UUID_UT: userToken.UUID_UT,
|
||||
CreatedAt_UA: {
|
||||
gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_UA: 'desc'
|
||||
},
|
||||
take: 50
|
||||
});
|
||||
|
||||
const activityPattern = {
|
||||
userId: userID,
|
||||
currentTime: new Date().toISOString(),
|
||||
recentActivities: recentActivities.map(a => ({
|
||||
type: a.ActivityType_UA || a.type,
|
||||
createdAt: a.CreatedAt_UA || a.timestamp,
|
||||
params: a.Params_UA || a.params
|
||||
})),
|
||||
historicalPattern: historicalActivities.map(a => ({
|
||||
type: a.ActivityType_UA,
|
||||
hour: new Date(a.CreatedAt_UA).getHours(),
|
||||
dayOfWeek: new Date(a.CreatedAt_UA).getDay(),
|
||||
createdAt: a.CreatedAt_UA
|
||||
})),
|
||||
stats: {
|
||||
totalActivities: historicalActivities.length,
|
||||
uniqueDays: [...new Set(historicalActivities.map(a =>
|
||||
new Date(a.CreatedAt_UA).toISOString().split('T')[0]
|
||||
))].length,
|
||||
avgActivitiesPerDay: historicalActivities.length / 7
|
||||
}
|
||||
};
|
||||
|
||||
logger.info(`Requesting AI predictive timing for user ${userID}...`);
|
||||
let timingPrediction = await predictOptimalDeliveryTime(activityPattern);
|
||||
|
||||
if (timingPrediction.error || !timingPrediction.optimalDeliveryTime) {
|
||||
logger.error(`AI timing prediction failed, using fallback`, timingPrediction);
|
||||
const now = new Date();
|
||||
const fallbackTime = new Date(now.getTime() + 2 * 60 * 60 * 1000); // 2 hours from now
|
||||
timingPrediction = {
|
||||
optimalDeliveryTime: fallbackTime.toISOString(),
|
||||
confidenceScore: 50,
|
||||
reasoning: "AI prediction failed, using default 2-hour delay",
|
||||
userEngagementPattern: "unknown",
|
||||
delayMinutes: 120
|
||||
};
|
||||
}
|
||||
|
||||
const scheduledTime = new Date(timingPrediction.optimalDeliveryTime);
|
||||
const now = new Date();
|
||||
|
||||
if (isNaN(scheduledTime.getTime())) {
|
||||
logger.error(`Invalid scheduled time from AI prediction: ${timingPrediction.optimalDeliveryTime}`);
|
||||
const fallbackTime = new Date(now.getTime() + 2 * 60 * 60 * 1000);
|
||||
timingPrediction.optimalDeliveryTime = fallbackTime.toISOString();
|
||||
timingPrediction.reasoning = "Invalid time corrected to 2-hour delay";
|
||||
scheduledTime.setTime(fallbackTime.getTime());
|
||||
}
|
||||
|
||||
const scheduledNotification = await prisma.aINotification.create({
|
||||
data: {
|
||||
UserID_AIN: userID,
|
||||
AnalyzedActivities_AIN: analyzedActivityCount,
|
||||
ActivityTypes_AIN: activityTypes,
|
||||
GeneratedTitle_AIN: notificationContent.title,
|
||||
GeneratedDesc_AIN: notificationContent.description,
|
||||
SentStatus_AIN: 'scheduled',
|
||||
ScheduledAt_AIN: localTime(scheduledTime),
|
||||
PredictedConfidence_AIN: timingPrediction.confidenceScore,
|
||||
PredictionReasoning_AIN: timingPrediction.reasoning,
|
||||
UserEngagementPattern_AIN: timingPrediction.userEngagementPattern,
|
||||
DelayMinutes_AIN: timingPrediction.delayMinutes,
|
||||
ActivityTimeRange_AIN: activityTimeRange,
|
||||
AIModel_AIN: aiModel,
|
||||
ProcessingTime_AIN: processingTime,
|
||||
CreatedAt_AIN: localTime(now),
|
||||
UpdatedAt_AIN: localTime(now)
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(` Notification scheduled for ${userID} at ${scheduledTime.toISOString()} (${timingPrediction.delayMinutes} min delay, ${timingPrediction.confidenceScore}% confidence)`);
|
||||
logger.info(` Reason: ${timingPrediction.reasoning}`);
|
||||
logger.info(` Pattern: ${timingPrediction.userEngagementPattern}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
notificationID: scheduledNotification.UUID_AIN,
|
||||
scheduledFor: scheduledTime.toISOString(),
|
||||
delayMinutes: timingPrediction.delayMinutes,
|
||||
confidence: timingPrediction.confidenceScore,
|
||||
reasoning: timingPrediction.reasoning,
|
||||
pattern: timingPrediction.userEngagementPattern,
|
||||
notification: {
|
||||
title: notificationContent.title,
|
||||
description: notificationContent.description
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error creating scheduled notification: ${error.message}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all notifications that are due to be sent
|
||||
* @returns {Promise<Array>} Array of due notifications
|
||||
*/
|
||||
exports.getDueNotifications = async () => {
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
const dueNotifications = await prisma.aINotification.findMany({
|
||||
where: {
|
||||
SentStatus_AIN: 'scheduled',
|
||||
ScheduledAt_AIN: {
|
||||
lte: localTime(now)
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
ScheduledAt_AIN: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Found ${dueNotifications.length} notifications due for delivery`);
|
||||
return dueNotifications;
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error fetching due notifications: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update notification status after sending
|
||||
* @param {string} notificationID - UUID of the notification
|
||||
* @param {Object} updateData - Update data
|
||||
* @returns {Promise<Object>} Updated notification
|
||||
*/
|
||||
exports.updateNotificationStatus = async (notificationID, updateData) => {
|
||||
try {
|
||||
const updated = await prisma.aINotification.update({
|
||||
where: { UUID_AIN: notificationID },
|
||||
data: {
|
||||
...updateData,
|
||||
UpdatedAt_AIN: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
return updated;
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error updating notification ${notificationID}: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get scheduled notifications statistics
|
||||
* @returns {Promise<Object>} Statistics
|
||||
*/
|
||||
exports.getScheduledNotificationsStats = async () => {
|
||||
try {
|
||||
const [scheduled, pending, upcoming] = await Promise.all([
|
||||
prisma.aINotification.count({
|
||||
where: { SentStatus_AIN: 'scheduled' }
|
||||
}),
|
||||
prisma.aINotification.count({
|
||||
where: {
|
||||
SentStatus_AIN: 'scheduled',
|
||||
ScheduledAt_AIN: {
|
||||
lte: localTime(new Date())
|
||||
}
|
||||
}
|
||||
}),
|
||||
prisma.aINotification.findMany({
|
||||
where: {
|
||||
SentStatus_AIN: 'scheduled',
|
||||
ScheduledAt_AIN: {
|
||||
gte: localTime(new Date()),
|
||||
lte: localTime(new Date(Date.now() + 24 * 60 * 60 * 1000))
|
||||
}
|
||||
},
|
||||
select: {
|
||||
UUID_AIN: true,
|
||||
UserID_AIN: true,
|
||||
ScheduledAt_AIN: true,
|
||||
GeneratedTitle_AIN: true,
|
||||
PredictedConfidence_AIN: true,
|
||||
UserEngagementPattern_AIN: true
|
||||
},
|
||||
orderBy: {
|
||||
ScheduledAt_AIN: 'asc'
|
||||
},
|
||||
take: 10
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
totalScheduled: scheduled,
|
||||
pendingDelivery: pending,
|
||||
upcomingIn24Hours: upcoming.length,
|
||||
nextScheduled: upcoming
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error getting scheduled stats: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel a scheduled notification
|
||||
* @param {string} notificationID - UUID of the notification
|
||||
* @returns {Promise<Object>} Result
|
||||
*/
|
||||
exports.cancelScheduledNotification = async (notificationID) => {
|
||||
try {
|
||||
const updated = await prisma.aINotification.update({
|
||||
where: { UUID_AIN: notificationID },
|
||||
data: {
|
||||
SentStatus_AIN: 'cancelled',
|
||||
UpdatedAt_AIN: localTime(new Date())
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Notification ${notificationID} cancelled`);
|
||||
return { success: true, notification: updated };
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error cancelling notification ${notificationID}: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,639 @@
|
|||
// ENVIRONMENTS
|
||||
require("dotenv").config();
|
||||
|
||||
// DATABASE
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
|
||||
// SERVICES
|
||||
const logger = require("./logger.services");
|
||||
const { sendNotification } = require("./firebase.services");
|
||||
const { getAIResponse } = require("./ai.services");
|
||||
const { localTime } = require("./time.services");
|
||||
const { createScheduledNotification } = require("./notification-scheduler.services");
|
||||
|
||||
// INSTANCES
|
||||
const prisma = new CMSClient();
|
||||
|
||||
async function analyzeUserActivitiesForNotification(userID, timeRangeMinutes = 60) {
|
||||
const startTime = Date.now();
|
||||
let aiNotificationRecord = null;
|
||||
|
||||
try {
|
||||
const userToken = await prisma.usersToken.findUnique({
|
||||
where: { UserID_UT: userID },
|
||||
select: { UUID_UT: true, Token_UT: true }
|
||||
});
|
||||
|
||||
if (!userToken || !userToken.Token_UT) {
|
||||
logger.warn(`No valid token found for user ${userID}`);
|
||||
return {
|
||||
shouldNotify: false,
|
||||
reason: "No valid FCM token"
|
||||
};
|
||||
}
|
||||
|
||||
const timeRangeStart = new Date(Date.now() - timeRangeMinutes * 60 * 1000);
|
||||
|
||||
const activities = await prisma.usersActivity.findMany({
|
||||
where: {
|
||||
UUID_UT: userToken.UUID_UT,
|
||||
CreatedAt_UA: {
|
||||
gte: timeRangeStart
|
||||
},
|
||||
Processed_UA: false
|
||||
},
|
||||
select: {
|
||||
UUID_UA: true,
|
||||
ActivityType_UA: true,
|
||||
Params_UA: true,
|
||||
CreatedAt_UA: true
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_UA: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
if (activities.length === 0) {
|
||||
logger.info(`No activities found for user ${userID} in the last ${timeRangeMinutes} minutes`);
|
||||
return {
|
||||
shouldNotify: false,
|
||||
reason: "No activities found"
|
||||
};
|
||||
}
|
||||
|
||||
const activityTypes = [...new Set(activities.map(act => act.ActivityType_UA))];
|
||||
const activitySummary = {
|
||||
userID,
|
||||
totalActivities: activities.length,
|
||||
timeRange: `${timeRangeMinutes} minutes`,
|
||||
activities: activities.map(act => ({
|
||||
type: act.ActivityType_UA,
|
||||
params: act.Params_UA,
|
||||
timestamp: act.CreatedAt_UA
|
||||
}))
|
||||
};
|
||||
|
||||
logger.info(`Analyzing ${activities.length} activities for user ${userID}`);
|
||||
|
||||
const aiStartTime = Date.now();
|
||||
const aiAnalysis = await getAIResponse(activitySummary);
|
||||
const aiResponseTime = Date.now() - aiStartTime;
|
||||
|
||||
aiNotificationRecord = await prisma.aINotification.create({
|
||||
data: {
|
||||
UserID_AIN: userID,
|
||||
AnalyzedActivities_AIN: activities.length,
|
||||
ActivityTypes_AIN: JSON.stringify(activityTypes),
|
||||
GeneratedTitle_AIN: aiAnalysis?.title || "No title generated",
|
||||
GeneratedDesc_AIN: aiAnalysis?.description || "No description generated",
|
||||
ResponseTime_AIN: aiResponseTime,
|
||||
ActivityTimeRange_AIN: timeRangeMinutes,
|
||||
ProcessingTime_AIN: Date.now() - startTime
|
||||
}
|
||||
});
|
||||
|
||||
if (activities.length > 0) {
|
||||
await prisma.usersActivity.updateMany({
|
||||
where: {
|
||||
UUID_UA: { in: activities.map(act => act.UUID_UA) }
|
||||
},
|
||||
data: {
|
||||
Processed_UA: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (aiAnalysis && aiAnalysis.title && aiAnalysis.description) {
|
||||
return {
|
||||
shouldNotify: true,
|
||||
notification: {
|
||||
title: aiAnalysis.title,
|
||||
description: aiAnalysis.description,
|
||||
data: {
|
||||
source: 'activity-analyzer',
|
||||
activityCount: activities.length.toString(),
|
||||
lastActivityType: activities[0].ActivityType_UA,
|
||||
aiNotificationId: aiNotificationRecord.UUID_AIN
|
||||
}
|
||||
},
|
||||
reason: "AI recommendation",
|
||||
activityCount: activities.length,
|
||||
aiNotificationId: aiNotificationRecord.UUID_AIN,
|
||||
recentActivities: activities,
|
||||
processingTime: Date.now() - startTime
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.aINotification.update({
|
||||
where: { UUID_AIN: aiNotificationRecord.UUID_AIN },
|
||||
data: {
|
||||
SentStatus_AIN: "failed",
|
||||
ErrorMessage_AIN: "AI did not recommend notification",
|
||||
ProcessingTime_AIN: Date.now() - startTime
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
shouldNotify: false,
|
||||
reason: "AI did not recommend notification"
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error analyzing activities for user ${userID}:`, { error });
|
||||
|
||||
if (aiNotificationRecord) {
|
||||
await prisma.aINotification.update({
|
||||
where: { UUID_AIN: aiNotificationRecord.UUID_AIN },
|
||||
data: {
|
||||
SentStatus_AIN: "failed",
|
||||
ErrorMessage_AIN: error.message,
|
||||
ProcessingTime_AIN: Date.now() - startTime
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
return {
|
||||
shouldNotify: false,
|
||||
reason: `Error during analysis: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function sendNotificationToUser(userID, notification) {
|
||||
try {
|
||||
const { title, description, data = {} } = notification;
|
||||
|
||||
if (!title || !description) {
|
||||
throw new Error("Notification must have title and description");
|
||||
}
|
||||
|
||||
const userToken = await prisma.usersToken.findUnique({
|
||||
where: { UserID_UT: userID }
|
||||
});
|
||||
|
||||
if (!userToken || !userToken.Token_UT) {
|
||||
logger.warn(`No valid token found for user ${userID}`);
|
||||
|
||||
if (data.aiNotificationId) {
|
||||
await prisma.aINotification.update({
|
||||
where: { UUID_AIN: data.aiNotificationId },
|
||||
data: {
|
||||
SentStatus_AIN: "failed",
|
||||
ErrorMessage_AIN: "No valid FCM token",
|
||||
FailedAt_AIN: localTime(new Date())
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
userID,
|
||||
message: "No valid FCM token"
|
||||
};
|
||||
}
|
||||
|
||||
const messageId = await sendNotification(
|
||||
userToken.Token_UT,
|
||||
title,
|
||||
description,
|
||||
data
|
||||
);
|
||||
|
||||
if (data.aiNotificationId) {
|
||||
await prisma.aINotification.update({
|
||||
where: { UUID_AIN: data.aiNotificationId },
|
||||
data: {
|
||||
SentStatus_AIN: "sent",
|
||||
SentAt_AIN: localTime(new Date()),
|
||||
FCMMessageId_AIN: messageId
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
logger.info(`Notification sent to user ${userID}`, { messageId });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
userID,
|
||||
messageId,
|
||||
notification: { title, description }
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error sending notification to user ${userID}:`, { error });
|
||||
|
||||
if (notification?.data?.aiNotificationId) {
|
||||
await prisma.aINotification.update({
|
||||
where: { UUID_AIN: notification.data.aiNotificationId },
|
||||
data: {
|
||||
SentStatus_AIN: "failed",
|
||||
ErrorMessage_AIN: error.message,
|
||||
FailedAt_AIN: localTime(new Date())
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
userID,
|
||||
message: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function processActivitiesAndSendNotifications(options = {}) {
|
||||
const { timeRangeMinutes = 60, minActivityCount = 5 } = options;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
logger.info(`Starting activity analysis and notification process (timeRange: ${timeRangeMinutes}min, minCount: ${minActivityCount})`);
|
||||
|
||||
const timeRangeStart = new Date(Date.now() - timeRangeMinutes * 60 * 1000);
|
||||
|
||||
const usersWithActivities = await prisma.usersToken.findMany({
|
||||
where: {
|
||||
UsersActivity: {
|
||||
some: {
|
||||
CreatedAt_UA: {
|
||||
gte: timeRangeStart
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
select: {
|
||||
UserID_UT: true,
|
||||
Token_UT: true,
|
||||
_count: {
|
||||
select: { UsersActivity: true }
|
||||
}
|
||||
},
|
||||
distinct: ['UserID_UT']
|
||||
});
|
||||
|
||||
logger.info(`Found ${usersWithActivities.length} users with recent activities`);
|
||||
|
||||
const results = {
|
||||
totalUsersProcessed: 0,
|
||||
notificationsSent: 0,
|
||||
notificationsSkipped: 0,
|
||||
errors: 0,
|
||||
processedUsers: [],
|
||||
errorDetails: []
|
||||
};
|
||||
|
||||
for (const user of usersWithActivities) {
|
||||
try {
|
||||
if (!user.Token_UT) {
|
||||
logger.warn(`User ${user.UserID_UT} has no FCM token`);
|
||||
results.notificationsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const analysis = await analyzeUserActivitiesForNotification(
|
||||
user.UserID_UT,
|
||||
timeRangeMinutes
|
||||
);
|
||||
|
||||
results.totalUsersProcessed++;
|
||||
|
||||
if (analysis.shouldNotify) {
|
||||
try {
|
||||
const scheduledResult = await createScheduledNotification({
|
||||
userID: user.UserID_UT,
|
||||
recentActivities: analysis.recentActivities || [],
|
||||
notificationContent: {
|
||||
title: analysis.notification.title,
|
||||
description: analysis.notification.description
|
||||
},
|
||||
analyzedActivityCount: analysis.activityCount || 0,
|
||||
activityTypes: analysis.notification.data?.lastActivityType || 'unknown',
|
||||
activityTimeRange: timeRangeMinutes,
|
||||
aiModel: 'gemini-1.5-pro',
|
||||
processingTime: analysis.processingTime || 0
|
||||
});
|
||||
|
||||
results.notificationsSent++; // Count as scheduled
|
||||
results.processedUsers.push({
|
||||
userID: user.UserID_UT,
|
||||
status: 'scheduled',
|
||||
notification: analysis.notification,
|
||||
activityCount: analysis.activityCount,
|
||||
scheduledFor: scheduledResult.scheduledFor,
|
||||
delayMinutes: scheduledResult.delayMinutes,
|
||||
confidence: scheduledResult.confidence,
|
||||
reasoning: scheduledResult.reasoning,
|
||||
pattern: scheduledResult.pattern
|
||||
});
|
||||
|
||||
logger.info(`✅ Notification scheduled for ${user.UserID_UT} at ${scheduledResult.scheduledFor}`);
|
||||
} catch (scheduleError) {
|
||||
logger.error(`Failed to schedule notification for ${user.UserID_UT}:`, scheduleError);
|
||||
results.errors++;
|
||||
results.errorDetails.push({
|
||||
userID: user.UserID_UT,
|
||||
error: scheduleError.message
|
||||
});
|
||||
}
|
||||
} else {
|
||||
results.notificationsSkipped++;
|
||||
logger.info(`Notification skipped for user ${user.UserID_UT}: ${analysis.reason}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
results.errors++;
|
||||
results.errorDetails.push({
|
||||
userID: user.UserID_UT,
|
||||
error: error.message
|
||||
});
|
||||
logger.error(`Error processing user ${user.UserID_UT}:`, { error });
|
||||
}
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
results.processingTimeMs = duration;
|
||||
results.timestamp = new Date();
|
||||
|
||||
logger.info(`Activity analysis completed`, {
|
||||
totalUsersProcessed: results.totalUsersProcessed,
|
||||
notificationsSent: results.notificationsSent,
|
||||
notificationsSkipped: results.notificationsSkipped,
|
||||
errors: results.errors,
|
||||
durationMs: duration
|
||||
});
|
||||
|
||||
return results;
|
||||
|
||||
} catch (error) {
|
||||
logger.error("Error in processActivitiesAndSendNotifications:", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function manualTriggerNotification(userID, timeRangeMinutes = 60) {
|
||||
try {
|
||||
logger.info(`Manual trigger for user ${userID}`);
|
||||
|
||||
const analysis = await analyzeUserActivitiesForNotification(userID, timeRangeMinutes);
|
||||
|
||||
if (!analysis.shouldNotify) {
|
||||
return {
|
||||
success: false,
|
||||
userID,
|
||||
reason: analysis.reason
|
||||
};
|
||||
}
|
||||
|
||||
const result = await sendNotificationToUser(userID, analysis.notification);
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
...result
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Error in manual trigger for user ${userID}:`, { error });
|
||||
return {
|
||||
success: false,
|
||||
userID,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function updateDailyAnalytics(date = new Date()) {
|
||||
try {
|
||||
const targetDate = new Date(date);
|
||||
targetDate.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const nextDate = new Date(targetDate);
|
||||
nextDate.setDate(nextDate.getDate() + 1);
|
||||
|
||||
const dailyStats = await prisma.aINotification.groupBy({
|
||||
by: ['SentStatus_AIN'],
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: targetDate,
|
||||
lt: nextDate
|
||||
}
|
||||
},
|
||||
_count: {
|
||||
SentStatus_AIN: true
|
||||
},
|
||||
_avg: {
|
||||
ResponseTime_AIN: true,
|
||||
ProcessingTime_AIN: true
|
||||
}
|
||||
});
|
||||
|
||||
const activityAnalysis = await prisma.aINotification.findMany({
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: targetDate,
|
||||
lt: nextDate
|
||||
},
|
||||
ActivityTypes_AIN: {
|
||||
not: null
|
||||
}
|
||||
},
|
||||
select: {
|
||||
ActivityTypes_AIN: true,
|
||||
GeneratedTitle_AIN: true
|
||||
}
|
||||
});
|
||||
|
||||
let totalAnalyzed = 0;
|
||||
let totalSent = 0;
|
||||
let totalDelivered = 0;
|
||||
let totalFailed = 0;
|
||||
let avgResponseTime = 0;
|
||||
let avgProcessingTime = 0;
|
||||
|
||||
const statusCounts = {};
|
||||
dailyStats.forEach(stat => {
|
||||
const status = stat.SentStatus_AIN;
|
||||
const count = stat._count.SentStatus_AIN;
|
||||
statusCounts[status] = count;
|
||||
totalAnalyzed += count;
|
||||
|
||||
if (status === 'sent') totalSent += count;
|
||||
if (status === 'delivered') totalDelivered += count;
|
||||
if (status === 'failed') totalFailed += count;
|
||||
|
||||
if (stat._avg.ResponseTime_AIN) {
|
||||
avgResponseTime += stat._avg.ResponseTime_AIN * count;
|
||||
}
|
||||
if (stat._avg.ProcessingTime_AIN) {
|
||||
avgProcessingTime += stat._avg.ProcessingTime_AIN * count;
|
||||
}
|
||||
});
|
||||
|
||||
if (totalAnalyzed > 0) {
|
||||
avgResponseTime = avgResponseTime / totalAnalyzed;
|
||||
avgProcessingTime = avgProcessingTime / totalAnalyzed;
|
||||
}
|
||||
|
||||
const activityTypeCount = {};
|
||||
const titleCount = {};
|
||||
|
||||
activityAnalysis.forEach(record => {
|
||||
try {
|
||||
const activityTypes = JSON.parse(record.ActivityTypes_AIN || '[]');
|
||||
activityTypes.forEach(type => {
|
||||
activityTypeCount[type] = (activityTypeCount[type] || 0) + 1;
|
||||
});
|
||||
|
||||
const title = record.GeneratedTitle_AIN;
|
||||
if (title) {
|
||||
titleCount[title] = (titleCount[title] || 0) + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
});
|
||||
|
||||
const topActivityTypes = Object.entries(activityTypeCount)
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([type, count]) => ({ type, count }));
|
||||
|
||||
const popularTitles = Object.entries(titleCount)
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([title, count]) => ({ title, count }));
|
||||
|
||||
const deliveryRate = totalSent > 0 ? (totalDelivered / totalSent) * 100 : 0;
|
||||
|
||||
await prisma.aINotificationAnalytics.upsert({
|
||||
where: {
|
||||
Date_ANA: targetDate
|
||||
},
|
||||
update: {
|
||||
TotalAnalyzed_ANA: totalAnalyzed,
|
||||
TotalGenerated_ANA: totalAnalyzed,
|
||||
TotalSent_ANA: totalSent,
|
||||
TotalDelivered_ANA: totalDelivered,
|
||||
TotalFailed_ANA: totalFailed,
|
||||
DeliveryRate_ANA: deliveryRate,
|
||||
AvgResponseTime_ANA: avgResponseTime,
|
||||
AvgProcessingTime_ANA: avgProcessingTime,
|
||||
TopActivityTypes_ANA: topActivityTypes,
|
||||
PopularTitles_ANA: popularTitles,
|
||||
ErrorBreakdown_ANA: statusCounts,
|
||||
UpdatedAt_ANA: localTime(new Date())
|
||||
},
|
||||
create: {
|
||||
Date_ANA: targetDate,
|
||||
TotalAnalyzed_ANA: totalAnalyzed,
|
||||
TotalGenerated_ANA: totalAnalyzed,
|
||||
TotalSent_ANA: totalSent,
|
||||
TotalDelivered_ANA: totalDelivered,
|
||||
TotalFailed_ANA: totalFailed,
|
||||
DeliveryRate_ANA: deliveryRate,
|
||||
AvgResponseTime_ANA: avgResponseTime,
|
||||
AvgProcessingTime_ANA: avgProcessingTime,
|
||||
TopActivityTypes_ANA: topActivityTypes,
|
||||
PopularTitles_ANA: popularTitles,
|
||||
ErrorBreakdown_ANA: statusCounts
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`AI Notification analytics updated for ${targetDate.toISOString().split('T')[0]}`, {
|
||||
totalAnalyzed,
|
||||
totalSent,
|
||||
deliveryRate: `${deliveryRate.toFixed(2)}%`
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
date: targetDate,
|
||||
stats: {
|
||||
totalAnalyzed,
|
||||
totalSent,
|
||||
totalDelivered,
|
||||
totalFailed,
|
||||
deliveryRate,
|
||||
avgResponseTime,
|
||||
avgProcessingTime
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error updating daily analytics:', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAINotificationAnalytics(options = {}) {
|
||||
try {
|
||||
const {
|
||||
startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
||||
endDate = new Date(),
|
||||
limit = 30
|
||||
} = options;
|
||||
|
||||
const analytics = await prisma.aINotificationAnalytics.findMany({
|
||||
where: {
|
||||
Date_ANA: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
Date_ANA: 'desc'
|
||||
},
|
||||
take: limit
|
||||
});
|
||||
|
||||
const recentNotifications = await prisma.aINotification.findMany({
|
||||
where: {
|
||||
CreatedAt_AIN: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
CreatedAt_AIN: 'desc'
|
||||
},
|
||||
take: 50
|
||||
});
|
||||
|
||||
const totalAnalyzed = analytics.reduce((sum, a) => sum + a.TotalAnalyzed_ANA, 0);
|
||||
const totalSent = analytics.reduce((sum, a) => sum + a.TotalSent_ANA, 0);
|
||||
const totalDelivered = analytics.reduce((sum, a) => sum + a.TotalDelivered_ANA, 0);
|
||||
const totalFailed = analytics.reduce((sum, a) => sum + a.TotalFailed_ANA, 0);
|
||||
|
||||
const avgDeliveryRate = analytics.length > 0
|
||||
? analytics.reduce((sum, a) => sum + (a.DeliveryRate_ANA || 0), 0) / analytics.length
|
||||
: 0;
|
||||
|
||||
const avgResponseTime = analytics.length > 0
|
||||
? analytics.reduce((sum, a) => sum + (a.AvgResponseTime_ANA || 0), 0) / analytics.length
|
||||
: 0;
|
||||
|
||||
return {
|
||||
summary: {
|
||||
totalAnalyzed,
|
||||
totalSent,
|
||||
totalDelivered,
|
||||
totalFailed,
|
||||
avgDeliveryRate: `${avgDeliveryRate.toFixed(2)}%`,
|
||||
avgResponseTime: `${avgResponseTime.toFixed(0)}ms`,
|
||||
successRate: totalAnalyzed > 0 ? `${((totalSent / totalAnalyzed) * 100).toFixed(2)}%` : '0%'
|
||||
},
|
||||
analytics,
|
||||
recentNotifications: recentNotifications.slice(0, 20)
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error getting AI notification analytics:', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
analyzeUserActivitiesForNotification,
|
||||
sendNotificationToUser,
|
||||
processActivitiesAndSendNotifications,
|
||||
manualTriggerNotification,
|
||||
updateDailyAnalytics,
|
||||
getAINotificationAnalytics
|
||||
};
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
|
||||
require('dotenv').config();
|
||||
const cron = require('node-cron');
|
||||
const logger = require('./logger.services');
|
||||
const { getDueNotifications, updateNotificationStatus } = require('./notification-scheduler.services');
|
||||
const { sendNotification } = require('./firebase.services');
|
||||
const { PrismaClient: CMSClient } = require("../../prisma/clients/cms");
|
||||
const { localTime } = require('./time.services');
|
||||
|
||||
const prisma = new CMSClient();
|
||||
|
||||
/**
|
||||
* Process and send a single due notification
|
||||
* @param {Object} notification - The notification record
|
||||
*/
|
||||
async function processDueNotification(notification) {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const userToken = await prisma.usersToken.findFirst({
|
||||
where: { UserID_UT: notification.UserID_AIN }
|
||||
});
|
||||
|
||||
if (!userToken || !userToken.Token_UT) {
|
||||
logger.warn(`No valid FCM token for user ${notification.UserID_AIN}`);
|
||||
await updateNotificationStatus(notification.UUID_AIN, {
|
||||
SentStatus_AIN: 'failed',
|
||||
ErrorMessage_AIN: 'No valid FCM token',
|
||||
FailedAt_AIN: localTime(new Date())
|
||||
});
|
||||
return { success: false, reason: 'No FCM token' };
|
||||
}
|
||||
|
||||
const messageId = await sendNotification(
|
||||
userToken.Token_UT,
|
||||
notification.GeneratedTitle_AIN,
|
||||
notification.GeneratedDesc_AIN,
|
||||
{
|
||||
source: 'scheduled-notification',
|
||||
aiNotificationId: notification.UUID_AIN,
|
||||
scheduledDelivery: 'true',
|
||||
confidence: notification.PredictedConfidence_AIN?.toString() || '0',
|
||||
pattern: notification.UserEngagementPattern_AIN || 'unknown'
|
||||
}
|
||||
);
|
||||
|
||||
await updateNotificationStatus(notification.UUID_AIN, {
|
||||
SentStatus_AIN: 'sent',
|
||||
SentAt_AIN: localTime(new Date()),
|
||||
FCMMessageId_AIN: messageId,
|
||||
ProcessingTime_AIN: Date.now() - startTime
|
||||
});
|
||||
|
||||
logger.info(`Scheduled notification sent to ${notification.UserID_AIN}`, {
|
||||
notificationId: notification.UUID_AIN,
|
||||
messageId,
|
||||
scheduledFor: notification.ScheduledAt_AIN,
|
||||
confidence: notification.PredictedConfidence_AIN,
|
||||
pattern: notification.UserEngagementPattern_AIN
|
||||
});
|
||||
|
||||
return { success: true, messageId };
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`Failed to send scheduled notification ${notification.UUID_AIN}:`, error);
|
||||
|
||||
await updateNotificationStatus(notification.UUID_AIN, {
|
||||
SentStatus_AIN: 'failed',
|
||||
ErrorMessage_AIN: error.message,
|
||||
FailedAt_AIN: localTime(new Date()),
|
||||
ProcessingTime_AIN: Date.now() - startTime
|
||||
});
|
||||
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main processor function - checks and sends all due notifications
|
||||
*/
|
||||
async function processScheduledNotifications() {
|
||||
try {
|
||||
logger.info('🔄 Checking for due scheduled notifications...');
|
||||
|
||||
const dueNotifications = await getDueNotifications();
|
||||
|
||||
if (dueNotifications.length === 0) {
|
||||
logger.info('✓ No notifications due at this time');
|
||||
return {
|
||||
processed: 0,
|
||||
sent: 0,
|
||||
failed: 0
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(`Found ${dueNotifications.length} notifications to process`);
|
||||
|
||||
const results = {
|
||||
processed: 0,
|
||||
sent: 0,
|
||||
failed: 0,
|
||||
details: []
|
||||
};
|
||||
|
||||
for (const notification of dueNotifications) {
|
||||
results.processed++;
|
||||
|
||||
const result = await processDueNotification(notification);
|
||||
|
||||
if (result.success) {
|
||||
results.sent++;
|
||||
} else {
|
||||
results.failed++;
|
||||
}
|
||||
|
||||
results.details.push({
|
||||
notificationId: notification.UUID_AIN,
|
||||
userId: notification.UserID_AIN,
|
||||
success: result.success,
|
||||
messageId: result.messageId,
|
||||
error: result.error,
|
||||
reason: result.reason
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
logger.info(`Scheduled notification processing complete:`, {
|
||||
processed: results.processed,
|
||||
sent: results.sent,
|
||||
failed: results.failed
|
||||
});
|
||||
|
||||
return results;
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error in scheduled notification processor:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the cron job for scheduled notifications
|
||||
* Runs every 5 minutes
|
||||
*/
|
||||
function startScheduledNotificationCron() {
|
||||
const cronExpression = '*/5 * * * *';
|
||||
|
||||
const job = cron.schedule(cronExpression, async () => {
|
||||
logger.info('🕐 Scheduled notification cron job triggered');
|
||||
try {
|
||||
await processScheduledNotifications();
|
||||
} catch (error) {
|
||||
logger.error('Cron job error:', error);
|
||||
}
|
||||
}, {
|
||||
scheduled: true,
|
||||
timezone: process.env.TIMEZONE || "Asia/Jakarta"
|
||||
});
|
||||
|
||||
logger.info(`✅ Scheduled notification cron started (runs every 5 minutes)`);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual trigger for testing
|
||||
*/
|
||||
async function manualTriggerScheduledProcessor() {
|
||||
logger.info('🔧 Manual trigger: Processing scheduled notifications');
|
||||
return await processScheduledNotifications();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
startScheduledNotificationCron,
|
||||
processScheduledNotifications,
|
||||
manualTriggerScheduledProcessor,
|
||||
processDueNotification
|
||||
};
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
const { PrismaClient: MSGClient } = require("../../prisma/clients/msg");
|
||||
const logger = require("./logger.services.js");
|
||||
|
||||
const msgPrisma = new MSGClient();
|
||||
|
||||
class SocketService {
|
||||
constructor(io) {
|
||||
this.io = io;
|
||||
this.setupSocketHandlers();
|
||||
}
|
||||
|
||||
setupSocketHandlers() {
|
||||
this.io.on('connection', (socket) => {
|
||||
logger.info(`New client connected: ${socket.id}`);
|
||||
|
||||
socket.on('join_conversation', (data) => {
|
||||
this.handleJoinConversation(socket, data);
|
||||
});
|
||||
|
||||
socket.on('send_message', async (data) => {
|
||||
await this.handleSendMessage(socket, data);
|
||||
});
|
||||
|
||||
socket.on('typing_start', (data) => {
|
||||
this.handleTypingStart(socket, data);
|
||||
});
|
||||
|
||||
socket.on('typing_stop', (data) => {
|
||||
this.handleTypingStop(socket, data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
this.handleDisconnect(socket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleJoinConversation(socket, data) {
|
||||
const { conversationId, userId, userType } = data;
|
||||
socket.join(conversationId);
|
||||
socket.userId = userId;
|
||||
socket.userType = userType;
|
||||
logger.info(`${userType} ${userId} joined conversation ${conversationId}`);
|
||||
|
||||
socket.to(conversationId).emit('user_joined', {
|
||||
userId,
|
||||
userType,
|
||||
message: `${userType} joined the conversation`
|
||||
});
|
||||
}
|
||||
|
||||
async handleSendMessage(socket, data) {
|
||||
try {
|
||||
const { conversationId, message, senderId, senderType, messageType = 'text' } = data;
|
||||
|
||||
const savedMessage = await this.saveMessageToDatabase({
|
||||
conversationId,
|
||||
message,
|
||||
senderId,
|
||||
senderType,
|
||||
messageType
|
||||
});
|
||||
|
||||
this.io.to(conversationId).emit('receive_message', savedMessage);
|
||||
|
||||
logger.info(`Message sent in conversation ${conversationId} by ${senderType} ${senderId}`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error sending message:', error);
|
||||
socket.emit('message_error', {
|
||||
error: 'Failed to send message',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleTypingStart(socket, data) {
|
||||
const { conversationId, userId, userType } = data;
|
||||
socket.to(conversationId).emit('user_typing', { userId, userType });
|
||||
}
|
||||
|
||||
handleTypingStop(socket, data) {
|
||||
const { conversationId, userId, userType } = data;
|
||||
socket.to(conversationId).emit('user_stopped_typing', { userId, userType });
|
||||
}
|
||||
|
||||
handleDisconnect(socket) {
|
||||
logger.info(`Client disconnected: ${socket.id}`);
|
||||
|
||||
if (socket.userId && socket.userType) {
|
||||
// You might want to emit to specific rooms the user was in
|
||||
// This would require tracking which rooms the user was in
|
||||
}
|
||||
}
|
||||
|
||||
async saveMessageToDatabase(messageData) {
|
||||
try {
|
||||
const message = await msgPrisma.messages.create({
|
||||
data: {
|
||||
conversationId: messageData.conversationId,
|
||||
content: messageData.message,
|
||||
messageType: messageData.messageType || 'text',
|
||||
senderId: messageData.senderId,
|
||||
senderType: messageData.senderType,
|
||||
senderName: messageData.senderName,
|
||||
status: 'sent',
|
||||
metadata: messageData.metadata || {}
|
||||
},
|
||||
include: {
|
||||
conversation: true
|
||||
}
|
||||
});
|
||||
|
||||
await msgPrisma.conversations.update({
|
||||
where: { id: messageData.conversationId },
|
||||
data: { lastMessageAt: new Date() }
|
||||
});
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
conversationId: message.conversationId,
|
||||
message: message.content,
|
||||
senderId: message.senderId,
|
||||
senderType: message.senderType,
|
||||
senderName: message.senderName,
|
||||
messageType: message.messageType,
|
||||
timestamp: message.createdAt.toISOString(),
|
||||
status: message.status
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error saving message to database:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToConversation(conversationId, messageData) {
|
||||
this.io.to(conversationId).emit('receive_message', messageData);
|
||||
}
|
||||
|
||||
sendNotificationToUser(userId, notificationData) {
|
||||
this.io.emit('notification', {
|
||||
userId,
|
||||
...notificationData
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SocketService;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
exports.localTime = (date) => {
|
||||
const utcOffset = 7;
|
||||
const utcOffsetMs = utcOffset * 60 * 60 * 1000;
|
||||
const localDate = date.getTime() + utcOffsetMs;
|
||||
return new Date(localDate)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
const prefixes = {
|
||||
bucketName: 'cifosuperapps',
|
||||
};
|
||||
|
||||
export default prefixes;
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// LIBRARY IMPORT
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const bodyParser = require("body-parser");
|
||||
const compression = require("compression");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const multer = require("multer");
|
||||
const morgan = require("morgan");
|
||||
const path = require("path");
|
||||
const admin = require("firebase-admin");
|
||||
const http = require("http");
|
||||
const socketIo = require("socket.io");
|
||||
|
||||
// SERVICES
|
||||
const { initializeCronJobs } = require("./app/services/cronjob.services.js");
|
||||
const { startScheduledNotificationCron } = require("./app/services/scheduled-notification-processor.services.js");
|
||||
const logger = require("./app/services/logger.services.js");
|
||||
const SocketService = require("./app/services/socket.services.js");
|
||||
|
||||
// INITIALIZE CRON JOBS
|
||||
initializeCronJobs();
|
||||
|
||||
startScheduledNotificationCron();
|
||||
|
||||
// APP CONFIG
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const upload = multer({
|
||||
limits: { fileSize: 100 * 1024 * 1024 }
|
||||
});
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(morgan('combined'));
|
||||
|
||||
app.use(cors({ origin: true, credentials: true }));
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(bodyParser.urlencoded({ limit: '100mb', extended: true }));
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(compression());
|
||||
|
||||
app.use(upload.any());
|
||||
|
||||
// FIREBASE
|
||||
const serviceAccount = require(path.join(__dirname, "app/config/serviceAccountKey.json"));
|
||||
|
||||
if (!admin.apps.length) {
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount),
|
||||
storageBucket: "cifowallet.firebasestorage.app",
|
||||
});
|
||||
}
|
||||
|
||||
// SOCKET.IO SETUP
|
||||
const io = socketIo(server, {
|
||||
cors: {
|
||||
origin: ["http://localhost:3000", "http://localhost:3001", "*"],
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
},
|
||||
transports: ['websocket', 'polling']
|
||||
});
|
||||
|
||||
// INITIALIZE SOCKET SERVICE
|
||||
const socketService = new SocketService(io);
|
||||
|
||||
// ROUTES
|
||||
const adminRoutes = require("./app/routes/admin.route.js");
|
||||
const appRoutes = require("./app/routes/app.route.js");
|
||||
const cmsRoutes = require("./app/routes/cms.route.js");
|
||||
const bucketRoutes = require("./app/routes/bucket.route.js");
|
||||
const userRoutes = require("./app/routes/users.route.js");
|
||||
const cctvRoutes = require("./app/routes/cctv.route.js");
|
||||
const campaignRoutes = require("./app/routes/campaign.route.js");
|
||||
const activityRoutes = require("./app/routes/activity.route.js");
|
||||
const crashRoutes = require("./app/routes/crash.route.js");
|
||||
const aiNotificationRoutes = require("./app/routes/ai-notification.route.js");
|
||||
const menuRoutes = require("./app/routes/menu.route.js");
|
||||
const messagesRoutes = require("./app/routes/messages.route.js");
|
||||
|
||||
// ENDPOINTS
|
||||
const endpoints = [
|
||||
adminRoutes,
|
||||
appRoutes,
|
||||
cmsRoutes,
|
||||
bucketRoutes,
|
||||
userRoutes,
|
||||
cctvRoutes,
|
||||
campaignRoutes,
|
||||
activityRoutes,
|
||||
crashRoutes,
|
||||
aiNotificationRoutes,
|
||||
menuRoutes,
|
||||
messagesRoutes
|
||||
];
|
||||
|
||||
app.use(endpoints);
|
||||
|
||||
server.listen(port, () => {
|
||||
logger.info(`App is listening on port ${port}`);
|
||||
logger.info("Started at " + new Date().toLocaleString() + "");
|
||||
logger.info("Socket.IO server is running");
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "cifosuperapps-be",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon index.js",
|
||||
"migrateDev": "prisma migrate dev --schema=./prisma/schema.cms.prisma && prisma migrate dev --schema=./prisma/schema.msg.prisma",
|
||||
"migrateProd": "prisma migrate deploy --schema=./prisma/schema.cms.prisma && prisma migrate deploy --schema=./prisma/schema.msg.prisma",
|
||||
"generate": "prisma generate --schema=./prisma/schema.cms.prisma && prisma generate --schema=./prisma/schema.utility.prisma && prisma generate --schema=./prisma/schema.msg.prisma",
|
||||
"postinstall": "prisma generate --schema=./prisma/schema.cms.prisma && prisma generate --schema=./prisma/schema.utility.prisma && prisma generate --schema=./prisma/schema.msg.prisma"
|
||||
},
|
||||
"author": "TengkuAchmad",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.13.0",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"argon2": "^0.43.1",
|
||||
"axios": "^1.13.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"compression": "^1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"firebase-admin": "^13.5.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mime-types": "^3.0.1",
|
||||
"minio": "^8.0.5",
|
||||
"morgan": "^1.10.1",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^4.2.1",
|
||||
"nodemon": "^3.1.10",
|
||||
"openai": "^6.7.0",
|
||||
"prisma": "^6.13.0",
|
||||
"serverless-http": "^3.2.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.18.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./default"
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,620 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.13.0
|
||||
* Query Engine version: 361e86d0ea4987e9f53a565309b3eed797a6bcbd
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.13.0",
|
||||
engine: "361e86d0ea4987e9f53a565309b3eed797a6bcbd"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.AdminAccountScalarFieldEnum = {
|
||||
UUID_AA: 'UUID_AA',
|
||||
Username_AA: 'Username_AA',
|
||||
Email_AA: 'Email_AA',
|
||||
Password_AA: 'Password_AA',
|
||||
LastLogin_AA: 'LastLogin_AA',
|
||||
UpdatedAt_AA: 'UpdatedAt_AA',
|
||||
CreatedAt_AA: 'CreatedAt_AA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCredentialScalarFieldEnum = {
|
||||
UUID_AC: 'UUID_AC',
|
||||
CreatedAt_AC: 'CreatedAt_AC',
|
||||
TokenCredential_AC: 'TokenCredential_AC',
|
||||
UpdatedAt_AC: 'UpdatedAt_AC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppContentScalarFieldEnum = {
|
||||
UUID_APC: 'UUID_APC',
|
||||
Content_APC: 'Content_APC',
|
||||
CreatedAt_APC: 'CreatedAt_APC',
|
||||
Filename_APC: 'Filename_APC',
|
||||
Title_APC: 'Title_APC',
|
||||
UpdatedAt_APC: 'UpdatedAt_APC',
|
||||
Url_APC: 'Url_APC',
|
||||
Type_APC: 'Type_APC',
|
||||
CorpType_APC: 'CorpType_APC',
|
||||
TargetUrl_APC: 'TargetUrl_APC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCampaignScalarFieldEnum = {
|
||||
UUID_ACP: 'UUID_ACP',
|
||||
Title_ACP: 'Title_ACP',
|
||||
Content_ACP: 'Content_ACP',
|
||||
Date_ACP: 'Date_ACP',
|
||||
Status_ACP: 'Status_ACP',
|
||||
UpdatedAt_ACP: 'UpdatedAt_ACP',
|
||||
CreatedAt_ACP: 'CreatedAt_ACP',
|
||||
TargetUsers_ACP: 'TargetUsers_ACP',
|
||||
SentCount_ACP: 'SentCount_ACP',
|
||||
SuccessCount_ACP: 'SuccessCount_ACP',
|
||||
FailureCount_ACP: 'FailureCount_ACP',
|
||||
DeliveryRate_ACP: 'DeliveryRate_ACP',
|
||||
SentAt_ACP: 'SentAt_ACP',
|
||||
CompletedAt_ACP: 'CompletedAt_ACP',
|
||||
ErrorMessage_ACP: 'ErrorMessage_ACP',
|
||||
Data_ACP: 'Data_ACP',
|
||||
ImageUrl_ACP: 'ImageUrl_ACP'
|
||||
};
|
||||
|
||||
exports.Prisma.CampaignDeliveryScalarFieldEnum = {
|
||||
UUID_CD: 'UUID_CD',
|
||||
Campaign_CD: 'Campaign_CD',
|
||||
UserID_CD: 'UserID_CD',
|
||||
Token_CD: 'Token_CD',
|
||||
Status_CD: 'Status_CD',
|
||||
SentAt_CD: 'SentAt_CD',
|
||||
DeliveredAt_CD: 'DeliveredAt_CD',
|
||||
FailedAt_CD: 'FailedAt_CD',
|
||||
ErrorMessage_CD: 'ErrorMessage_CD',
|
||||
ResponseData_CD: 'ResponseData_CD',
|
||||
CreatedAt_CD: 'CreatedAt_CD',
|
||||
UpdatedAt_CD: 'UpdatedAt_CD'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersTokenScalarFieldEnum = {
|
||||
UUID_UT: 'UUID_UT',
|
||||
UserID_UT: 'UserID_UT',
|
||||
Token_UT: 'Token_UT',
|
||||
UpdatedAt_UT: 'UpdatedAt_UT',
|
||||
CreatedAt_UT: 'CreatedAt_UT'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersActivityScalarFieldEnum = {
|
||||
UUID_UA: 'UUID_UA',
|
||||
UUID_UT: 'UUID_UT',
|
||||
ActivityType_UA: 'ActivityType_UA',
|
||||
Params_UA: 'Params_UA',
|
||||
NotifyAt_UA: 'NotifyAt_UA',
|
||||
UpdatedAt_UA: 'UpdatedAt_UA',
|
||||
CreatedAt_UA: 'CreatedAt_UA',
|
||||
Processed_UA: 'Processed_UA'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashReportScalarFieldEnum = {
|
||||
UUID_CR: 'UUID_CR',
|
||||
AppId_CR: 'AppId_CR',
|
||||
AppVersion_CR: 'AppVersion_CR',
|
||||
BuildVersion_CR: 'BuildVersion_CR',
|
||||
CrashId_CR: 'CrashId_CR',
|
||||
SessionId_CR: 'SessionId_CR',
|
||||
UserId_CR: 'UserId_CR',
|
||||
CrashType_CR: 'CrashType_CR',
|
||||
ExceptionName_CR: 'ExceptionName_CR',
|
||||
ExceptionReason_CR: 'ExceptionReason_CR',
|
||||
StackTrace_CR: 'StackTrace_CR',
|
||||
ThreadName_CR: 'ThreadName_CR',
|
||||
IsFatal_CR: 'IsFatal_CR',
|
||||
Severity_CR: 'Severity_CR',
|
||||
Status_CR: 'Status_CR',
|
||||
DeviceModel_CR: 'DeviceModel_CR',
|
||||
DeviceBrand_CR: 'DeviceBrand_CR',
|
||||
OSName_CR: 'OSName_CR',
|
||||
OSVersion_CR: 'OSVersion_CR',
|
||||
Architecture_CR: 'Architecture_CR',
|
||||
AvailableRam_CR: 'AvailableRam_CR',
|
||||
TotalRam_CR: 'TotalRam_CR',
|
||||
AvailableDisk_CR: 'AvailableDisk_CR',
|
||||
TotalDisk_CR: 'TotalDisk_CR',
|
||||
BatteryLevel_CR: 'BatteryLevel_CR',
|
||||
IsRooted_CR: 'IsRooted_CR',
|
||||
IsDebugger_CR: 'IsDebugger_CR',
|
||||
NetworkType_CR: 'NetworkType_CR',
|
||||
Breadcrumbs_CR: 'Breadcrumbs_CR',
|
||||
CustomData_CR: 'CustomData_CR',
|
||||
Logs_CR: 'Logs_CR',
|
||||
CrashCount_CR: 'CrashCount_CR',
|
||||
FirstOccurred_CR: 'FirstOccurred_CR',
|
||||
LastOccurred_CR: 'LastOccurred_CR',
|
||||
AffectedUsers_CR: 'AffectedUsers_CR',
|
||||
CreatedAt_CR: 'CreatedAt_CR',
|
||||
UpdatedAt_CR: 'UpdatedAt_CR',
|
||||
ResolvedAt_CR: 'ResolvedAt_CR',
|
||||
ResolvedBy_CR: 'ResolvedBy_CR'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashSessionScalarFieldEnum = {
|
||||
UUID_CS: 'UUID_CS',
|
||||
SessionId_CS: 'SessionId_CS',
|
||||
AppId_CS: 'AppId_CS',
|
||||
AppVersion_CS: 'AppVersion_CS',
|
||||
UserId_CS: 'UserId_CS',
|
||||
StartedAt_CS: 'StartedAt_CS',
|
||||
EndedAt_CS: 'EndedAt_CS',
|
||||
Duration_CS: 'Duration_CS',
|
||||
IsCrashed_CS: 'IsCrashed_CS',
|
||||
CrashCount_CS: 'CrashCount_CS',
|
||||
DeviceModel_CS: 'DeviceModel_CS',
|
||||
OSVersion_CS: 'OSVersion_CS',
|
||||
CreatedAt_CS: 'CreatedAt_CS',
|
||||
UpdatedAt_CS: 'UpdatedAt_CS'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashAnalyticsScalarFieldEnum = {
|
||||
UUID_CA: 'UUID_CA',
|
||||
AppId_CA: 'AppId_CA',
|
||||
AppVersion_CA: 'AppVersion_CA',
|
||||
Date_CA: 'Date_CA',
|
||||
TotalCrashes_CA: 'TotalCrashes_CA',
|
||||
FatalCrashes_CA: 'FatalCrashes_CA',
|
||||
NonFatalCrashes_CA: 'NonFatalCrashes_CA',
|
||||
UniqueCrashes_CA: 'UniqueCrashes_CA',
|
||||
AffectedUsers_CA: 'AffectedUsers_CA',
|
||||
TotalSessions_CA: 'TotalSessions_CA',
|
||||
CrashedSessions_CA: 'CrashedSessions_CA',
|
||||
CrashFreeRate_CA: 'CrashFreeRate_CA',
|
||||
TopCrashes_CA: 'TopCrashes_CA',
|
||||
CreatedAt_CA: 'CreatedAt_CA',
|
||||
UpdatedAt_CA: 'UpdatedAt_CA'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationScalarFieldEnum = {
|
||||
UUID_AIN: 'UUID_AIN',
|
||||
UserID_AIN: 'UserID_AIN',
|
||||
AnalyzedActivities_AIN: 'AnalyzedActivities_AIN',
|
||||
ActivityTypes_AIN: 'ActivityTypes_AIN',
|
||||
GeneratedTitle_AIN: 'GeneratedTitle_AIN',
|
||||
GeneratedDesc_AIN: 'GeneratedDesc_AIN',
|
||||
SentStatus_AIN: 'SentStatus_AIN',
|
||||
SentAt_AIN: 'SentAt_AIN',
|
||||
DeliveredAt_AIN: 'DeliveredAt_AIN',
|
||||
FailedAt_AIN: 'FailedAt_AIN',
|
||||
ErrorMessage_AIN: 'ErrorMessage_AIN',
|
||||
FCMMessageId_AIN: 'FCMMessageId_AIN',
|
||||
ResponseTime_AIN: 'ResponseTime_AIN',
|
||||
ProcessingTime_AIN: 'ProcessingTime_AIN',
|
||||
ActivityTimeRange_AIN: 'ActivityTimeRange_AIN',
|
||||
AIModel_AIN: 'AIModel_AIN',
|
||||
ScheduledAt_AIN: 'ScheduledAt_AIN',
|
||||
PredictedConfidence_AIN: 'PredictedConfidence_AIN',
|
||||
PredictionReasoning_AIN: 'PredictionReasoning_AIN',
|
||||
UserEngagementPattern_AIN: 'UserEngagementPattern_AIN',
|
||||
DelayMinutes_AIN: 'DelayMinutes_AIN',
|
||||
CreatedAt_AIN: 'CreatedAt_AIN',
|
||||
UpdatedAt_AIN: 'UpdatedAt_AIN'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationAnalyticsScalarFieldEnum = {
|
||||
UUID_ANA: 'UUID_ANA',
|
||||
Date_ANA: 'Date_ANA',
|
||||
TotalAnalyzed_ANA: 'TotalAnalyzed_ANA',
|
||||
TotalGenerated_ANA: 'TotalGenerated_ANA',
|
||||
TotalSent_ANA: 'TotalSent_ANA',
|
||||
TotalDelivered_ANA: 'TotalDelivered_ANA',
|
||||
TotalFailed_ANA: 'TotalFailed_ANA',
|
||||
DeliveryRate_ANA: 'DeliveryRate_ANA',
|
||||
AvgResponseTime_ANA: 'AvgResponseTime_ANA',
|
||||
AvgProcessingTime_ANA: 'AvgProcessingTime_ANA',
|
||||
TopActivityTypes_ANA: 'TopActivityTypes_ANA',
|
||||
PopularTitles_ANA: 'PopularTitles_ANA',
|
||||
ErrorBreakdown_ANA: 'ErrorBreakdown_ANA',
|
||||
CreatedAt_ANA: 'CreatedAt_ANA',
|
||||
UpdatedAt_ANA: 'UpdatedAt_ANA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppMenuScalarFieldEnum = {
|
||||
UUID_AM: 'UUID_AM',
|
||||
Name_AM: 'Name_AM',
|
||||
Route_AM: 'Route_AM',
|
||||
Icon_AM: 'Icon_AM',
|
||||
IsActive_AM: 'IsActive_AM',
|
||||
Badge_AM: 'Badge_AM',
|
||||
Order_AM: 'Order_AM',
|
||||
Type_AM: 'Type_AM',
|
||||
CreatedAt_AM: 'CreatedAt_AM',
|
||||
UpdatedAt_AM: 'UpdatedAt_AM'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.NullableJsonNullValueInput = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
exports.Prisma.AdminAccountOrderByRelevanceFieldEnum = {
|
||||
UUID_AA: 'UUID_AA',
|
||||
Username_AA: 'Username_AA',
|
||||
Email_AA: 'Email_AA',
|
||||
Password_AA: 'Password_AA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCredentialOrderByRelevanceFieldEnum = {
|
||||
UUID_AC: 'UUID_AC',
|
||||
TokenCredential_AC: 'TokenCredential_AC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppContentOrderByRelevanceFieldEnum = {
|
||||
UUID_APC: 'UUID_APC',
|
||||
Content_APC: 'Content_APC',
|
||||
Filename_APC: 'Filename_APC',
|
||||
Title_APC: 'Title_APC',
|
||||
Url_APC: 'Url_APC',
|
||||
TargetUrl_APC: 'TargetUrl_APC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCampaignOrderByRelevanceFieldEnum = {
|
||||
UUID_ACP: 'UUID_ACP',
|
||||
Title_ACP: 'Title_ACP',
|
||||
Content_ACP: 'Content_ACP',
|
||||
ErrorMessage_ACP: 'ErrorMessage_ACP',
|
||||
Data_ACP: 'Data_ACP',
|
||||
ImageUrl_ACP: 'ImageUrl_ACP'
|
||||
};
|
||||
|
||||
exports.Prisma.CampaignDeliveryOrderByRelevanceFieldEnum = {
|
||||
UUID_CD: 'UUID_CD',
|
||||
Campaign_CD: 'Campaign_CD',
|
||||
UserID_CD: 'UserID_CD',
|
||||
Token_CD: 'Token_CD',
|
||||
ErrorMessage_CD: 'ErrorMessage_CD',
|
||||
ResponseData_CD: 'ResponseData_CD'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersTokenOrderByRelevanceFieldEnum = {
|
||||
UUID_UT: 'UUID_UT',
|
||||
UserID_UT: 'UserID_UT',
|
||||
Token_UT: 'Token_UT'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersActivityOrderByRelevanceFieldEnum = {
|
||||
UUID_UA: 'UUID_UA',
|
||||
UUID_UT: 'UUID_UT',
|
||||
Params_UA: 'Params_UA'
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueFilter = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull,
|
||||
AnyNull: Prisma.AnyNull
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashReportOrderByRelevanceFieldEnum = {
|
||||
UUID_CR: 'UUID_CR',
|
||||
AppId_CR: 'AppId_CR',
|
||||
AppVersion_CR: 'AppVersion_CR',
|
||||
BuildVersion_CR: 'BuildVersion_CR',
|
||||
CrashId_CR: 'CrashId_CR',
|
||||
SessionId_CR: 'SessionId_CR',
|
||||
UserId_CR: 'UserId_CR',
|
||||
CrashType_CR: 'CrashType_CR',
|
||||
ExceptionName_CR: 'ExceptionName_CR',
|
||||
ExceptionReason_CR: 'ExceptionReason_CR',
|
||||
StackTrace_CR: 'StackTrace_CR',
|
||||
ThreadName_CR: 'ThreadName_CR',
|
||||
DeviceModel_CR: 'DeviceModel_CR',
|
||||
DeviceBrand_CR: 'DeviceBrand_CR',
|
||||
OSVersion_CR: 'OSVersion_CR',
|
||||
Architecture_CR: 'Architecture_CR',
|
||||
NetworkType_CR: 'NetworkType_CR',
|
||||
ResolvedBy_CR: 'ResolvedBy_CR'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashSessionOrderByRelevanceFieldEnum = {
|
||||
UUID_CS: 'UUID_CS',
|
||||
SessionId_CS: 'SessionId_CS',
|
||||
AppId_CS: 'AppId_CS',
|
||||
AppVersion_CS: 'AppVersion_CS',
|
||||
UserId_CS: 'UserId_CS',
|
||||
DeviceModel_CS: 'DeviceModel_CS',
|
||||
OSVersion_CS: 'OSVersion_CS'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashAnalyticsOrderByRelevanceFieldEnum = {
|
||||
UUID_CA: 'UUID_CA',
|
||||
AppId_CA: 'AppId_CA',
|
||||
AppVersion_CA: 'AppVersion_CA'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationOrderByRelevanceFieldEnum = {
|
||||
UUID_AIN: 'UUID_AIN',
|
||||
UserID_AIN: 'UserID_AIN',
|
||||
ActivityTypes_AIN: 'ActivityTypes_AIN',
|
||||
GeneratedTitle_AIN: 'GeneratedTitle_AIN',
|
||||
GeneratedDesc_AIN: 'GeneratedDesc_AIN',
|
||||
ErrorMessage_AIN: 'ErrorMessage_AIN',
|
||||
FCMMessageId_AIN: 'FCMMessageId_AIN',
|
||||
AIModel_AIN: 'AIModel_AIN',
|
||||
PredictionReasoning_AIN: 'PredictionReasoning_AIN',
|
||||
UserEngagementPattern_AIN: 'UserEngagementPattern_AIN'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationAnalyticsOrderByRelevanceFieldEnum = {
|
||||
UUID_ANA: 'UUID_ANA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppMenuOrderByRelevanceFieldEnum = {
|
||||
UUID_AM: 'UUID_AM',
|
||||
Name_AM: 'Name_AM',
|
||||
Route_AM: 'Route_AM',
|
||||
Icon_AM: 'Icon_AM',
|
||||
Badge_AM: 'Badge_AM'
|
||||
};
|
||||
exports.ContentType = exports.$Enums.ContentType = {
|
||||
splash: 'splash',
|
||||
promo: 'promo',
|
||||
article: 'article',
|
||||
banner: 'banner',
|
||||
floatingWidget: 'floatingWidget'
|
||||
};
|
||||
|
||||
exports.CorpType = exports.$Enums.CorpType = {
|
||||
walanja: 'walanja',
|
||||
simaya: 'simaya',
|
||||
cifo: 'cifo'
|
||||
};
|
||||
|
||||
exports.CampaignStatus = exports.$Enums.CampaignStatus = {
|
||||
failed: 'failed',
|
||||
pending: 'pending',
|
||||
cancelled: 'cancelled',
|
||||
completed: 'completed'
|
||||
};
|
||||
|
||||
exports.DeliveryStatus = exports.$Enums.DeliveryStatus = {
|
||||
pending: 'pending',
|
||||
scheduled: 'scheduled',
|
||||
sent: 'sent',
|
||||
delivered: 'delivered',
|
||||
failed: 'failed',
|
||||
cancelled: 'cancelled'
|
||||
};
|
||||
|
||||
exports.ActivityType = exports.$Enums.ActivityType = {
|
||||
VisitHotel: 'VisitHotel',
|
||||
VisitRoom: 'VisitRoom',
|
||||
VisitRetail: 'VisitRetail',
|
||||
VisitCCTV: 'VisitCCTV',
|
||||
VisitTopup: 'VisitTopup',
|
||||
VisitTransfer: 'VisitTransfer',
|
||||
VisitSwap: 'VisitSwap',
|
||||
CheckBalance: 'CheckBalance',
|
||||
InputDepositAmount: 'InputDepositAmount',
|
||||
InputTransferAmount: 'InputTransferAmount',
|
||||
ScanQRCode: 'ScanQRCode',
|
||||
SelectSwapPair: 'SelectSwapPair',
|
||||
ViewArticle: 'ViewArticle',
|
||||
ViewPromo: 'ViewPromo',
|
||||
ViewDiscount: 'ViewDiscount',
|
||||
SearchHotel: 'SearchHotel',
|
||||
FilterHotel: 'FilterHotel',
|
||||
ViewHotelGallery: 'ViewHotelGallery',
|
||||
CheckRoomAvail: 'CheckRoomAvail',
|
||||
ViewProfile: 'ViewProfile',
|
||||
UpdateProfile: 'UpdateProfile',
|
||||
ChangePIN: 'ChangePIN',
|
||||
ViewQRCode: 'ViewQRCode',
|
||||
InitiateBooking: 'InitiateBooking',
|
||||
SelectCheckInDate: 'SelectCheckInDate',
|
||||
SelectCheckOutDate: 'SelectCheckOutDate',
|
||||
ViewBookingSummary: 'ViewBookingSummary',
|
||||
SelectPaymentMethod: 'SelectPaymentMethod',
|
||||
ViewPaymentEstimate: 'ViewPaymentEstimate',
|
||||
InitiatePayment: 'InitiatePayment',
|
||||
CheckInternetBill: 'CheckInternetBill',
|
||||
ViewTransactionHistory: 'ViewTransactionHistory',
|
||||
ViewNotification: 'ViewNotification',
|
||||
ShareContent: 'ShareContent',
|
||||
SaveFavorite: 'SaveFavorite',
|
||||
ContactSupport: 'ContactSupport',
|
||||
ViewFAQ: 'ViewFAQ'
|
||||
};
|
||||
|
||||
exports.CrashSeverity = exports.$Enums.CrashSeverity = {
|
||||
fatal: 'fatal',
|
||||
error: 'error',
|
||||
warning: 'warning',
|
||||
info: 'info'
|
||||
};
|
||||
|
||||
exports.CrashStatus = exports.$Enums.CrashStatus = {
|
||||
new: 'new',
|
||||
acknowledged: 'acknowledged',
|
||||
resolved: 'resolved',
|
||||
ignored: 'ignored'
|
||||
};
|
||||
|
||||
exports.DeviceOS = exports.$Enums.DeviceOS = {
|
||||
android: 'android',
|
||||
ios: 'ios',
|
||||
web: 'web',
|
||||
desktop: 'desktop'
|
||||
};
|
||||
|
||||
exports.MenuType = exports.$Enums.MenuType = {
|
||||
IN_APP_ROUTE: 'IN_APP_ROUTE',
|
||||
WEB_OPEN: 'WEB_OPEN'
|
||||
};
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
AdminAccount: 'AdminAccount',
|
||||
AppCredential: 'AppCredential',
|
||||
AppContent: 'AppContent',
|
||||
AppCampaign: 'AppCampaign',
|
||||
CampaignDelivery: 'CampaignDelivery',
|
||||
UsersToken: 'UsersToken',
|
||||
UsersActivity: 'UsersActivity',
|
||||
CrashReport: 'CrashReport',
|
||||
CrashSession: 'CrashSession',
|
||||
CrashAnalytics: 'CrashAnalytics',
|
||||
AINotification: 'AINotification',
|
||||
AINotificationAnalytics: 'AINotificationAnalytics',
|
||||
AppMenu: 'AppMenu'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"name": "prisma-client-870577abbf7d908bc1a7d692edeaa301b6235b2f63eff479d515ae8032ab988a",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "index-browser.js",
|
||||
"exports": {
|
||||
"./client": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./edge": {
|
||||
"types": "./edge.d.ts",
|
||||
"require": "./edge.js",
|
||||
"import": "./edge.js",
|
||||
"default": "./edge.js"
|
||||
},
|
||||
"./react-native": {
|
||||
"types": "./react-native.d.ts",
|
||||
"require": "./react-native.js",
|
||||
"import": "./react-native.js",
|
||||
"default": "./react-native.js"
|
||||
},
|
||||
"./extension": {
|
||||
"types": "./extension.d.ts",
|
||||
"require": "./extension.js",
|
||||
"import": "./extension.js",
|
||||
"default": "./extension.js"
|
||||
},
|
||||
"./index-browser": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index-browser.js",
|
||||
"import": "./index-browser.js",
|
||||
"default": "./index-browser.js"
|
||||
},
|
||||
"./index": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.js",
|
||||
"import": "./index.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./wasm": {
|
||||
"types": "./wasm.d.ts",
|
||||
"require": "./wasm.js",
|
||||
"import": "./wasm.mjs",
|
||||
"default": "./wasm.mjs"
|
||||
},
|
||||
"./runtime/client": {
|
||||
"types": "./runtime/client.d.ts",
|
||||
"node": {
|
||||
"require": "./runtime/client.js",
|
||||
"default": "./runtime/client.js"
|
||||
},
|
||||
"require": "./runtime/client.js",
|
||||
"import": "./runtime/client.mjs",
|
||||
"default": "./runtime/client.mjs"
|
||||
},
|
||||
"./runtime/library": {
|
||||
"types": "./runtime/library.d.ts",
|
||||
"require": "./runtime/library.js",
|
||||
"import": "./runtime/library.mjs",
|
||||
"default": "./runtime/library.mjs"
|
||||
},
|
||||
"./runtime/binary": {
|
||||
"types": "./runtime/binary.d.ts",
|
||||
"require": "./runtime/binary.js",
|
||||
"import": "./runtime/binary.mjs",
|
||||
"default": "./runtime/binary.mjs"
|
||||
},
|
||||
"./runtime/wasm-engine-edge": {
|
||||
"types": "./runtime/wasm-engine-edge.d.ts",
|
||||
"require": "./runtime/wasm-engine-edge.js",
|
||||
"import": "./runtime/wasm-engine-edge.mjs",
|
||||
"default": "./runtime/wasm-engine-edge.mjs"
|
||||
},
|
||||
"./runtime/wasm-compiler-edge": {
|
||||
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||
"require": "./runtime/wasm-compiler-edge.js",
|
||||
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||
},
|
||||
"./runtime/edge": {
|
||||
"types": "./runtime/edge.d.ts",
|
||||
"require": "./runtime/edge.js",
|
||||
"import": "./runtime/edge-esm.js",
|
||||
"default": "./runtime/edge-esm.js"
|
||||
},
|
||||
"./runtime/react-native": {
|
||||
"types": "./runtime/react-native.d.ts",
|
||||
"require": "./runtime/react-native.js",
|
||||
"import": "./runtime/react-native.js",
|
||||
"default": "./runtime/react-native.js"
|
||||
},
|
||||
"./generator-build": {
|
||||
"require": "./generator-build/index.js",
|
||||
"import": "./generator-build/index.js",
|
||||
"default": "./generator-build/index.js"
|
||||
},
|
||||
"./sql": {
|
||||
"require": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.js",
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.mjs",
|
||||
"default": "./sql.mjs"
|
||||
},
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "6.13.0",
|
||||
"sideEffects": false
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,370 @@
|
|||
declare class AnyNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
declare type Args<T, F extends Operation> = T extends {
|
||||
[K: symbol]: {
|
||||
types: {
|
||||
operations: {
|
||||
[K in F]: {
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||
|
||||
declare class DbNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
export declare function Decimal(n: Decimal.Value): Decimal;
|
||||
|
||||
export declare namespace Decimal {
|
||||
export type Constructor = typeof Decimal;
|
||||
export type Instance = Decimal;
|
||||
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
export type Modulo = Rounding | 9;
|
||||
export type Value = string | number | Decimal;
|
||||
|
||||
// http://mikemcl.github.io/decimal.js/#constructor-properties
|
||||
export interface Config {
|
||||
precision?: number;
|
||||
rounding?: Rounding;
|
||||
toExpNeg?: number;
|
||||
toExpPos?: number;
|
||||
minE?: number;
|
||||
maxE?: number;
|
||||
crypto?: boolean;
|
||||
modulo?: Modulo;
|
||||
defaults?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export declare class Decimal {
|
||||
readonly d: number[];
|
||||
readonly e: number;
|
||||
readonly s: number;
|
||||
|
||||
constructor(n: Decimal.Value);
|
||||
|
||||
absoluteValue(): Decimal;
|
||||
abs(): Decimal;
|
||||
|
||||
ceil(): Decimal;
|
||||
|
||||
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
|
||||
comparedTo(n: Decimal.Value): number;
|
||||
cmp(n: Decimal.Value): number;
|
||||
|
||||
cosine(): Decimal;
|
||||
cos(): Decimal;
|
||||
|
||||
cubeRoot(): Decimal;
|
||||
cbrt(): Decimal;
|
||||
|
||||
decimalPlaces(): number;
|
||||
dp(): number;
|
||||
|
||||
dividedBy(n: Decimal.Value): Decimal;
|
||||
div(n: Decimal.Value): Decimal;
|
||||
|
||||
dividedToIntegerBy(n: Decimal.Value): Decimal;
|
||||
divToInt(n: Decimal.Value): Decimal;
|
||||
|
||||
equals(n: Decimal.Value): boolean;
|
||||
eq(n: Decimal.Value): boolean;
|
||||
|
||||
floor(): Decimal;
|
||||
|
||||
greaterThan(n: Decimal.Value): boolean;
|
||||
gt(n: Decimal.Value): boolean;
|
||||
|
||||
greaterThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
gte(n: Decimal.Value): boolean;
|
||||
|
||||
hyperbolicCosine(): Decimal;
|
||||
cosh(): Decimal;
|
||||
|
||||
hyperbolicSine(): Decimal;
|
||||
sinh(): Decimal;
|
||||
|
||||
hyperbolicTangent(): Decimal;
|
||||
tanh(): Decimal;
|
||||
|
||||
inverseCosine(): Decimal;
|
||||
acos(): Decimal;
|
||||
|
||||
inverseHyperbolicCosine(): Decimal;
|
||||
acosh(): Decimal;
|
||||
|
||||
inverseHyperbolicSine(): Decimal;
|
||||
asinh(): Decimal;
|
||||
|
||||
inverseHyperbolicTangent(): Decimal;
|
||||
atanh(): Decimal;
|
||||
|
||||
inverseSine(): Decimal;
|
||||
asin(): Decimal;
|
||||
|
||||
inverseTangent(): Decimal;
|
||||
atan(): Decimal;
|
||||
|
||||
isFinite(): boolean;
|
||||
|
||||
isInteger(): boolean;
|
||||
isInt(): boolean;
|
||||
|
||||
isNaN(): boolean;
|
||||
|
||||
isNegative(): boolean;
|
||||
isNeg(): boolean;
|
||||
|
||||
isPositive(): boolean;
|
||||
isPos(): boolean;
|
||||
|
||||
isZero(): boolean;
|
||||
|
||||
lessThan(n: Decimal.Value): boolean;
|
||||
lt(n: Decimal.Value): boolean;
|
||||
|
||||
lessThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
lte(n: Decimal.Value): boolean;
|
||||
|
||||
logarithm(n?: Decimal.Value): Decimal;
|
||||
log(n?: Decimal.Value): Decimal;
|
||||
|
||||
minus(n: Decimal.Value): Decimal;
|
||||
sub(n: Decimal.Value): Decimal;
|
||||
|
||||
modulo(n: Decimal.Value): Decimal;
|
||||
mod(n: Decimal.Value): Decimal;
|
||||
|
||||
naturalExponential(): Decimal;
|
||||
exp(): Decimal;
|
||||
|
||||
naturalLogarithm(): Decimal;
|
||||
ln(): Decimal;
|
||||
|
||||
negated(): Decimal;
|
||||
neg(): Decimal;
|
||||
|
||||
plus(n: Decimal.Value): Decimal;
|
||||
add(n: Decimal.Value): Decimal;
|
||||
|
||||
precision(includeZeros?: boolean): number;
|
||||
sd(includeZeros?: boolean): number;
|
||||
|
||||
round(): Decimal;
|
||||
|
||||
sine() : Decimal;
|
||||
sin() : Decimal;
|
||||
|
||||
squareRoot(): Decimal;
|
||||
sqrt(): Decimal;
|
||||
|
||||
tangent() : Decimal;
|
||||
tan() : Decimal;
|
||||
|
||||
times(n: Decimal.Value): Decimal;
|
||||
mul(n: Decimal.Value) : Decimal;
|
||||
|
||||
toBinary(significantDigits?: number): string;
|
||||
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toDecimalPlaces(decimalPlaces?: number): Decimal;
|
||||
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
toDP(decimalPlaces?: number): Decimal;
|
||||
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toExponential(decimalPlaces?: number): string;
|
||||
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFixed(decimalPlaces?: number): string;
|
||||
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFraction(max_denominator?: Decimal.Value): Decimal[];
|
||||
|
||||
toHexadecimal(significantDigits?: number): string;
|
||||
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
toHex(significantDigits?: number): string;
|
||||
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
|
||||
|
||||
toJSON(): string;
|
||||
|
||||
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
|
||||
|
||||
toNumber(): number;
|
||||
|
||||
toOctal(significantDigits?: number): string;
|
||||
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toPower(n: Decimal.Value): Decimal;
|
||||
pow(n: Decimal.Value): Decimal;
|
||||
|
||||
toPrecision(significantDigits?: number): string;
|
||||
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toSignificantDigits(significantDigits?: number): Decimal;
|
||||
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
toSD(significantDigits?: number): Decimal;
|
||||
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toString(): string;
|
||||
|
||||
truncated(): Decimal;
|
||||
trunc(): Decimal;
|
||||
|
||||
valueOf(): string;
|
||||
|
||||
static abs(n: Decimal.Value): Decimal;
|
||||
static acos(n: Decimal.Value): Decimal;
|
||||
static acosh(n: Decimal.Value): Decimal;
|
||||
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static asin(n: Decimal.Value): Decimal;
|
||||
static asinh(n: Decimal.Value): Decimal;
|
||||
static atan(n: Decimal.Value): Decimal;
|
||||
static atanh(n: Decimal.Value): Decimal;
|
||||
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
|
||||
static cbrt(n: Decimal.Value): Decimal;
|
||||
static ceil(n: Decimal.Value): Decimal;
|
||||
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
static clone(object?: Decimal.Config): Decimal.Constructor;
|
||||
static config(object: Decimal.Config): Decimal.Constructor;
|
||||
static cos(n: Decimal.Value): Decimal;
|
||||
static cosh(n: Decimal.Value): Decimal;
|
||||
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static exp(n: Decimal.Value): Decimal;
|
||||
static floor(n: Decimal.Value): Decimal;
|
||||
static hypot(...n: Decimal.Value[]): Decimal;
|
||||
static isDecimal(object: any): object is Decimal;
|
||||
static ln(n: Decimal.Value): Decimal;
|
||||
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
|
||||
static log2(n: Decimal.Value): Decimal;
|
||||
static log10(n: Decimal.Value): Decimal;
|
||||
static max(...n: Decimal.Value[]): Decimal;
|
||||
static min(...n: Decimal.Value[]): Decimal;
|
||||
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static noConflict(): Decimal.Constructor; // Browser only
|
||||
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
|
||||
static random(significantDigits?: number): Decimal;
|
||||
static round(n: Decimal.Value): Decimal;
|
||||
static set(object: Decimal.Config): Decimal.Constructor;
|
||||
static sign(n: Decimal.Value): number;
|
||||
static sin(n: Decimal.Value): Decimal;
|
||||
static sinh(n: Decimal.Value): Decimal;
|
||||
static sqrt(n: Decimal.Value): Decimal;
|
||||
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static sum(...n: Decimal.Value[]): Decimal;
|
||||
static tan(n: Decimal.Value): Decimal;
|
||||
static tanh(n: Decimal.Value): Decimal;
|
||||
static trunc(n: Decimal.Value): Decimal;
|
||||
|
||||
static readonly default?: Decimal.Constructor;
|
||||
static readonly Decimal?: Decimal.Constructor;
|
||||
|
||||
static readonly precision: number;
|
||||
static readonly rounding: Decimal.Rounding;
|
||||
static readonly toExpNeg: number;
|
||||
static readonly toExpPos: number;
|
||||
static readonly minE: number;
|
||||
static readonly maxE: number;
|
||||
static readonly crypto: boolean;
|
||||
static readonly modulo: Decimal.Modulo;
|
||||
|
||||
static readonly ROUND_UP: 0;
|
||||
static readonly ROUND_DOWN: 1;
|
||||
static readonly ROUND_CEIL: 2;
|
||||
static readonly ROUND_FLOOR: 3;
|
||||
static readonly ROUND_HALF_UP: 4;
|
||||
static readonly ROUND_HALF_DOWN: 5;
|
||||
static readonly ROUND_HALF_EVEN: 6;
|
||||
static readonly ROUND_HALF_CEIL: 7;
|
||||
static readonly ROUND_HALF_FLOOR: 8;
|
||||
static readonly EUCLID: 9;
|
||||
}
|
||||
|
||||
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||
[K in keyof A]: Exact<A[K], W[K]>;
|
||||
} : W) : never) | (A extends Narrowable ? A : never);
|
||||
|
||||
export declare function getRuntime(): GetRuntimeOutput;
|
||||
|
||||
declare type GetRuntimeOutput = {
|
||||
id: RuntimeName;
|
||||
prettyName: string;
|
||||
isEdge: boolean;
|
||||
};
|
||||
|
||||
declare class JsonNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates more strict variant of an enum which, unlike regular enum,
|
||||
* throws on non-existing property access. This can be useful in following situations:
|
||||
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||
* - enum values are generated dynamically from DMMF.
|
||||
*
|
||||
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||
*
|
||||
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||
* `in` operator or `hasOwnProperty` function.
|
||||
*
|
||||
* @param definition
|
||||
* @returns
|
||||
*/
|
||||
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||
|
||||
declare type Narrowable = string | number | bigint | boolean | [];
|
||||
|
||||
declare class NullTypesEnumValue extends ObjectEnumValue {
|
||||
_getNamespace(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for unique values of object-valued enums.
|
||||
*/
|
||||
declare abstract class ObjectEnumValue {
|
||||
constructor(arg?: symbol);
|
||||
abstract _getNamespace(): string;
|
||||
_getName(): string;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export declare const objectEnumValues: {
|
||||
classes: {
|
||||
DbNull: typeof DbNull;
|
||||
JsonNull: typeof JsonNull;
|
||||
AnyNull: typeof AnyNull;
|
||||
};
|
||||
instances: {
|
||||
DbNull: DbNull;
|
||||
JsonNull: JsonNull;
|
||||
AnyNull: AnyNull;
|
||||
};
|
||||
};
|
||||
|
||||
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||
|
||||
declare namespace Public {
|
||||
export {
|
||||
validator
|
||||
}
|
||||
}
|
||||
export { Public }
|
||||
|
||||
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||
|
||||
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||
|
||||
export { }
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,343 @@
|
|||
generator client_cms {
|
||||
provider = "prisma-client-js"
|
||||
output = "clients/cms"
|
||||
}
|
||||
|
||||
datasource db_cms {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model AdminAccount {
|
||||
UUID_AA String @id @default(uuid())
|
||||
Username_AA String @unique
|
||||
Email_AA String @unique
|
||||
Password_AA String
|
||||
LastLogin_AA DateTime?
|
||||
UpdatedAt_AA DateTime @updatedAt
|
||||
CreatedAt_AA DateTime @default(now())
|
||||
}
|
||||
|
||||
model AppCredential {
|
||||
UUID_AC String @id @default(uuid())
|
||||
CreatedAt_AC DateTime @default(now())
|
||||
TokenCredential_AC String @unique
|
||||
UpdatedAt_AC DateTime @updatedAt
|
||||
}
|
||||
|
||||
model AppContent {
|
||||
UUID_APC String @id @default(uuid())
|
||||
Content_APC String @db_cms.Text
|
||||
CreatedAt_APC DateTime @default(now())
|
||||
Filename_APC String?
|
||||
Title_APC String
|
||||
UpdatedAt_APC DateTime @updatedAt
|
||||
Url_APC String?
|
||||
Type_APC ContentType
|
||||
CorpType_APC CorpType
|
||||
TargetUrl_APC String?
|
||||
}
|
||||
|
||||
model AppCampaign {
|
||||
UUID_ACP String @id @default(uuid())
|
||||
Title_ACP String
|
||||
Content_ACP String @db_cms.Text
|
||||
Date_ACP DateTime
|
||||
Status_ACP CampaignStatus @default(pending)
|
||||
UpdatedAt_ACP DateTime @updatedAt
|
||||
CreatedAt_ACP DateTime @default(now())
|
||||
TargetUsers_ACP Int? @default(0)
|
||||
SentCount_ACP Int? @default(0)
|
||||
SuccessCount_ACP Int? @default(0)
|
||||
FailureCount_ACP Int? @default(0)
|
||||
DeliveryRate_ACP Float? @default(0)
|
||||
SentAt_ACP DateTime?
|
||||
CompletedAt_ACP DateTime?
|
||||
ErrorMessage_ACP String? @db_cms.Text
|
||||
Data_ACP String? @db_cms.Text
|
||||
ImageUrl_ACP String?
|
||||
CampaignDelivery CampaignDelivery[]
|
||||
}
|
||||
|
||||
model CampaignDelivery {
|
||||
UUID_CD String @id @default(uuid()) @db_cms.Char(36)
|
||||
Campaign_CD String
|
||||
UserID_CD String
|
||||
Token_CD String
|
||||
Status_CD DeliveryStatus @default(pending)
|
||||
SentAt_CD DateTime?
|
||||
DeliveredAt_CD DateTime?
|
||||
FailedAt_CD DateTime?
|
||||
ErrorMessage_CD String? @db_cms.Text
|
||||
ResponseData_CD String? @db_cms.Text
|
||||
CreatedAt_CD DateTime @default(now())
|
||||
UpdatedAt_CD DateTime @updatedAt
|
||||
AppCampaign AppCampaign @relation(fields: [Campaign_CD], references: [UUID_ACP], onDelete: Cascade)
|
||||
|
||||
@@index([Campaign_CD])
|
||||
@@index([UserID_CD])
|
||||
}
|
||||
|
||||
model UsersToken {
|
||||
UUID_UT String @id @default(uuid())
|
||||
UserID_UT String @unique
|
||||
Token_UT String @unique
|
||||
UpdatedAt_UT DateTime @updatedAt
|
||||
CreatedAt_UT DateTime @default(now())
|
||||
UsersActivity UsersActivity[]
|
||||
}
|
||||
|
||||
model UsersActivity {
|
||||
UUID_UA String @id @default(uuid())
|
||||
UUID_UT String
|
||||
ActivityType_UA ActivityType
|
||||
Params_UA String
|
||||
NotifyAt_UA DateTime @default(now())
|
||||
UpdatedAt_UA DateTime @updatedAt
|
||||
CreatedAt_UA DateTime @default(now())
|
||||
Processed_UA Boolean @default(false)
|
||||
UsersToken UsersToken @relation(fields: [UUID_UT], references: [UUID_UT], onDelete: Cascade)
|
||||
|
||||
@@index([UUID_UT], map: "UsersActivity_UUID_UT_fkey")
|
||||
}
|
||||
|
||||
model CrashReport {
|
||||
UUID_CR String @id @default(uuid())
|
||||
AppId_CR String
|
||||
AppVersion_CR String
|
||||
BuildVersion_CR String?
|
||||
CrashId_CR String @unique
|
||||
SessionId_CR String?
|
||||
UserId_CR String?
|
||||
CrashType_CR String
|
||||
ExceptionName_CR String?
|
||||
ExceptionReason_CR String? @db_cms.Text
|
||||
StackTrace_CR String? @db_cms.LongText
|
||||
ThreadName_CR String?
|
||||
IsFatal_CR Boolean @default(true)
|
||||
Severity_CR CrashSeverity @default(fatal)
|
||||
Status_CR CrashStatus @default(new)
|
||||
DeviceModel_CR String?
|
||||
DeviceBrand_CR String?
|
||||
OSName_CR DeviceOS @default(android)
|
||||
OSVersion_CR String?
|
||||
Architecture_CR String?
|
||||
AvailableRam_CR BigInt?
|
||||
TotalRam_CR BigInt?
|
||||
AvailableDisk_CR BigInt?
|
||||
TotalDisk_CR BigInt?
|
||||
BatteryLevel_CR Float?
|
||||
IsRooted_CR Boolean? @default(false)
|
||||
IsDebugger_CR Boolean? @default(false)
|
||||
NetworkType_CR String?
|
||||
Breadcrumbs_CR Json?
|
||||
CustomData_CR Json?
|
||||
Logs_CR Json?
|
||||
CrashCount_CR Int @default(1)
|
||||
FirstOccurred_CR DateTime @default(now())
|
||||
LastOccurred_CR DateTime @default(now())
|
||||
AffectedUsers_CR Int @default(1)
|
||||
CreatedAt_CR DateTime @default(now())
|
||||
UpdatedAt_CR DateTime @updatedAt
|
||||
ResolvedAt_CR DateTime?
|
||||
ResolvedBy_CR String?
|
||||
}
|
||||
|
||||
model CrashSession {
|
||||
UUID_CS String @id @default(uuid())
|
||||
SessionId_CS String @unique
|
||||
AppId_CS String
|
||||
AppVersion_CS String
|
||||
UserId_CS String?
|
||||
StartedAt_CS DateTime
|
||||
EndedAt_CS DateTime?
|
||||
Duration_CS Int?
|
||||
IsCrashed_CS Boolean @default(false)
|
||||
CrashCount_CS Int @default(0)
|
||||
DeviceModel_CS String?
|
||||
OSVersion_CS String?
|
||||
CreatedAt_CS DateTime @default(now())
|
||||
UpdatedAt_CS DateTime @updatedAt
|
||||
}
|
||||
|
||||
model CrashAnalytics {
|
||||
UUID_CA String @id @default(uuid())
|
||||
AppId_CA String
|
||||
AppVersion_CA String
|
||||
Date_CA DateTime @db_cms.Date
|
||||
TotalCrashes_CA Int @default(0)
|
||||
FatalCrashes_CA Int @default(0)
|
||||
NonFatalCrashes_CA Int @default(0)
|
||||
UniqueCrashes_CA Int @default(0)
|
||||
AffectedUsers_CA Int @default(0)
|
||||
TotalSessions_CA Int @default(0)
|
||||
CrashedSessions_CA Int @default(0)
|
||||
CrashFreeRate_CA Float?
|
||||
TopCrashes_CA Json?
|
||||
CreatedAt_CA DateTime @default(now())
|
||||
UpdatedAt_CA DateTime @updatedAt
|
||||
|
||||
@@unique([AppId_CA, AppVersion_CA, Date_CA])
|
||||
}
|
||||
|
||||
model AINotification {
|
||||
UUID_AIN String @id @default(uuid())
|
||||
UserID_AIN String
|
||||
AnalyzedActivities_AIN Int @default(0)
|
||||
ActivityTypes_AIN String? @db_cms.Text
|
||||
GeneratedTitle_AIN String
|
||||
GeneratedDesc_AIN String @db_cms.Text
|
||||
SentStatus_AIN DeliveryStatus @default(pending)
|
||||
SentAt_AIN DateTime?
|
||||
DeliveredAt_AIN DateTime?
|
||||
FailedAt_AIN DateTime?
|
||||
ErrorMessage_AIN String? @db_cms.Text
|
||||
FCMMessageId_AIN String?
|
||||
ResponseTime_AIN Int?
|
||||
ProcessingTime_AIN Int?
|
||||
ActivityTimeRange_AIN Int @default(60)
|
||||
AIModel_AIN String @default("google/gemma-3n-e4b-it")
|
||||
ScheduledAt_AIN DateTime?
|
||||
PredictedConfidence_AIN Int?
|
||||
PredictionReasoning_AIN String? @db_cms.VarChar(255)
|
||||
UserEngagementPattern_AIN String? @db_cms.VarChar(50)
|
||||
DelayMinutes_AIN Int?
|
||||
CreatedAt_AIN DateTime @default(now())
|
||||
UpdatedAt_AIN DateTime @updatedAt
|
||||
|
||||
@@index([UserID_AIN])
|
||||
@@index([SentStatus_AIN])
|
||||
@@index([CreatedAt_AIN])
|
||||
@@index([SentStatus_AIN, ScheduledAt_AIN], name: "idx_scheduled_notifications")
|
||||
@@index([UserID_AIN, UserEngagementPattern_AIN], name: "idx_user_pattern")
|
||||
}
|
||||
|
||||
model AINotificationAnalytics {
|
||||
UUID_ANA String @id @default(uuid())
|
||||
Date_ANA DateTime @unique @db_cms.Date
|
||||
TotalAnalyzed_ANA Int @default(0)
|
||||
TotalGenerated_ANA Int @default(0)
|
||||
TotalSent_ANA Int @default(0)
|
||||
TotalDelivered_ANA Int @default(0)
|
||||
TotalFailed_ANA Int @default(0)
|
||||
DeliveryRate_ANA Float? @default(0)
|
||||
AvgResponseTime_ANA Float? @default(0)
|
||||
AvgProcessingTime_ANA Float? @default(0)
|
||||
TopActivityTypes_ANA Json?
|
||||
PopularTitles_ANA Json?
|
||||
ErrorBreakdown_ANA Json?
|
||||
CreatedAt_ANA DateTime @default(now())
|
||||
UpdatedAt_ANA DateTime @updatedAt
|
||||
}
|
||||
|
||||
model AppMenu {
|
||||
UUID_AM String @id @default(uuid())
|
||||
Name_AM String
|
||||
Route_AM String
|
||||
Icon_AM String
|
||||
IsActive_AM Boolean @default(true)
|
||||
Badge_AM String?
|
||||
Order_AM Int @default(0)
|
||||
Type_AM MenuType @default(IN_APP_ROUTE)
|
||||
CreatedAt_AM DateTime @default(now())
|
||||
UpdatedAt_AM DateTime @updatedAt
|
||||
|
||||
@@index([Order_AM])
|
||||
@@index([IsActive_AM])
|
||||
}
|
||||
|
||||
enum MenuType {
|
||||
IN_APP_ROUTE
|
||||
WEB_OPEN
|
||||
}
|
||||
|
||||
enum CampaignStatus {
|
||||
failed
|
||||
pending
|
||||
cancelled
|
||||
completed
|
||||
}
|
||||
|
||||
enum ContentType {
|
||||
splash
|
||||
promo
|
||||
article
|
||||
banner
|
||||
floatingWidget
|
||||
}
|
||||
|
||||
enum CorpType {
|
||||
walanja
|
||||
simaya
|
||||
cifo
|
||||
}
|
||||
|
||||
enum ActivityType {
|
||||
VisitHotel
|
||||
VisitRoom
|
||||
VisitRetail
|
||||
VisitCCTV
|
||||
VisitTopup
|
||||
VisitTransfer
|
||||
VisitSwap
|
||||
CheckBalance
|
||||
InputDepositAmount
|
||||
InputTransferAmount
|
||||
ScanQRCode
|
||||
SelectSwapPair
|
||||
ViewArticle
|
||||
ViewPromo
|
||||
ViewDiscount
|
||||
SearchHotel
|
||||
FilterHotel
|
||||
ViewHotelGallery
|
||||
CheckRoomAvail
|
||||
ViewProfile
|
||||
UpdateProfile
|
||||
ChangePIN
|
||||
ViewQRCode
|
||||
InitiateBooking
|
||||
SelectCheckInDate
|
||||
SelectCheckOutDate
|
||||
ViewBookingSummary
|
||||
SelectPaymentMethod
|
||||
ViewPaymentEstimate
|
||||
InitiatePayment
|
||||
CheckInternetBill
|
||||
ViewTransactionHistory
|
||||
ViewNotification
|
||||
ShareContent
|
||||
SaveFavorite
|
||||
ContactSupport
|
||||
ViewFAQ
|
||||
}
|
||||
|
||||
enum DeliveryStatus {
|
||||
pending
|
||||
scheduled
|
||||
sent
|
||||
delivered
|
||||
failed
|
||||
cancelled
|
||||
}
|
||||
|
||||
enum CrashSeverity {
|
||||
fatal
|
||||
error
|
||||
warning
|
||||
info
|
||||
}
|
||||
|
||||
enum CrashStatus {
|
||||
new
|
||||
acknowledged
|
||||
resolved
|
||||
ignored
|
||||
}
|
||||
|
||||
enum DeviceOS {
|
||||
android
|
||||
ios
|
||||
web
|
||||
desktop
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,620 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.13.0
|
||||
* Query Engine version: 361e86d0ea4987e9f53a565309b3eed797a6bcbd
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.13.0",
|
||||
engine: "361e86d0ea4987e9f53a565309b3eed797a6bcbd"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.AdminAccountScalarFieldEnum = {
|
||||
UUID_AA: 'UUID_AA',
|
||||
Username_AA: 'Username_AA',
|
||||
Email_AA: 'Email_AA',
|
||||
Password_AA: 'Password_AA',
|
||||
LastLogin_AA: 'LastLogin_AA',
|
||||
UpdatedAt_AA: 'UpdatedAt_AA',
|
||||
CreatedAt_AA: 'CreatedAt_AA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCredentialScalarFieldEnum = {
|
||||
UUID_AC: 'UUID_AC',
|
||||
CreatedAt_AC: 'CreatedAt_AC',
|
||||
TokenCredential_AC: 'TokenCredential_AC',
|
||||
UpdatedAt_AC: 'UpdatedAt_AC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppContentScalarFieldEnum = {
|
||||
UUID_APC: 'UUID_APC',
|
||||
Content_APC: 'Content_APC',
|
||||
CreatedAt_APC: 'CreatedAt_APC',
|
||||
Filename_APC: 'Filename_APC',
|
||||
Title_APC: 'Title_APC',
|
||||
UpdatedAt_APC: 'UpdatedAt_APC',
|
||||
Url_APC: 'Url_APC',
|
||||
Type_APC: 'Type_APC',
|
||||
CorpType_APC: 'CorpType_APC',
|
||||
TargetUrl_APC: 'TargetUrl_APC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCampaignScalarFieldEnum = {
|
||||
UUID_ACP: 'UUID_ACP',
|
||||
Title_ACP: 'Title_ACP',
|
||||
Content_ACP: 'Content_ACP',
|
||||
Date_ACP: 'Date_ACP',
|
||||
Status_ACP: 'Status_ACP',
|
||||
UpdatedAt_ACP: 'UpdatedAt_ACP',
|
||||
CreatedAt_ACP: 'CreatedAt_ACP',
|
||||
TargetUsers_ACP: 'TargetUsers_ACP',
|
||||
SentCount_ACP: 'SentCount_ACP',
|
||||
SuccessCount_ACP: 'SuccessCount_ACP',
|
||||
FailureCount_ACP: 'FailureCount_ACP',
|
||||
DeliveryRate_ACP: 'DeliveryRate_ACP',
|
||||
SentAt_ACP: 'SentAt_ACP',
|
||||
CompletedAt_ACP: 'CompletedAt_ACP',
|
||||
ErrorMessage_ACP: 'ErrorMessage_ACP',
|
||||
Data_ACP: 'Data_ACP',
|
||||
ImageUrl_ACP: 'ImageUrl_ACP'
|
||||
};
|
||||
|
||||
exports.Prisma.CampaignDeliveryScalarFieldEnum = {
|
||||
UUID_CD: 'UUID_CD',
|
||||
Campaign_CD: 'Campaign_CD',
|
||||
UserID_CD: 'UserID_CD',
|
||||
Token_CD: 'Token_CD',
|
||||
Status_CD: 'Status_CD',
|
||||
SentAt_CD: 'SentAt_CD',
|
||||
DeliveredAt_CD: 'DeliveredAt_CD',
|
||||
FailedAt_CD: 'FailedAt_CD',
|
||||
ErrorMessage_CD: 'ErrorMessage_CD',
|
||||
ResponseData_CD: 'ResponseData_CD',
|
||||
CreatedAt_CD: 'CreatedAt_CD',
|
||||
UpdatedAt_CD: 'UpdatedAt_CD'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersTokenScalarFieldEnum = {
|
||||
UUID_UT: 'UUID_UT',
|
||||
UserID_UT: 'UserID_UT',
|
||||
Token_UT: 'Token_UT',
|
||||
UpdatedAt_UT: 'UpdatedAt_UT',
|
||||
CreatedAt_UT: 'CreatedAt_UT'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersActivityScalarFieldEnum = {
|
||||
UUID_UA: 'UUID_UA',
|
||||
UUID_UT: 'UUID_UT',
|
||||
ActivityType_UA: 'ActivityType_UA',
|
||||
Params_UA: 'Params_UA',
|
||||
NotifyAt_UA: 'NotifyAt_UA',
|
||||
UpdatedAt_UA: 'UpdatedAt_UA',
|
||||
CreatedAt_UA: 'CreatedAt_UA',
|
||||
Processed_UA: 'Processed_UA'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashReportScalarFieldEnum = {
|
||||
UUID_CR: 'UUID_CR',
|
||||
AppId_CR: 'AppId_CR',
|
||||
AppVersion_CR: 'AppVersion_CR',
|
||||
BuildVersion_CR: 'BuildVersion_CR',
|
||||
CrashId_CR: 'CrashId_CR',
|
||||
SessionId_CR: 'SessionId_CR',
|
||||
UserId_CR: 'UserId_CR',
|
||||
CrashType_CR: 'CrashType_CR',
|
||||
ExceptionName_CR: 'ExceptionName_CR',
|
||||
ExceptionReason_CR: 'ExceptionReason_CR',
|
||||
StackTrace_CR: 'StackTrace_CR',
|
||||
ThreadName_CR: 'ThreadName_CR',
|
||||
IsFatal_CR: 'IsFatal_CR',
|
||||
Severity_CR: 'Severity_CR',
|
||||
Status_CR: 'Status_CR',
|
||||
DeviceModel_CR: 'DeviceModel_CR',
|
||||
DeviceBrand_CR: 'DeviceBrand_CR',
|
||||
OSName_CR: 'OSName_CR',
|
||||
OSVersion_CR: 'OSVersion_CR',
|
||||
Architecture_CR: 'Architecture_CR',
|
||||
AvailableRam_CR: 'AvailableRam_CR',
|
||||
TotalRam_CR: 'TotalRam_CR',
|
||||
AvailableDisk_CR: 'AvailableDisk_CR',
|
||||
TotalDisk_CR: 'TotalDisk_CR',
|
||||
BatteryLevel_CR: 'BatteryLevel_CR',
|
||||
IsRooted_CR: 'IsRooted_CR',
|
||||
IsDebugger_CR: 'IsDebugger_CR',
|
||||
NetworkType_CR: 'NetworkType_CR',
|
||||
Breadcrumbs_CR: 'Breadcrumbs_CR',
|
||||
CustomData_CR: 'CustomData_CR',
|
||||
Logs_CR: 'Logs_CR',
|
||||
CrashCount_CR: 'CrashCount_CR',
|
||||
FirstOccurred_CR: 'FirstOccurred_CR',
|
||||
LastOccurred_CR: 'LastOccurred_CR',
|
||||
AffectedUsers_CR: 'AffectedUsers_CR',
|
||||
CreatedAt_CR: 'CreatedAt_CR',
|
||||
UpdatedAt_CR: 'UpdatedAt_CR',
|
||||
ResolvedAt_CR: 'ResolvedAt_CR',
|
||||
ResolvedBy_CR: 'ResolvedBy_CR'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashSessionScalarFieldEnum = {
|
||||
UUID_CS: 'UUID_CS',
|
||||
SessionId_CS: 'SessionId_CS',
|
||||
AppId_CS: 'AppId_CS',
|
||||
AppVersion_CS: 'AppVersion_CS',
|
||||
UserId_CS: 'UserId_CS',
|
||||
StartedAt_CS: 'StartedAt_CS',
|
||||
EndedAt_CS: 'EndedAt_CS',
|
||||
Duration_CS: 'Duration_CS',
|
||||
IsCrashed_CS: 'IsCrashed_CS',
|
||||
CrashCount_CS: 'CrashCount_CS',
|
||||
DeviceModel_CS: 'DeviceModel_CS',
|
||||
OSVersion_CS: 'OSVersion_CS',
|
||||
CreatedAt_CS: 'CreatedAt_CS',
|
||||
UpdatedAt_CS: 'UpdatedAt_CS'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashAnalyticsScalarFieldEnum = {
|
||||
UUID_CA: 'UUID_CA',
|
||||
AppId_CA: 'AppId_CA',
|
||||
AppVersion_CA: 'AppVersion_CA',
|
||||
Date_CA: 'Date_CA',
|
||||
TotalCrashes_CA: 'TotalCrashes_CA',
|
||||
FatalCrashes_CA: 'FatalCrashes_CA',
|
||||
NonFatalCrashes_CA: 'NonFatalCrashes_CA',
|
||||
UniqueCrashes_CA: 'UniqueCrashes_CA',
|
||||
AffectedUsers_CA: 'AffectedUsers_CA',
|
||||
TotalSessions_CA: 'TotalSessions_CA',
|
||||
CrashedSessions_CA: 'CrashedSessions_CA',
|
||||
CrashFreeRate_CA: 'CrashFreeRate_CA',
|
||||
TopCrashes_CA: 'TopCrashes_CA',
|
||||
CreatedAt_CA: 'CreatedAt_CA',
|
||||
UpdatedAt_CA: 'UpdatedAt_CA'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationScalarFieldEnum = {
|
||||
UUID_AIN: 'UUID_AIN',
|
||||
UserID_AIN: 'UserID_AIN',
|
||||
AnalyzedActivities_AIN: 'AnalyzedActivities_AIN',
|
||||
ActivityTypes_AIN: 'ActivityTypes_AIN',
|
||||
GeneratedTitle_AIN: 'GeneratedTitle_AIN',
|
||||
GeneratedDesc_AIN: 'GeneratedDesc_AIN',
|
||||
SentStatus_AIN: 'SentStatus_AIN',
|
||||
SentAt_AIN: 'SentAt_AIN',
|
||||
DeliveredAt_AIN: 'DeliveredAt_AIN',
|
||||
FailedAt_AIN: 'FailedAt_AIN',
|
||||
ErrorMessage_AIN: 'ErrorMessage_AIN',
|
||||
FCMMessageId_AIN: 'FCMMessageId_AIN',
|
||||
ResponseTime_AIN: 'ResponseTime_AIN',
|
||||
ProcessingTime_AIN: 'ProcessingTime_AIN',
|
||||
ActivityTimeRange_AIN: 'ActivityTimeRange_AIN',
|
||||
AIModel_AIN: 'AIModel_AIN',
|
||||
ScheduledAt_AIN: 'ScheduledAt_AIN',
|
||||
PredictedConfidence_AIN: 'PredictedConfidence_AIN',
|
||||
PredictionReasoning_AIN: 'PredictionReasoning_AIN',
|
||||
UserEngagementPattern_AIN: 'UserEngagementPattern_AIN',
|
||||
DelayMinutes_AIN: 'DelayMinutes_AIN',
|
||||
CreatedAt_AIN: 'CreatedAt_AIN',
|
||||
UpdatedAt_AIN: 'UpdatedAt_AIN'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationAnalyticsScalarFieldEnum = {
|
||||
UUID_ANA: 'UUID_ANA',
|
||||
Date_ANA: 'Date_ANA',
|
||||
TotalAnalyzed_ANA: 'TotalAnalyzed_ANA',
|
||||
TotalGenerated_ANA: 'TotalGenerated_ANA',
|
||||
TotalSent_ANA: 'TotalSent_ANA',
|
||||
TotalDelivered_ANA: 'TotalDelivered_ANA',
|
||||
TotalFailed_ANA: 'TotalFailed_ANA',
|
||||
DeliveryRate_ANA: 'DeliveryRate_ANA',
|
||||
AvgResponseTime_ANA: 'AvgResponseTime_ANA',
|
||||
AvgProcessingTime_ANA: 'AvgProcessingTime_ANA',
|
||||
TopActivityTypes_ANA: 'TopActivityTypes_ANA',
|
||||
PopularTitles_ANA: 'PopularTitles_ANA',
|
||||
ErrorBreakdown_ANA: 'ErrorBreakdown_ANA',
|
||||
CreatedAt_ANA: 'CreatedAt_ANA',
|
||||
UpdatedAt_ANA: 'UpdatedAt_ANA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppMenuScalarFieldEnum = {
|
||||
UUID_AM: 'UUID_AM',
|
||||
Name_AM: 'Name_AM',
|
||||
Route_AM: 'Route_AM',
|
||||
Icon_AM: 'Icon_AM',
|
||||
IsActive_AM: 'IsActive_AM',
|
||||
Badge_AM: 'Badge_AM',
|
||||
Order_AM: 'Order_AM',
|
||||
Type_AM: 'Type_AM',
|
||||
CreatedAt_AM: 'CreatedAt_AM',
|
||||
UpdatedAt_AM: 'UpdatedAt_AM'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.NullableJsonNullValueInput = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
exports.Prisma.AdminAccountOrderByRelevanceFieldEnum = {
|
||||
UUID_AA: 'UUID_AA',
|
||||
Username_AA: 'Username_AA',
|
||||
Email_AA: 'Email_AA',
|
||||
Password_AA: 'Password_AA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCredentialOrderByRelevanceFieldEnum = {
|
||||
UUID_AC: 'UUID_AC',
|
||||
TokenCredential_AC: 'TokenCredential_AC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppContentOrderByRelevanceFieldEnum = {
|
||||
UUID_APC: 'UUID_APC',
|
||||
Content_APC: 'Content_APC',
|
||||
Filename_APC: 'Filename_APC',
|
||||
Title_APC: 'Title_APC',
|
||||
Url_APC: 'Url_APC',
|
||||
TargetUrl_APC: 'TargetUrl_APC'
|
||||
};
|
||||
|
||||
exports.Prisma.AppCampaignOrderByRelevanceFieldEnum = {
|
||||
UUID_ACP: 'UUID_ACP',
|
||||
Title_ACP: 'Title_ACP',
|
||||
Content_ACP: 'Content_ACP',
|
||||
ErrorMessage_ACP: 'ErrorMessage_ACP',
|
||||
Data_ACP: 'Data_ACP',
|
||||
ImageUrl_ACP: 'ImageUrl_ACP'
|
||||
};
|
||||
|
||||
exports.Prisma.CampaignDeliveryOrderByRelevanceFieldEnum = {
|
||||
UUID_CD: 'UUID_CD',
|
||||
Campaign_CD: 'Campaign_CD',
|
||||
UserID_CD: 'UserID_CD',
|
||||
Token_CD: 'Token_CD',
|
||||
ErrorMessage_CD: 'ErrorMessage_CD',
|
||||
ResponseData_CD: 'ResponseData_CD'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersTokenOrderByRelevanceFieldEnum = {
|
||||
UUID_UT: 'UUID_UT',
|
||||
UserID_UT: 'UserID_UT',
|
||||
Token_UT: 'Token_UT'
|
||||
};
|
||||
|
||||
exports.Prisma.UsersActivityOrderByRelevanceFieldEnum = {
|
||||
UUID_UA: 'UUID_UA',
|
||||
UUID_UT: 'UUID_UT',
|
||||
Params_UA: 'Params_UA'
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueFilter = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull,
|
||||
AnyNull: Prisma.AnyNull
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashReportOrderByRelevanceFieldEnum = {
|
||||
UUID_CR: 'UUID_CR',
|
||||
AppId_CR: 'AppId_CR',
|
||||
AppVersion_CR: 'AppVersion_CR',
|
||||
BuildVersion_CR: 'BuildVersion_CR',
|
||||
CrashId_CR: 'CrashId_CR',
|
||||
SessionId_CR: 'SessionId_CR',
|
||||
UserId_CR: 'UserId_CR',
|
||||
CrashType_CR: 'CrashType_CR',
|
||||
ExceptionName_CR: 'ExceptionName_CR',
|
||||
ExceptionReason_CR: 'ExceptionReason_CR',
|
||||
StackTrace_CR: 'StackTrace_CR',
|
||||
ThreadName_CR: 'ThreadName_CR',
|
||||
DeviceModel_CR: 'DeviceModel_CR',
|
||||
DeviceBrand_CR: 'DeviceBrand_CR',
|
||||
OSVersion_CR: 'OSVersion_CR',
|
||||
Architecture_CR: 'Architecture_CR',
|
||||
NetworkType_CR: 'NetworkType_CR',
|
||||
ResolvedBy_CR: 'ResolvedBy_CR'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashSessionOrderByRelevanceFieldEnum = {
|
||||
UUID_CS: 'UUID_CS',
|
||||
SessionId_CS: 'SessionId_CS',
|
||||
AppId_CS: 'AppId_CS',
|
||||
AppVersion_CS: 'AppVersion_CS',
|
||||
UserId_CS: 'UserId_CS',
|
||||
DeviceModel_CS: 'DeviceModel_CS',
|
||||
OSVersion_CS: 'OSVersion_CS'
|
||||
};
|
||||
|
||||
exports.Prisma.CrashAnalyticsOrderByRelevanceFieldEnum = {
|
||||
UUID_CA: 'UUID_CA',
|
||||
AppId_CA: 'AppId_CA',
|
||||
AppVersion_CA: 'AppVersion_CA'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationOrderByRelevanceFieldEnum = {
|
||||
UUID_AIN: 'UUID_AIN',
|
||||
UserID_AIN: 'UserID_AIN',
|
||||
ActivityTypes_AIN: 'ActivityTypes_AIN',
|
||||
GeneratedTitle_AIN: 'GeneratedTitle_AIN',
|
||||
GeneratedDesc_AIN: 'GeneratedDesc_AIN',
|
||||
ErrorMessage_AIN: 'ErrorMessage_AIN',
|
||||
FCMMessageId_AIN: 'FCMMessageId_AIN',
|
||||
AIModel_AIN: 'AIModel_AIN',
|
||||
PredictionReasoning_AIN: 'PredictionReasoning_AIN',
|
||||
UserEngagementPattern_AIN: 'UserEngagementPattern_AIN'
|
||||
};
|
||||
|
||||
exports.Prisma.AINotificationAnalyticsOrderByRelevanceFieldEnum = {
|
||||
UUID_ANA: 'UUID_ANA'
|
||||
};
|
||||
|
||||
exports.Prisma.AppMenuOrderByRelevanceFieldEnum = {
|
||||
UUID_AM: 'UUID_AM',
|
||||
Name_AM: 'Name_AM',
|
||||
Route_AM: 'Route_AM',
|
||||
Icon_AM: 'Icon_AM',
|
||||
Badge_AM: 'Badge_AM'
|
||||
};
|
||||
exports.ContentType = exports.$Enums.ContentType = {
|
||||
splash: 'splash',
|
||||
promo: 'promo',
|
||||
article: 'article',
|
||||
banner: 'banner',
|
||||
floatingWidget: 'floatingWidget'
|
||||
};
|
||||
|
||||
exports.CorpType = exports.$Enums.CorpType = {
|
||||
walanja: 'walanja',
|
||||
simaya: 'simaya',
|
||||
cifo: 'cifo'
|
||||
};
|
||||
|
||||
exports.CampaignStatus = exports.$Enums.CampaignStatus = {
|
||||
failed: 'failed',
|
||||
pending: 'pending',
|
||||
cancelled: 'cancelled',
|
||||
completed: 'completed'
|
||||
};
|
||||
|
||||
exports.DeliveryStatus = exports.$Enums.DeliveryStatus = {
|
||||
pending: 'pending',
|
||||
scheduled: 'scheduled',
|
||||
sent: 'sent',
|
||||
delivered: 'delivered',
|
||||
failed: 'failed',
|
||||
cancelled: 'cancelled'
|
||||
};
|
||||
|
||||
exports.ActivityType = exports.$Enums.ActivityType = {
|
||||
VisitHotel: 'VisitHotel',
|
||||
VisitRoom: 'VisitRoom',
|
||||
VisitRetail: 'VisitRetail',
|
||||
VisitCCTV: 'VisitCCTV',
|
||||
VisitTopup: 'VisitTopup',
|
||||
VisitTransfer: 'VisitTransfer',
|
||||
VisitSwap: 'VisitSwap',
|
||||
CheckBalance: 'CheckBalance',
|
||||
InputDepositAmount: 'InputDepositAmount',
|
||||
InputTransferAmount: 'InputTransferAmount',
|
||||
ScanQRCode: 'ScanQRCode',
|
||||
SelectSwapPair: 'SelectSwapPair',
|
||||
ViewArticle: 'ViewArticle',
|
||||
ViewPromo: 'ViewPromo',
|
||||
ViewDiscount: 'ViewDiscount',
|
||||
SearchHotel: 'SearchHotel',
|
||||
FilterHotel: 'FilterHotel',
|
||||
ViewHotelGallery: 'ViewHotelGallery',
|
||||
CheckRoomAvail: 'CheckRoomAvail',
|
||||
ViewProfile: 'ViewProfile',
|
||||
UpdateProfile: 'UpdateProfile',
|
||||
ChangePIN: 'ChangePIN',
|
||||
ViewQRCode: 'ViewQRCode',
|
||||
InitiateBooking: 'InitiateBooking',
|
||||
SelectCheckInDate: 'SelectCheckInDate',
|
||||
SelectCheckOutDate: 'SelectCheckOutDate',
|
||||
ViewBookingSummary: 'ViewBookingSummary',
|
||||
SelectPaymentMethod: 'SelectPaymentMethod',
|
||||
ViewPaymentEstimate: 'ViewPaymentEstimate',
|
||||
InitiatePayment: 'InitiatePayment',
|
||||
CheckInternetBill: 'CheckInternetBill',
|
||||
ViewTransactionHistory: 'ViewTransactionHistory',
|
||||
ViewNotification: 'ViewNotification',
|
||||
ShareContent: 'ShareContent',
|
||||
SaveFavorite: 'SaveFavorite',
|
||||
ContactSupport: 'ContactSupport',
|
||||
ViewFAQ: 'ViewFAQ'
|
||||
};
|
||||
|
||||
exports.CrashSeverity = exports.$Enums.CrashSeverity = {
|
||||
fatal: 'fatal',
|
||||
error: 'error',
|
||||
warning: 'warning',
|
||||
info: 'info'
|
||||
};
|
||||
|
||||
exports.CrashStatus = exports.$Enums.CrashStatus = {
|
||||
new: 'new',
|
||||
acknowledged: 'acknowledged',
|
||||
resolved: 'resolved',
|
||||
ignored: 'ignored'
|
||||
};
|
||||
|
||||
exports.DeviceOS = exports.$Enums.DeviceOS = {
|
||||
android: 'android',
|
||||
ios: 'ios',
|
||||
web: 'web',
|
||||
desktop: 'desktop'
|
||||
};
|
||||
|
||||
exports.MenuType = exports.$Enums.MenuType = {
|
||||
IN_APP_ROUTE: 'IN_APP_ROUTE',
|
||||
WEB_OPEN: 'WEB_OPEN'
|
||||
};
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
AdminAccount: 'AdminAccount',
|
||||
AppCredential: 'AppCredential',
|
||||
AppContent: 'AppContent',
|
||||
AppCampaign: 'AppCampaign',
|
||||
CampaignDelivery: 'CampaignDelivery',
|
||||
UsersToken: 'UsersToken',
|
||||
UsersActivity: 'UsersActivity',
|
||||
CrashReport: 'CrashReport',
|
||||
CrashSession: 'CrashSession',
|
||||
CrashAnalytics: 'CrashAnalytics',
|
||||
AINotification: 'AINotification',
|
||||
AINotificationAnalytics: 'AINotificationAnalytics',
|
||||
AppMenu: 'AppMenu'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./default"
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,287 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.13.0
|
||||
* Query Engine version: 361e86d0ea4987e9f53a565309b3eed797a6bcbd
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.13.0",
|
||||
engine: "361e86d0ea4987e9f53a565309b3eed797a6bcbd"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.ConversationsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
subject: 'subject',
|
||||
status: 'status',
|
||||
priority: 'priority',
|
||||
category: 'category',
|
||||
userId: 'userId',
|
||||
adminId: 'adminId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
lastMessageAt: 'lastMessageAt',
|
||||
metadata: 'metadata'
|
||||
};
|
||||
|
||||
exports.Prisma.MessagesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
conversationId: 'conversationId',
|
||||
content: 'content',
|
||||
messageType: 'messageType',
|
||||
senderId: 'senderId',
|
||||
senderType: 'senderType',
|
||||
senderName: 'senderName',
|
||||
status: 'status',
|
||||
readAt: 'readAt',
|
||||
deliveredAt: 'deliveredAt',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.MessageAttachmentsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
messageId: 'messageId',
|
||||
fileName: 'fileName',
|
||||
fileSize: 'fileSize',
|
||||
mimeType: 'mimeType',
|
||||
fileUrl: 'fileUrl',
|
||||
thumbnailUrl: 'thumbnailUrl',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.TypingIndicatorsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
conversationId: 'conversationId',
|
||||
userId: 'userId',
|
||||
userType: 'userType',
|
||||
startedAt: 'startedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.ConversationParticipantsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
conversationId: 'conversationId',
|
||||
userId: 'userId',
|
||||
userType: 'userType',
|
||||
role: 'role',
|
||||
joinedAt: 'joinedAt',
|
||||
leftAt: 'leftAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.NullableJsonNullValueInput = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueFilter = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull,
|
||||
AnyNull: Prisma.AnyNull
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
exports.ConversationStatus = exports.$Enums.ConversationStatus = {
|
||||
active: 'active',
|
||||
closed: 'closed',
|
||||
archived: 'archived'
|
||||
};
|
||||
|
||||
exports.Priority = exports.$Enums.Priority = {
|
||||
low: 'low',
|
||||
normal: 'normal',
|
||||
high: 'high',
|
||||
urgent: 'urgent'
|
||||
};
|
||||
|
||||
exports.MessageType = exports.$Enums.MessageType = {
|
||||
text: 'text',
|
||||
image: 'image',
|
||||
file: 'file',
|
||||
audio: 'audio',
|
||||
video: 'video',
|
||||
location: 'location',
|
||||
contact: 'contact'
|
||||
};
|
||||
|
||||
exports.SenderType = exports.$Enums.SenderType = {
|
||||
admin: 'admin',
|
||||
user: 'user',
|
||||
system: 'system'
|
||||
};
|
||||
|
||||
exports.MessageStatus = exports.$Enums.MessageStatus = {
|
||||
sent: 'sent',
|
||||
delivered: 'delivered',
|
||||
read: 'read',
|
||||
failed: 'failed'
|
||||
};
|
||||
|
||||
exports.ParticipantRole = exports.$Enums.ParticipantRole = {
|
||||
admin: 'admin',
|
||||
member: 'member',
|
||||
observer: 'observer'
|
||||
};
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
Conversations: 'Conversations',
|
||||
Messages: 'Messages',
|
||||
MessageAttachments: 'MessageAttachments',
|
||||
TypingIndicators: 'TypingIndicators',
|
||||
ConversationParticipants: 'ConversationParticipants'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,150 @@
|
|||
{
|
||||
"name": "prisma-client-4a7804b65497821ae7c9d2703b5e7dec726d0c0060cc755423374a7c224c41a0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "index-browser.js",
|
||||
"exports": {
|
||||
"./client": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"require": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"import": {
|
||||
"node": "./index.js",
|
||||
"edge-light": "./wasm.js",
|
||||
"workerd": "./wasm.js",
|
||||
"worker": "./wasm.js",
|
||||
"browser": "./index-browser.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./edge": {
|
||||
"types": "./edge.d.ts",
|
||||
"require": "./edge.js",
|
||||
"import": "./edge.js",
|
||||
"default": "./edge.js"
|
||||
},
|
||||
"./react-native": {
|
||||
"types": "./react-native.d.ts",
|
||||
"require": "./react-native.js",
|
||||
"import": "./react-native.js",
|
||||
"default": "./react-native.js"
|
||||
},
|
||||
"./extension": {
|
||||
"types": "./extension.d.ts",
|
||||
"require": "./extension.js",
|
||||
"import": "./extension.js",
|
||||
"default": "./extension.js"
|
||||
},
|
||||
"./index-browser": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index-browser.js",
|
||||
"import": "./index-browser.js",
|
||||
"default": "./index-browser.js"
|
||||
},
|
||||
"./index": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.js",
|
||||
"import": "./index.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./wasm": {
|
||||
"types": "./wasm.d.ts",
|
||||
"require": "./wasm.js",
|
||||
"import": "./wasm.mjs",
|
||||
"default": "./wasm.mjs"
|
||||
},
|
||||
"./runtime/client": {
|
||||
"types": "./runtime/client.d.ts",
|
||||
"node": {
|
||||
"require": "./runtime/client.js",
|
||||
"default": "./runtime/client.js"
|
||||
},
|
||||
"require": "./runtime/client.js",
|
||||
"import": "./runtime/client.mjs",
|
||||
"default": "./runtime/client.mjs"
|
||||
},
|
||||
"./runtime/library": {
|
||||
"types": "./runtime/library.d.ts",
|
||||
"require": "./runtime/library.js",
|
||||
"import": "./runtime/library.mjs",
|
||||
"default": "./runtime/library.mjs"
|
||||
},
|
||||
"./runtime/binary": {
|
||||
"types": "./runtime/binary.d.ts",
|
||||
"require": "./runtime/binary.js",
|
||||
"import": "./runtime/binary.mjs",
|
||||
"default": "./runtime/binary.mjs"
|
||||
},
|
||||
"./runtime/wasm-engine-edge": {
|
||||
"types": "./runtime/wasm-engine-edge.d.ts",
|
||||
"require": "./runtime/wasm-engine-edge.js",
|
||||
"import": "./runtime/wasm-engine-edge.mjs",
|
||||
"default": "./runtime/wasm-engine-edge.mjs"
|
||||
},
|
||||
"./runtime/wasm-compiler-edge": {
|
||||
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||
"require": "./runtime/wasm-compiler-edge.js",
|
||||
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||
},
|
||||
"./runtime/edge": {
|
||||
"types": "./runtime/edge.d.ts",
|
||||
"require": "./runtime/edge.js",
|
||||
"import": "./runtime/edge-esm.js",
|
||||
"default": "./runtime/edge-esm.js"
|
||||
},
|
||||
"./runtime/react-native": {
|
||||
"types": "./runtime/react-native.d.ts",
|
||||
"require": "./runtime/react-native.js",
|
||||
"import": "./runtime/react-native.js",
|
||||
"default": "./runtime/react-native.js"
|
||||
},
|
||||
"./generator-build": {
|
||||
"require": "./generator-build/index.js",
|
||||
"import": "./generator-build/index.js",
|
||||
"default": "./generator-build/index.js"
|
||||
},
|
||||
"./sql": {
|
||||
"require": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.js",
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./sql.d.ts",
|
||||
"node": "./sql.mjs",
|
||||
"default": "./sql.mjs"
|
||||
},
|
||||
"default": "./sql.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"version": "6.13.0",
|
||||
"sideEffects": false
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,370 @@
|
|||
declare class AnyNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
declare type Args<T, F extends Operation> = T extends {
|
||||
[K: symbol]: {
|
||||
types: {
|
||||
operations: {
|
||||
[K in F]: {
|
||||
args: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||
|
||||
declare class DbNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
export declare function Decimal(n: Decimal.Value): Decimal;
|
||||
|
||||
export declare namespace Decimal {
|
||||
export type Constructor = typeof Decimal;
|
||||
export type Instance = Decimal;
|
||||
export type Rounding = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
export type Modulo = Rounding | 9;
|
||||
export type Value = string | number | Decimal;
|
||||
|
||||
// http://mikemcl.github.io/decimal.js/#constructor-properties
|
||||
export interface Config {
|
||||
precision?: number;
|
||||
rounding?: Rounding;
|
||||
toExpNeg?: number;
|
||||
toExpPos?: number;
|
||||
minE?: number;
|
||||
maxE?: number;
|
||||
crypto?: boolean;
|
||||
modulo?: Modulo;
|
||||
defaults?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export declare class Decimal {
|
||||
readonly d: number[];
|
||||
readonly e: number;
|
||||
readonly s: number;
|
||||
|
||||
constructor(n: Decimal.Value);
|
||||
|
||||
absoluteValue(): Decimal;
|
||||
abs(): Decimal;
|
||||
|
||||
ceil(): Decimal;
|
||||
|
||||
clampedTo(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
clamp(min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
|
||||
comparedTo(n: Decimal.Value): number;
|
||||
cmp(n: Decimal.Value): number;
|
||||
|
||||
cosine(): Decimal;
|
||||
cos(): Decimal;
|
||||
|
||||
cubeRoot(): Decimal;
|
||||
cbrt(): Decimal;
|
||||
|
||||
decimalPlaces(): number;
|
||||
dp(): number;
|
||||
|
||||
dividedBy(n: Decimal.Value): Decimal;
|
||||
div(n: Decimal.Value): Decimal;
|
||||
|
||||
dividedToIntegerBy(n: Decimal.Value): Decimal;
|
||||
divToInt(n: Decimal.Value): Decimal;
|
||||
|
||||
equals(n: Decimal.Value): boolean;
|
||||
eq(n: Decimal.Value): boolean;
|
||||
|
||||
floor(): Decimal;
|
||||
|
||||
greaterThan(n: Decimal.Value): boolean;
|
||||
gt(n: Decimal.Value): boolean;
|
||||
|
||||
greaterThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
gte(n: Decimal.Value): boolean;
|
||||
|
||||
hyperbolicCosine(): Decimal;
|
||||
cosh(): Decimal;
|
||||
|
||||
hyperbolicSine(): Decimal;
|
||||
sinh(): Decimal;
|
||||
|
||||
hyperbolicTangent(): Decimal;
|
||||
tanh(): Decimal;
|
||||
|
||||
inverseCosine(): Decimal;
|
||||
acos(): Decimal;
|
||||
|
||||
inverseHyperbolicCosine(): Decimal;
|
||||
acosh(): Decimal;
|
||||
|
||||
inverseHyperbolicSine(): Decimal;
|
||||
asinh(): Decimal;
|
||||
|
||||
inverseHyperbolicTangent(): Decimal;
|
||||
atanh(): Decimal;
|
||||
|
||||
inverseSine(): Decimal;
|
||||
asin(): Decimal;
|
||||
|
||||
inverseTangent(): Decimal;
|
||||
atan(): Decimal;
|
||||
|
||||
isFinite(): boolean;
|
||||
|
||||
isInteger(): boolean;
|
||||
isInt(): boolean;
|
||||
|
||||
isNaN(): boolean;
|
||||
|
||||
isNegative(): boolean;
|
||||
isNeg(): boolean;
|
||||
|
||||
isPositive(): boolean;
|
||||
isPos(): boolean;
|
||||
|
||||
isZero(): boolean;
|
||||
|
||||
lessThan(n: Decimal.Value): boolean;
|
||||
lt(n: Decimal.Value): boolean;
|
||||
|
||||
lessThanOrEqualTo(n: Decimal.Value): boolean;
|
||||
lte(n: Decimal.Value): boolean;
|
||||
|
||||
logarithm(n?: Decimal.Value): Decimal;
|
||||
log(n?: Decimal.Value): Decimal;
|
||||
|
||||
minus(n: Decimal.Value): Decimal;
|
||||
sub(n: Decimal.Value): Decimal;
|
||||
|
||||
modulo(n: Decimal.Value): Decimal;
|
||||
mod(n: Decimal.Value): Decimal;
|
||||
|
||||
naturalExponential(): Decimal;
|
||||
exp(): Decimal;
|
||||
|
||||
naturalLogarithm(): Decimal;
|
||||
ln(): Decimal;
|
||||
|
||||
negated(): Decimal;
|
||||
neg(): Decimal;
|
||||
|
||||
plus(n: Decimal.Value): Decimal;
|
||||
add(n: Decimal.Value): Decimal;
|
||||
|
||||
precision(includeZeros?: boolean): number;
|
||||
sd(includeZeros?: boolean): number;
|
||||
|
||||
round(): Decimal;
|
||||
|
||||
sine() : Decimal;
|
||||
sin() : Decimal;
|
||||
|
||||
squareRoot(): Decimal;
|
||||
sqrt(): Decimal;
|
||||
|
||||
tangent() : Decimal;
|
||||
tan() : Decimal;
|
||||
|
||||
times(n: Decimal.Value): Decimal;
|
||||
mul(n: Decimal.Value) : Decimal;
|
||||
|
||||
toBinary(significantDigits?: number): string;
|
||||
toBinary(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toDecimalPlaces(decimalPlaces?: number): Decimal;
|
||||
toDecimalPlaces(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
toDP(decimalPlaces?: number): Decimal;
|
||||
toDP(decimalPlaces: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toExponential(decimalPlaces?: number): string;
|
||||
toExponential(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFixed(decimalPlaces?: number): string;
|
||||
toFixed(decimalPlaces: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toFraction(max_denominator?: Decimal.Value): Decimal[];
|
||||
|
||||
toHexadecimal(significantDigits?: number): string;
|
||||
toHexadecimal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
toHex(significantDigits?: number): string;
|
||||
toHex(significantDigits: number, rounding?: Decimal.Rounding): string;
|
||||
|
||||
toJSON(): string;
|
||||
|
||||
toNearest(n: Decimal.Value, rounding?: Decimal.Rounding): Decimal;
|
||||
|
||||
toNumber(): number;
|
||||
|
||||
toOctal(significantDigits?: number): string;
|
||||
toOctal(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toPower(n: Decimal.Value): Decimal;
|
||||
pow(n: Decimal.Value): Decimal;
|
||||
|
||||
toPrecision(significantDigits?: number): string;
|
||||
toPrecision(significantDigits: number, rounding: Decimal.Rounding): string;
|
||||
|
||||
toSignificantDigits(significantDigits?: number): Decimal;
|
||||
toSignificantDigits(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
toSD(significantDigits?: number): Decimal;
|
||||
toSD(significantDigits: number, rounding: Decimal.Rounding): Decimal;
|
||||
|
||||
toString(): string;
|
||||
|
||||
truncated(): Decimal;
|
||||
trunc(): Decimal;
|
||||
|
||||
valueOf(): string;
|
||||
|
||||
static abs(n: Decimal.Value): Decimal;
|
||||
static acos(n: Decimal.Value): Decimal;
|
||||
static acosh(n: Decimal.Value): Decimal;
|
||||
static add(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static asin(n: Decimal.Value): Decimal;
|
||||
static asinh(n: Decimal.Value): Decimal;
|
||||
static atan(n: Decimal.Value): Decimal;
|
||||
static atanh(n: Decimal.Value): Decimal;
|
||||
static atan2(y: Decimal.Value, x: Decimal.Value): Decimal;
|
||||
static cbrt(n: Decimal.Value): Decimal;
|
||||
static ceil(n: Decimal.Value): Decimal;
|
||||
static clamp(n: Decimal.Value, min: Decimal.Value, max: Decimal.Value): Decimal;
|
||||
static clone(object?: Decimal.Config): Decimal.Constructor;
|
||||
static config(object: Decimal.Config): Decimal.Constructor;
|
||||
static cos(n: Decimal.Value): Decimal;
|
||||
static cosh(n: Decimal.Value): Decimal;
|
||||
static div(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static exp(n: Decimal.Value): Decimal;
|
||||
static floor(n: Decimal.Value): Decimal;
|
||||
static hypot(...n: Decimal.Value[]): Decimal;
|
||||
static isDecimal(object: any): object is Decimal;
|
||||
static ln(n: Decimal.Value): Decimal;
|
||||
static log(n: Decimal.Value, base?: Decimal.Value): Decimal;
|
||||
static log2(n: Decimal.Value): Decimal;
|
||||
static log10(n: Decimal.Value): Decimal;
|
||||
static max(...n: Decimal.Value[]): Decimal;
|
||||
static min(...n: Decimal.Value[]): Decimal;
|
||||
static mod(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static mul(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static noConflict(): Decimal.Constructor; // Browser only
|
||||
static pow(base: Decimal.Value, exponent: Decimal.Value): Decimal;
|
||||
static random(significantDigits?: number): Decimal;
|
||||
static round(n: Decimal.Value): Decimal;
|
||||
static set(object: Decimal.Config): Decimal.Constructor;
|
||||
static sign(n: Decimal.Value): number;
|
||||
static sin(n: Decimal.Value): Decimal;
|
||||
static sinh(n: Decimal.Value): Decimal;
|
||||
static sqrt(n: Decimal.Value): Decimal;
|
||||
static sub(x: Decimal.Value, y: Decimal.Value): Decimal;
|
||||
static sum(...n: Decimal.Value[]): Decimal;
|
||||
static tan(n: Decimal.Value): Decimal;
|
||||
static tanh(n: Decimal.Value): Decimal;
|
||||
static trunc(n: Decimal.Value): Decimal;
|
||||
|
||||
static readonly default?: Decimal.Constructor;
|
||||
static readonly Decimal?: Decimal.Constructor;
|
||||
|
||||
static readonly precision: number;
|
||||
static readonly rounding: Decimal.Rounding;
|
||||
static readonly toExpNeg: number;
|
||||
static readonly toExpPos: number;
|
||||
static readonly minE: number;
|
||||
static readonly maxE: number;
|
||||
static readonly crypto: boolean;
|
||||
static readonly modulo: Decimal.Modulo;
|
||||
|
||||
static readonly ROUND_UP: 0;
|
||||
static readonly ROUND_DOWN: 1;
|
||||
static readonly ROUND_CEIL: 2;
|
||||
static readonly ROUND_FLOOR: 3;
|
||||
static readonly ROUND_HALF_UP: 4;
|
||||
static readonly ROUND_HALF_DOWN: 5;
|
||||
static readonly ROUND_HALF_EVEN: 6;
|
||||
static readonly ROUND_HALF_CEIL: 7;
|
||||
static readonly ROUND_HALF_FLOOR: 8;
|
||||
static readonly EUCLID: 9;
|
||||
}
|
||||
|
||||
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||
[K in keyof A]: Exact<A[K], W[K]>;
|
||||
} : W) : never) | (A extends Narrowable ? A : never);
|
||||
|
||||
export declare function getRuntime(): GetRuntimeOutput;
|
||||
|
||||
declare type GetRuntimeOutput = {
|
||||
id: RuntimeName;
|
||||
prettyName: string;
|
||||
isEdge: boolean;
|
||||
};
|
||||
|
||||
declare class JsonNull extends NullTypesEnumValue {
|
||||
#private;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates more strict variant of an enum which, unlike regular enum,
|
||||
* throws on non-existing property access. This can be useful in following situations:
|
||||
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||
* - enum values are generated dynamically from DMMF.
|
||||
*
|
||||
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||
*
|
||||
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||
* `in` operator or `hasOwnProperty` function.
|
||||
*
|
||||
* @param definition
|
||||
* @returns
|
||||
*/
|
||||
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||
|
||||
declare type Narrowable = string | number | bigint | boolean | [];
|
||||
|
||||
declare class NullTypesEnumValue extends ObjectEnumValue {
|
||||
_getNamespace(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for unique values of object-valued enums.
|
||||
*/
|
||||
declare abstract class ObjectEnumValue {
|
||||
constructor(arg?: symbol);
|
||||
abstract _getNamespace(): string;
|
||||
_getName(): string;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export declare const objectEnumValues: {
|
||||
classes: {
|
||||
DbNull: typeof DbNull;
|
||||
JsonNull: typeof JsonNull;
|
||||
AnyNull: typeof AnyNull;
|
||||
};
|
||||
instances: {
|
||||
DbNull: DbNull;
|
||||
JsonNull: JsonNull;
|
||||
AnyNull: AnyNull;
|
||||
};
|
||||
};
|
||||
|
||||
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||
|
||||
declare namespace Public {
|
||||
export {
|
||||
validator
|
||||
}
|
||||
}
|
||||
export { Public }
|
||||
|
||||
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||
|
||||
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||
|
||||
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||
|
||||
export { }
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,133 @@
|
|||
generator client_msg {
|
||||
provider = "prisma-client-js"
|
||||
output = "clients/msg"
|
||||
}
|
||||
|
||||
datasource db_msg {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL_MSG")
|
||||
}
|
||||
|
||||
model Conversations {
|
||||
id String @id @default(uuid())
|
||||
subject String? @default("New Conversation")
|
||||
status ConversationStatus @default(active)
|
||||
priority Priority @default(normal)
|
||||
category String?
|
||||
userId String
|
||||
adminId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
lastMessageAt DateTime?
|
||||
metadata Json? @default("{}")
|
||||
messages Messages[]
|
||||
|
||||
@@map("conversations")
|
||||
}
|
||||
|
||||
model Messages {
|
||||
id String @id @default(uuid())
|
||||
conversationId String
|
||||
content String
|
||||
messageType MessageType @default(text)
|
||||
senderId String
|
||||
senderType SenderType
|
||||
senderName String?
|
||||
status MessageStatus @default(sent)
|
||||
readAt DateTime?
|
||||
deliveredAt DateTime?
|
||||
metadata Json? @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
conversation Conversations @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([conversationId])
|
||||
@@index([senderId])
|
||||
@@index([createdAt])
|
||||
@@index([status])
|
||||
@@map("messages")
|
||||
}
|
||||
|
||||
model MessageAttachments {
|
||||
id String @id @default(uuid())
|
||||
messageId String
|
||||
fileName String
|
||||
fileSize Int
|
||||
mimeType String
|
||||
fileUrl String
|
||||
thumbnailUrl String?
|
||||
metadata Json? @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([messageId])
|
||||
@@map("message_attachments")
|
||||
}
|
||||
|
||||
model TypingIndicators {
|
||||
id String @id @default(uuid())
|
||||
conversationId String
|
||||
userId String
|
||||
userType SenderType
|
||||
startedAt DateTime @default(now())
|
||||
|
||||
@@unique([conversationId, userId])
|
||||
@@index([conversationId])
|
||||
@@map("typing_indicators")
|
||||
}
|
||||
|
||||
model ConversationParticipants {
|
||||
id String @id @default(uuid())
|
||||
conversationId String
|
||||
userId String
|
||||
userType SenderType
|
||||
role ParticipantRole @default(member)
|
||||
joinedAt DateTime @default(now())
|
||||
leftAt DateTime?
|
||||
|
||||
@@unique([conversationId, userId])
|
||||
@@index([conversationId])
|
||||
@@index([userId])
|
||||
@@map("conversation_participants")
|
||||
}
|
||||
|
||||
enum ConversationStatus {
|
||||
active
|
||||
closed
|
||||
archived
|
||||
}
|
||||
|
||||
enum Priority {
|
||||
low
|
||||
normal
|
||||
high
|
||||
urgent
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
text
|
||||
image
|
||||
file
|
||||
audio
|
||||
video
|
||||
location
|
||||
contact
|
||||
}
|
||||
|
||||
enum SenderType {
|
||||
admin
|
||||
user
|
||||
system
|
||||
}
|
||||
|
||||
enum MessageStatus {
|
||||
sent
|
||||
delivered
|
||||
read
|
||||
failed
|
||||
}
|
||||
|
||||
enum ParticipantRole {
|
||||
admin
|
||||
member
|
||||
observer
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.13.0
|
||||
* Query Engine version: 361e86d0ea4987e9f53a565309b3eed797a6bcbd
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.13.0",
|
||||
engine: "361e86d0ea4987e9f53a565309b3eed797a6bcbd"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.ConversationsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
subject: 'subject',
|
||||
status: 'status',
|
||||
priority: 'priority',
|
||||
category: 'category',
|
||||
userId: 'userId',
|
||||
adminId: 'adminId',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
lastMessageAt: 'lastMessageAt',
|
||||
metadata: 'metadata'
|
||||
};
|
||||
|
||||
exports.Prisma.MessagesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
conversationId: 'conversationId',
|
||||
content: 'content',
|
||||
messageType: 'messageType',
|
||||
senderId: 'senderId',
|
||||
senderType: 'senderType',
|
||||
senderName: 'senderName',
|
||||
status: 'status',
|
||||
readAt: 'readAt',
|
||||
deliveredAt: 'deliveredAt',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.MessageAttachmentsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
messageId: 'messageId',
|
||||
fileName: 'fileName',
|
||||
fileSize: 'fileSize',
|
||||
mimeType: 'mimeType',
|
||||
fileUrl: 'fileUrl',
|
||||
thumbnailUrl: 'thumbnailUrl',
|
||||
metadata: 'metadata',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
exports.Prisma.TypingIndicatorsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
conversationId: 'conversationId',
|
||||
userId: 'userId',
|
||||
userType: 'userType',
|
||||
startedAt: 'startedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.ConversationParticipantsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
conversationId: 'conversationId',
|
||||
userId: 'userId',
|
||||
userType: 'userType',
|
||||
role: 'role',
|
||||
joinedAt: 'joinedAt',
|
||||
leftAt: 'leftAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.NullableJsonNullValueInput = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueFilter = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull,
|
||||
AnyNull: Prisma.AnyNull
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
exports.ConversationStatus = exports.$Enums.ConversationStatus = {
|
||||
active: 'active',
|
||||
closed: 'closed',
|
||||
archived: 'archived'
|
||||
};
|
||||
|
||||
exports.Priority = exports.$Enums.Priority = {
|
||||
low: 'low',
|
||||
normal: 'normal',
|
||||
high: 'high',
|
||||
urgent: 'urgent'
|
||||
};
|
||||
|
||||
exports.MessageType = exports.$Enums.MessageType = {
|
||||
text: 'text',
|
||||
image: 'image',
|
||||
file: 'file',
|
||||
audio: 'audio',
|
||||
video: 'video',
|
||||
location: 'location',
|
||||
contact: 'contact'
|
||||
};
|
||||
|
||||
exports.SenderType = exports.$Enums.SenderType = {
|
||||
admin: 'admin',
|
||||
user: 'user',
|
||||
system: 'system'
|
||||
};
|
||||
|
||||
exports.MessageStatus = exports.$Enums.MessageStatus = {
|
||||
sent: 'sent',
|
||||
delivered: 'delivered',
|
||||
read: 'read',
|
||||
failed: 'failed'
|
||||
};
|
||||
|
||||
exports.ParticipantRole = exports.$Enums.ParticipantRole = {
|
||||
admin: 'admin',
|
||||
member: 'member',
|
||||
observer: 'observer'
|
||||
};
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
Conversations: 'Conversations',
|
||||
Messages: 'Messages',
|
||||
MessageAttachments: 'MessageAttachments',
|
||||
TypingIndicators: 'TypingIndicators',
|
||||
ConversationParticipants: 'ConversationParticipants'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./index"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
module.exports = { ...require('.') }
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./default"
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,975 @@
|
|||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||
/* eslint-disable */
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
const {
|
||||
Decimal,
|
||||
objectEnumValues,
|
||||
makeStrictEnum,
|
||||
Public,
|
||||
getRuntime,
|
||||
skip
|
||||
} = require('./runtime/index-browser.js')
|
||||
|
||||
|
||||
const Prisma = {}
|
||||
|
||||
exports.Prisma = Prisma
|
||||
exports.$Enums = {}
|
||||
|
||||
/**
|
||||
* Prisma Client JS version: 6.13.0
|
||||
* Query Engine version: 361e86d0ea4987e9f53a565309b3eed797a6bcbd
|
||||
*/
|
||||
Prisma.prismaVersion = {
|
||||
client: "6.13.0",
|
||||
engine: "361e86d0ea4987e9f53a565309b3eed797a6bcbd"
|
||||
}
|
||||
|
||||
Prisma.PrismaClientKnownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)};
|
||||
Prisma.PrismaClientUnknownRequestError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientRustPanicError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientInitializationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.PrismaClientValidationError = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.Decimal = Decimal
|
||||
|
||||
/**
|
||||
* Re-export of sql-template-tag
|
||||
*/
|
||||
Prisma.sql = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.empty = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.join = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.raw = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.validator = Public.validator
|
||||
|
||||
/**
|
||||
* Extensions
|
||||
*/
|
||||
Prisma.getExtensionContext = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
Prisma.defineExtension = () => {
|
||||
const runtimeName = getRuntime().prettyName;
|
||||
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||
)}
|
||||
|
||||
/**
|
||||
* Shorthand utilities for JSON filtering
|
||||
*/
|
||||
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||
|
||||
Prisma.NullTypes = {
|
||||
DbNull: objectEnumValues.classes.DbNull,
|
||||
JsonNull: objectEnumValues.classes.JsonNull,
|
||||
AnyNull: objectEnumValues.classes.AnyNull
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
|
||||
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
|
||||
exports.Prisma.Alert_recipientsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
alert_id: 'alert_id',
|
||||
recipient_type: 'recipient_type',
|
||||
recipient_id: 'recipient_id',
|
||||
recipient_name: 'recipient_name',
|
||||
recipient_contact: 'recipient_contact',
|
||||
channel: 'channel',
|
||||
delivery_status: 'delivery_status',
|
||||
sent_at: 'sent_at',
|
||||
delivered_at: 'delivered_at',
|
||||
acknowledged_at: 'acknowledged_at',
|
||||
failed_at: 'failed_at',
|
||||
delivery_attempts: 'delivery_attempts',
|
||||
last_attempt_at: 'last_attempt_at',
|
||||
failure_reason: 'failure_reason',
|
||||
delivery_metadata: 'delivery_metadata',
|
||||
response_data: 'response_data',
|
||||
response_time: 'response_time',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.AlertsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
alert_id: 'alert_id',
|
||||
title: 'title',
|
||||
message: 'message',
|
||||
type: 'type',
|
||||
category: 'category',
|
||||
severity: 'severity',
|
||||
priority: 'priority',
|
||||
status: 'status',
|
||||
is_emergency: 'is_emergency',
|
||||
is_broadcast: 'is_broadcast',
|
||||
is_recurring: 'is_recurring',
|
||||
location: 'location',
|
||||
coordinates_lat: 'coordinates_lat',
|
||||
coordinates_lng: 'coordinates_lng',
|
||||
target_zones: 'target_zones',
|
||||
target_buildings: 'target_buildings',
|
||||
target_roles: 'target_roles',
|
||||
target_teams: 'target_teams',
|
||||
target_users: 'target_users',
|
||||
geofence_ids: 'geofence_ids',
|
||||
scheduled_at: 'scheduled_at',
|
||||
starts_at: 'starts_at',
|
||||
ends_at: 'ends_at',
|
||||
expires_at: 'expires_at',
|
||||
acknowledged_at: 'acknowledged_at',
|
||||
resolved_at: 'resolved_at',
|
||||
recurrence_pattern: 'recurrence_pattern',
|
||||
next_occurrence: 'next_occurrence',
|
||||
content: 'content',
|
||||
attachments: 'attachments',
|
||||
media_urls: 'media_urls',
|
||||
action_buttons: 'action_buttons',
|
||||
channels: 'channels',
|
||||
delivery_config: 'delivery_config',
|
||||
source: 'source',
|
||||
source_id: 'source_id',
|
||||
context: 'context',
|
||||
incident_id: 'incident_id',
|
||||
camera_ids: 'camera_ids',
|
||||
related_alerts: 'related_alerts',
|
||||
total_recipients: 'total_recipients',
|
||||
delivered_count: 'delivered_count',
|
||||
acknowledged_count: 'acknowledged_count',
|
||||
failed_count: 'failed_count',
|
||||
tags: 'tags',
|
||||
metadata: 'metadata',
|
||||
custom_fields: 'custom_fields',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
deleted_at: 'deleted_at',
|
||||
deleted_by: 'deleted_by'
|
||||
};
|
||||
|
||||
exports.Prisma.Basemap_configsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
svg_data: 'svg_data',
|
||||
calibration: 'calibration',
|
||||
dimensions: 'dimensions',
|
||||
is_active: 'is_active',
|
||||
is_default: 'is_default',
|
||||
version: 'version',
|
||||
settings: 'settings',
|
||||
metadata: 'metadata',
|
||||
file_size: 'file_size',
|
||||
checksum: 'checksum',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.Camera_health_logScalarFieldEnum = {
|
||||
id: 'id',
|
||||
camera_id: 'camera_id',
|
||||
status: 'status',
|
||||
response_time: 'response_time',
|
||||
health_score: 'health_score',
|
||||
error_message: 'error_message',
|
||||
stream_accessible: 'stream_accessible',
|
||||
timestamp: 'timestamp'
|
||||
};
|
||||
|
||||
exports.Prisma.CamerasScalarFieldEnum = {
|
||||
id: 'id',
|
||||
label: 'label',
|
||||
area: 'area',
|
||||
lat: 'lat',
|
||||
lng: 'lng',
|
||||
stream_url: 'stream_url',
|
||||
status: 'status',
|
||||
last_heartbeat: 'last_heartbeat',
|
||||
response_time: 'response_time',
|
||||
health_score: 'health_score',
|
||||
error_message: 'error_message',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Feature_flagsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
key: 'key',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
is_enabled: 'is_enabled',
|
||||
type: 'type',
|
||||
value: 'value',
|
||||
default_value: 'default_value',
|
||||
environment: 'environment',
|
||||
category: 'category',
|
||||
tags: 'tags',
|
||||
conditions: 'conditions',
|
||||
rollout_percentage: 'rollout_percentage',
|
||||
user_segments: 'user_segments',
|
||||
is_archived: 'is_archived',
|
||||
is_permanent: 'is_permanent',
|
||||
expires_at: 'expires_at',
|
||||
last_evaluated_at: 'last_evaluated_at',
|
||||
evaluation_count: 'evaluation_count',
|
||||
metadata: 'metadata',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
enabled_at: 'enabled_at',
|
||||
disabled_at: 'disabled_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Geofence_breachesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
geofence_id: 'geofence_id',
|
||||
team_member_id: 'team_member_id',
|
||||
breach_type: 'breach_type',
|
||||
location: 'location',
|
||||
severity: 'severity',
|
||||
is_resolved: 'is_resolved',
|
||||
resolved_at: 'resolved_at',
|
||||
resolved_by: 'resolved_by',
|
||||
notes: 'notes',
|
||||
metadata: 'metadata',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.GeofencesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
type: 'type',
|
||||
coordinates: 'coordinates',
|
||||
description: 'description',
|
||||
is_active: 'is_active',
|
||||
priority: 'priority',
|
||||
notification_settings: 'notification_settings',
|
||||
metadata: 'metadata',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.HousesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
house_number: 'house_number',
|
||||
block: 'block',
|
||||
street: 'street',
|
||||
owner_name: 'owner_name',
|
||||
owner_phone: 'owner_phone',
|
||||
status: 'status',
|
||||
perumahan_id: 'perumahan_id',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Incident_updatesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
incident_id: 'incident_id',
|
||||
update_type: 'update_type',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
old_values: 'old_values',
|
||||
new_values: 'new_values',
|
||||
changed_fields: 'changed_fields',
|
||||
is_internal: 'is_internal',
|
||||
is_system_generated: 'is_system_generated',
|
||||
attachments: 'attachments',
|
||||
created_at: 'created_at',
|
||||
created_by: 'created_by',
|
||||
notifications_sent: 'notifications_sent',
|
||||
notification_status: 'notification_status'
|
||||
};
|
||||
|
||||
exports.Prisma.IncidentsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
incident_number: 'incident_number',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
type: 'type',
|
||||
severity: 'severity',
|
||||
priority: 'priority',
|
||||
status: 'status',
|
||||
location: 'location',
|
||||
coordinates_lat: 'coordinates_lat',
|
||||
coordinates_lng: 'coordinates_lng',
|
||||
address: 'address',
|
||||
zone: 'zone',
|
||||
building: 'building',
|
||||
floor: 'floor',
|
||||
room: 'room',
|
||||
reported_by: 'reported_by',
|
||||
reporter_name: 'reporter_name',
|
||||
reporter_contact: 'reporter_contact',
|
||||
reporter_type: 'reporter_type',
|
||||
assigned_to: 'assigned_to',
|
||||
assigned_team: 'assigned_team',
|
||||
assigned_at: 'assigned_at',
|
||||
occurred_at: 'occurred_at',
|
||||
reported_at: 'reported_at',
|
||||
acknowledged_at: 'acknowledged_at',
|
||||
resolved_at: 'resolved_at',
|
||||
closed_at: 'closed_at',
|
||||
due_date: 'due_date',
|
||||
tags: 'tags',
|
||||
attachments: 'attachments',
|
||||
evidence: 'evidence',
|
||||
witnesses: 'witnesses',
|
||||
related_incidents: 'related_incidents',
|
||||
camera_ids: 'camera_ids',
|
||||
geofence_id: 'geofence_id',
|
||||
alert_id: 'alert_id',
|
||||
metadata: 'metadata',
|
||||
custom_fields: 'custom_fields',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
deleted_at: 'deleted_at',
|
||||
deleted_by: 'deleted_by'
|
||||
};
|
||||
|
||||
exports.Prisma.Ktp_validation_auditScalarFieldEnum = {
|
||||
id: 'id',
|
||||
ktp_data_id: 'ktp_data_id',
|
||||
action: 'action',
|
||||
old_values: 'old_values',
|
||||
new_values: 'new_values',
|
||||
performed_by: 'performed_by',
|
||||
ip_address: 'ip_address',
|
||||
user_agent: 'user_agent',
|
||||
reason: 'reason',
|
||||
created_at: 'created_at',
|
||||
metadata: 'metadata'
|
||||
};
|
||||
|
||||
exports.Prisma.Map_pinsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
coordinates: 'coordinates',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
status: 'status',
|
||||
priority: 'priority',
|
||||
icon_type: 'icon_type',
|
||||
icon_color: 'icon_color',
|
||||
size: 'size',
|
||||
is_visible: 'is_visible',
|
||||
is_clickable: 'is_clickable',
|
||||
metadata: 'metadata',
|
||||
alert_id: 'alert_id',
|
||||
incident_id: 'incident_id',
|
||||
team_member_id: 'team_member_id',
|
||||
camera_id: 'camera_id',
|
||||
geofence_id: 'geofence_id',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.Perumahan_facilitiesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
type: 'type',
|
||||
description: 'description',
|
||||
location: 'location',
|
||||
lat: 'lat',
|
||||
lng: 'lng',
|
||||
status: 'status',
|
||||
operating_hours: 'operating_hours',
|
||||
contact_info: 'contact_info',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Perumahan_infoScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
total_units: 'total_units',
|
||||
clusters: 'clusters',
|
||||
address: 'address',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Qr_codesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
entry_point: 'entry_point',
|
||||
qr_data: 'qr_data',
|
||||
location_lat: 'location_lat',
|
||||
location_lng: 'location_lng',
|
||||
geofence_radius: 'geofence_radius',
|
||||
is_active: 'is_active',
|
||||
description: 'description',
|
||||
max_daily_registrations: 'max_daily_registrations',
|
||||
operating_hours: 'operating_hours',
|
||||
security_level: 'security_level',
|
||||
requires_approval: 'requires_approval',
|
||||
auto_approve_roles: 'auto_approve_roles',
|
||||
metadata: 'metadata',
|
||||
last_used_at: 'last_used_at',
|
||||
usage_count: 'usage_count',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Security_activitiesScalarFieldEnum = {
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
ref_id: 'ref_id',
|
||||
actor: 'actor',
|
||||
note: 'note',
|
||||
severity: 'severity',
|
||||
timestamp: 'timestamp',
|
||||
metadata: 'metadata',
|
||||
source: 'source'
|
||||
};
|
||||
|
||||
exports.Prisma.SequelizemetaScalarFieldEnum = {
|
||||
name: 'name'
|
||||
};
|
||||
|
||||
exports.Prisma.Team_location_historyScalarFieldEnum = {
|
||||
id: 'id',
|
||||
member_id: 'member_id',
|
||||
location: 'location',
|
||||
lat: 'lat',
|
||||
lng: 'lng',
|
||||
activity_type: 'activity_type',
|
||||
timestamp: 'timestamp'
|
||||
};
|
||||
|
||||
exports.Prisma.Team_membersScalarFieldEnum = {
|
||||
id: 'id',
|
||||
nama: 'nama',
|
||||
role: 'role',
|
||||
status: 'status',
|
||||
phone: 'phone',
|
||||
email: 'email',
|
||||
current_location: 'current_location',
|
||||
last_update: 'last_update',
|
||||
shift_start: 'shift_start',
|
||||
shift_end: 'shift_end',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Visitor_ktp_dataScalarFieldEnum = {
|
||||
id: 'id',
|
||||
visitor_registration_id: 'visitor_registration_id',
|
||||
ktp_number: 'ktp_number',
|
||||
full_name: 'full_name',
|
||||
birth_date: 'birth_date',
|
||||
birth_place: 'birth_place',
|
||||
gender: 'gender',
|
||||
address: 'address',
|
||||
rt_rw: 'rt_rw',
|
||||
kelurahan: 'kelurahan',
|
||||
kecamatan: 'kecamatan',
|
||||
religion: 'religion',
|
||||
marital_status: 'marital_status',
|
||||
occupation: 'occupation',
|
||||
nationality: 'nationality',
|
||||
ktp_photo_url: 'ktp_photo_url',
|
||||
ktp_photo_hash: 'ktp_photo_hash',
|
||||
validation_status: 'validation_status',
|
||||
validation_confidence: 'validation_confidence',
|
||||
validation_notes: 'validation_notes',
|
||||
validated_by: 'validated_by',
|
||||
validated_at: 'validated_at',
|
||||
ocr_raw_data: 'ocr_raw_data',
|
||||
manual_corrections: 'manual_corrections',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.Visitor_registrationsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
phone: 'phone',
|
||||
photo_url: 'photo_url',
|
||||
purpose: 'purpose',
|
||||
entry_point: 'entry_point',
|
||||
location_lat: 'location_lat',
|
||||
location_lng: 'location_lng',
|
||||
qr_code_id: 'qr_code_id',
|
||||
status: 'status',
|
||||
approved_by: 'approved_by',
|
||||
approved_at: 'approved_at',
|
||||
rejected_by: 'rejected_by',
|
||||
rejected_at: 'rejected_at',
|
||||
rejection_reason: 'rejection_reason',
|
||||
expires_at: 'expires_at',
|
||||
checked_in_at: 'checked_in_at',
|
||||
checked_out_at: 'checked_out_at',
|
||||
metadata: 'metadata',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
created_at: 'created_at',
|
||||
updated_at: 'updated_at',
|
||||
location_address: 'location_address',
|
||||
location_updated_at: 'location_updated_at'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
|
||||
exports.Prisma.NullableJsonNullValueInput = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueInput = {
|
||||
JsonNull: Prisma.JsonNull
|
||||
};
|
||||
|
||||
exports.Prisma.JsonNullValueFilter = {
|
||||
DbNull: Prisma.DbNull,
|
||||
JsonNull: Prisma.JsonNull,
|
||||
AnyNull: Prisma.AnyNull
|
||||
};
|
||||
|
||||
exports.Prisma.QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
|
||||
exports.Prisma.NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
|
||||
exports.Prisma.alert_recipientsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
alert_id: 'alert_id',
|
||||
recipient_type: 'recipient_type',
|
||||
recipient_id: 'recipient_id',
|
||||
recipient_name: 'recipient_name',
|
||||
channel: 'channel',
|
||||
delivery_status: 'delivery_status',
|
||||
failure_reason: 'failure_reason'
|
||||
};
|
||||
|
||||
exports.Prisma.alertsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
alert_id: 'alert_id',
|
||||
title: 'title',
|
||||
message: 'message',
|
||||
type: 'type',
|
||||
category: 'category',
|
||||
severity: 'severity',
|
||||
priority: 'priority',
|
||||
status: 'status',
|
||||
source: 'source',
|
||||
source_id: 'source_id',
|
||||
incident_id: 'incident_id',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
deleted_by: 'deleted_by'
|
||||
};
|
||||
|
||||
exports.Prisma.basemap_configsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
svg_data: 'svg_data',
|
||||
checksum: 'checksum',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.camera_health_logOrderByRelevanceFieldEnum = {
|
||||
camera_id: 'camera_id',
|
||||
status: 'status',
|
||||
error_message: 'error_message'
|
||||
};
|
||||
|
||||
exports.Prisma.camerasOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
label: 'label',
|
||||
area: 'area',
|
||||
stream_url: 'stream_url',
|
||||
error_message: 'error_message'
|
||||
};
|
||||
|
||||
exports.Prisma.feature_flagsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
key: 'key',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
type: 'type',
|
||||
environment: 'environment',
|
||||
category: 'category',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.geofence_breachesOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
geofence_id: 'geofence_id',
|
||||
team_member_id: 'team_member_id',
|
||||
breach_type: 'breach_type',
|
||||
severity: 'severity',
|
||||
resolved_by: 'resolved_by',
|
||||
notes: 'notes'
|
||||
};
|
||||
|
||||
exports.Prisma.geofencesOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
type: 'type',
|
||||
description: 'description',
|
||||
priority: 'priority',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.housesOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
house_number: 'house_number',
|
||||
block: 'block',
|
||||
street: 'street',
|
||||
owner_name: 'owner_name',
|
||||
owner_phone: 'owner_phone',
|
||||
perumahan_id: 'perumahan_id'
|
||||
};
|
||||
|
||||
exports.Prisma.incident_updatesOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
incident_id: 'incident_id',
|
||||
update_type: 'update_type',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
created_by: 'created_by',
|
||||
notification_status: 'notification_status'
|
||||
};
|
||||
|
||||
exports.Prisma.incidentsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
incident_number: 'incident_number',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
type: 'type',
|
||||
severity: 'severity',
|
||||
priority: 'priority',
|
||||
status: 'status',
|
||||
address: 'address',
|
||||
zone: 'zone',
|
||||
building: 'building',
|
||||
floor: 'floor',
|
||||
room: 'room',
|
||||
reported_by: 'reported_by',
|
||||
reporter_name: 'reporter_name',
|
||||
reporter_contact: 'reporter_contact',
|
||||
reporter_type: 'reporter_type',
|
||||
assigned_to: 'assigned_to',
|
||||
assigned_team: 'assigned_team',
|
||||
geofence_id: 'geofence_id',
|
||||
alert_id: 'alert_id',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
deleted_by: 'deleted_by'
|
||||
};
|
||||
|
||||
exports.Prisma.ktp_validation_auditOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
ktp_data_id: 'ktp_data_id',
|
||||
performed_by: 'performed_by',
|
||||
ip_address: 'ip_address',
|
||||
user_agent: 'user_agent',
|
||||
reason: 'reason'
|
||||
};
|
||||
|
||||
exports.Prisma.map_pinsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
status: 'status',
|
||||
priority: 'priority',
|
||||
icon_type: 'icon_type',
|
||||
icon_color: 'icon_color',
|
||||
size: 'size',
|
||||
alert_id: 'alert_id',
|
||||
incident_id: 'incident_id',
|
||||
team_member_id: 'team_member_id',
|
||||
camera_id: 'camera_id',
|
||||
geofence_id: 'geofence_id',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.perumahan_facilitiesOrderByRelevanceFieldEnum = {
|
||||
name: 'name',
|
||||
type: 'type',
|
||||
description: 'description',
|
||||
location: 'location'
|
||||
};
|
||||
|
||||
exports.Prisma.perumahan_infoOrderByRelevanceFieldEnum = {
|
||||
name: 'name',
|
||||
address: 'address'
|
||||
};
|
||||
|
||||
exports.Prisma.qr_codesOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
entry_point: 'entry_point',
|
||||
qr_data: 'qr_data',
|
||||
description: 'description',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.security_activitiesOrderByRelevanceFieldEnum = {
|
||||
type: 'type',
|
||||
ref_id: 'ref_id',
|
||||
actor: 'actor',
|
||||
note: 'note',
|
||||
source: 'source'
|
||||
};
|
||||
|
||||
exports.Prisma.sequelizemetaOrderByRelevanceFieldEnum = {
|
||||
name: 'name'
|
||||
};
|
||||
|
||||
exports.Prisma.team_location_historyOrderByRelevanceFieldEnum = {
|
||||
member_id: 'member_id',
|
||||
location: 'location',
|
||||
activity_type: 'activity_type'
|
||||
};
|
||||
|
||||
exports.Prisma.team_membersOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
nama: 'nama',
|
||||
role: 'role',
|
||||
phone: 'phone',
|
||||
email: 'email',
|
||||
current_location: 'current_location'
|
||||
};
|
||||
|
||||
exports.Prisma.visitor_ktp_dataOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
visitor_registration_id: 'visitor_registration_id',
|
||||
ktp_number: 'ktp_number',
|
||||
full_name: 'full_name',
|
||||
birth_place: 'birth_place',
|
||||
address: 'address',
|
||||
rt_rw: 'rt_rw',
|
||||
kelurahan: 'kelurahan',
|
||||
kecamatan: 'kecamatan',
|
||||
religion: 'religion',
|
||||
marital_status: 'marital_status',
|
||||
occupation: 'occupation',
|
||||
nationality: 'nationality',
|
||||
ktp_photo_url: 'ktp_photo_url',
|
||||
ktp_photo_hash: 'ktp_photo_hash',
|
||||
validation_notes: 'validation_notes',
|
||||
validated_by: 'validated_by'
|
||||
};
|
||||
|
||||
exports.Prisma.visitor_registrationsOrderByRelevanceFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
phone: 'phone',
|
||||
photo_url: 'photo_url',
|
||||
purpose: 'purpose',
|
||||
entry_point: 'entry_point',
|
||||
qr_code_id: 'qr_code_id',
|
||||
approved_by: 'approved_by',
|
||||
rejected_by: 'rejected_by',
|
||||
rejection_reason: 'rejection_reason',
|
||||
created_by: 'created_by',
|
||||
updated_by: 'updated_by',
|
||||
location_address: 'location_address'
|
||||
};
|
||||
exports.cameras_status = exports.$Enums.cameras_status = {
|
||||
online: 'online',
|
||||
offline: 'offline',
|
||||
degraded: 'degraded',
|
||||
error: 'error'
|
||||
};
|
||||
|
||||
exports.houses_status = exports.$Enums.houses_status = {
|
||||
active: 'active',
|
||||
inactive: 'inactive'
|
||||
};
|
||||
|
||||
exports.ktp_validation_audit_action = exports.$Enums.ktp_validation_audit_action = {
|
||||
created: 'created',
|
||||
updated: 'updated',
|
||||
verified: 'verified',
|
||||
rejected: 'rejected',
|
||||
manual_override: 'manual_override',
|
||||
duplicate_detected: 'duplicate_detected',
|
||||
validation_error: 'validation_error',
|
||||
manual_validation: 'manual_validation',
|
||||
manual_validation_error: 'manual_validation_error'
|
||||
};
|
||||
|
||||
exports.perumahan_facilities_status = exports.$Enums.perumahan_facilities_status = {
|
||||
active: 'active',
|
||||
inactive: 'inactive',
|
||||
maintenance: 'maintenance'
|
||||
};
|
||||
|
||||
exports.qr_codes_security_level = exports.$Enums.qr_codes_security_level = {
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
restricted: 'restricted'
|
||||
};
|
||||
|
||||
exports.security_activities_severity = exports.$Enums.security_activities_severity = {
|
||||
INFO: 'INFO',
|
||||
WARNING: 'WARNING',
|
||||
ERROR: 'ERROR',
|
||||
CRITICAL: 'CRITICAL'
|
||||
};
|
||||
|
||||
exports.team_members_status = exports.$Enums.team_members_status = {
|
||||
ON_DUTY: 'ON_DUTY',
|
||||
OFF_DUTY: 'OFF_DUTY',
|
||||
PATROLLING: 'PATROLLING',
|
||||
BREAK: 'BREAK'
|
||||
};
|
||||
|
||||
exports.visitor_ktp_data_gender = exports.$Enums.visitor_ktp_data_gender = {
|
||||
L: 'L',
|
||||
P: 'P'
|
||||
};
|
||||
|
||||
exports.visitor_ktp_data_validation_status = exports.$Enums.visitor_ktp_data_validation_status = {
|
||||
pending: 'pending',
|
||||
processing: 'processing',
|
||||
verified: 'verified',
|
||||
rejected: 'rejected',
|
||||
manual_review: 'manual_review'
|
||||
};
|
||||
|
||||
exports.visitor_registrations_status = exports.$Enums.visitor_registrations_status = {
|
||||
pending: 'pending',
|
||||
approved: 'approved',
|
||||
rejected: 'rejected',
|
||||
expired: 'expired',
|
||||
checked_in: 'checked_in',
|
||||
checked_out: 'checked_out'
|
||||
};
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
alert_recipients: 'alert_recipients',
|
||||
alerts: 'alerts',
|
||||
basemap_configs: 'basemap_configs',
|
||||
camera_health_log: 'camera_health_log',
|
||||
cameras: 'cameras',
|
||||
feature_flags: 'feature_flags',
|
||||
geofence_breaches: 'geofence_breaches',
|
||||
geofences: 'geofences',
|
||||
houses: 'houses',
|
||||
incident_updates: 'incident_updates',
|
||||
incidents: 'incidents',
|
||||
ktp_validation_audit: 'ktp_validation_audit',
|
||||
map_pins: 'map_pins',
|
||||
perumahan_facilities: 'perumahan_facilities',
|
||||
perumahan_info: 'perumahan_info',
|
||||
qr_codes: 'qr_codes',
|
||||
security_activities: 'security_activities',
|
||||
sequelizemeta: 'sequelizemeta',
|
||||
team_location_history: 'team_location_history',
|
||||
team_members: 'team_members',
|
||||
visitor_ktp_data: 'visitor_ktp_data',
|
||||
visitor_registrations: 'visitor_registrations'
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a stub Prisma Client that will error at runtime if called.
|
||||
*/
|
||||
class PrismaClient {
|
||||
constructor() {
|
||||
return new Proxy(this, {
|
||||
get(target, prop) {
|
||||
let message
|
||||
const runtime = getRuntime()
|
||||
if (runtime.isEdge) {
|
||||
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||
`;
|
||||
} else {
|
||||
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||
}
|
||||
|
||||
message += `
|
||||
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.PrismaClient = PrismaClient
|
||||
|
||||
Object.assign(exports, Prisma)
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue