20 changed files with 406 additions and 31 deletions
-
3.gitignore
-
5api/package.json
-
78api/scripts/fix-database.js
-
30api/scripts/seed-database.js
-
30api/scripts/seed-database.ts
-
38api/scripts/seed.sh
-
2api/src/app.module.ts
-
12api/src/auth/auth.controller.ts
-
11api/src/billing/billing.service.ts
-
6api/src/billing/schemas/plan.schema.ts
-
3api/src/gateway/gateway.controller.ts
-
4api/src/main.ts
-
12api/src/seed.ts
-
65api/src/seeds/admin.seed.ts
-
79api/src/seeds/plan.seed.ts
-
22api/src/seeds/seed.module.ts
-
2api/tsconfig.build.json
-
3api/tsconfig.json
-
26docker-compose.yaml
-
2web/lib/httpServerClient.ts
@ -0,0 +1,78 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
const { NestFactory } = require('@nestjs/core'); |
|||
const { AppModule } = require('../dist/app.module'); |
|||
const { getConnectionToken } = require('@nestjs/mongoose'); |
|||
|
|||
async function fixDatabase() { |
|||
console.log('🔧 Fixing database schema issues...'); |
|||
|
|||
try { |
|||
const app = await NestFactory.create(AppModule, { |
|||
logger: ['error', 'warn', 'log'], |
|||
}); |
|||
|
|||
const connection = app.get(getConnectionToken()); |
|||
const db = connection.db; |
|||
|
|||
console.log('📋 Checking current plans...'); |
|||
const plans = await db.collection('plans').find({}).toArray(); |
|||
console.log('Current plans:', plans.map(p => ({ name: p.name, polarProductId: p.polarProductId }))); |
|||
|
|||
console.log('🧹 Removing polarProductId from existing plans...'); |
|||
await db.collection('plans').updateMany( |
|||
{ name: { $in: ['free', 'mega'] } }, |
|||
{ $unset: { polarProductId: "" } } |
|||
); |
|||
console.log('✅ Removed polarProductId from basic plans'); |
|||
|
|||
console.log('🗑️ Dropping unique index on polarProductId...'); |
|||
try { |
|||
await db.collection('plans').dropIndex('polarProductId_1'); |
|||
console.log('✅ Unique index dropped successfully'); |
|||
} catch (error) { |
|||
if (error.code === 27) { |
|||
console.log('ℹ️ Index does not exist, continuing...'); |
|||
} else { |
|||
console.log('⚠️ Error dropping index:', error.message); |
|||
} |
|||
} |
|||
|
|||
console.log('🆔 Creating new sparse index on polarProductId...'); |
|||
try { |
|||
await db.collection('plans').createIndex( |
|||
{ polarProductId: 1 }, |
|||
{ |
|||
unique: true, |
|||
sparse: true, // This allows multiple null/undefined values
|
|||
name: 'polarProductId_sparse_1' |
|||
} |
|||
); |
|||
console.log('✅ New sparse index created'); |
|||
} catch (error) { |
|||
console.log('⚠️ Index creation error (may already exist):', error.message); |
|||
} |
|||
|
|||
console.log('🧹 Cleaning up any duplicate plans...'); |
|||
const megaPlans = await db.collection('plans').find({ name: 'mega' }).toArray(); |
|||
if (megaPlans.length > 1) { |
|||
console.log(`Found ${megaPlans.length} mega plans, keeping the first one...`); |
|||
for (let i = 1; i < megaPlans.length; i++) { |
|||
await db.collection('plans').deleteOne({ _id: megaPlans[i]._id }); |
|||
} |
|||
} |
|||
|
|||
console.log('📋 Final plans check...'); |
|||
const finalPlans = await db.collection('plans').find({}).toArray(); |
|||
console.log('Final plans:', finalPlans.map(p => ({ name: p.name, polarProductId: p.polarProductId }))); |
|||
|
|||
console.log('✅ Database schema fixed successfully!'); |
|||
await app.close(); |
|||
process.exit(0); |
|||
} catch (error) { |
|||
console.error('❌ Database fix failed:', error); |
|||
process.exit(1); |
|||
} |
|||
} |
|||
|
|||
fixDatabase(); |
|||
@ -0,0 +1,30 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
const { NestFactory } = require('@nestjs/core'); |
|||
const { AppModule } = require('../dist/app.module'); |
|||
const { AdminSeed } = require('../dist/seeds/admin.seed'); |
|||
|
|||
async function bootstrap() { |
|||
console.log('🌱 Starting database seeding...'); |
|||
|
|||
try { |
|||
const app = await NestFactory.create(AppModule, { |
|||
logger: ['error', 'warn', 'log'], |
|||
}); |
|||
|
|||
console.log('📦 Application created, getting seeder...'); |
|||
const seeder = app.get(AdminSeed); |
|||
|
|||
console.log('🚀 Running seed process...'); |
|||
await seeder.seed(); |
|||
|
|||
console.log('✅ Database seeding completed successfully!'); |
|||
await app.close(); |
|||
process.exit(0); |
|||
} catch (error) { |
|||
console.error('❌ Database seeding failed:', error); |
|||
process.exit(1); |
|||
} |
|||
} |
|||
|
|||
bootstrap(); |
|||
@ -0,0 +1,30 @@ |
|||
#!/usr/bin/env node |
|||
|
|||
import { NestFactory } from '@nestjs/core'; |
|||
import { AppModule } from '../src/app.module'; |
|||
import { AdminSeed } from '../src/seeds/admin.seed'; |
|||
|
|||
async function bootstrap() { |
|||
console.log('🌱 Starting database seeding...'); |
|||
|
|||
try { |
|||
const app = await NestFactory.create(AppModule, { |
|||
logger: ['error', 'warn', 'log'], |
|||
}); |
|||
|
|||
console.log('📦 Application created, getting seeder...'); |
|||
const seeder = app.get(AdminSeed); |
|||
|
|||
console.log('🚀 Running seed process...'); |
|||
await seeder.seed(); |
|||
|
|||
console.log('✅ Database seeding completed successfully!'); |
|||
await app.close(); |
|||
process.exit(0); |
|||
} catch (error) { |
|||
console.error('❌ Database seeding failed:', error); |
|||
process.exit(1); |
|||
} |
|||
} |
|||
|
|||
bootstrap(); |
|||
@ -0,0 +1,38 @@ |
|||
#!/bin/bash |
|||
|
|||
echo "🌱 TextBee Database Seeding Script" |
|||
echo "==================================" |
|||
|
|||
# Check if we're in the right directory |
|||
if [ ! -f "package.json" ]; then |
|||
echo "❌ Error: package.json not found. Please run this script from the API directory." |
|||
exit 1 |
|||
fi |
|||
|
|||
# Check if dist directory exists (compiled TypeScript) |
|||
if [ ! -d "dist" ]; then |
|||
echo "📦 Building the application..." |
|||
npm run build |
|||
if [ $? -ne 0 ]; then |
|||
echo "❌ Build failed. Please check your code for errors." |
|||
exit 1 |
|||
fi |
|||
fi |
|||
|
|||
echo "🚀 Starting database seeding..." |
|||
|
|||
# Run the seeding script |
|||
node dist/seed.js |
|||
|
|||
if [ $? -eq 0 ]; then |
|||
echo "✅ Database seeding completed successfully!" |
|||
echo "" |
|||
echo "📋 What was created:" |
|||
echo " • Admin user: ${ADMIN_EMAIL:-admin@example.com}" |
|||
echo " • Free plan: 10 daily, 100 monthly messages" |
|||
echo " • Mega plan: Unlimited messages" |
|||
echo " • Admin user assigned to Mega plan" |
|||
else |
|||
echo "❌ Database seeding failed. Check the logs above for details." |
|||
exit 1 |
|||
fi |
|||
@ -0,0 +1,12 @@ |
|||
import { NestFactory } from '@nestjs/core'; |
|||
import { AppModule } from './app.module'; |
|||
import { AdminSeed } from './seeds/admin.seed'; |
|||
|
|||
async function bootstrap() { |
|||
const app = await NestFactory.create(AppModule); |
|||
const seeder = app.get(AdminSeed); |
|||
await seeder.seed(); |
|||
await app.close(); |
|||
} |
|||
|
|||
bootstrap(); |
|||
@ -0,0 +1,65 @@ |
|||
import { Injectable } from '@nestjs/common'; |
|||
import { UsersService } from '../users/users.service'; |
|||
import { UserRole } from '../users/user-roles.enum'; |
|||
import * as bcrypt from 'bcryptjs'; |
|||
import { AuthService } from '../auth/auth.service'; |
|||
import { BillingService } from '../billing/billing.service'; |
|||
import { InjectModel } from '@nestjs/mongoose'; |
|||
import { Plan, PlanDocument } from '../billing/schemas/plan.schema'; |
|||
import { Model } from 'mongoose'; |
|||
import { PlanSeed } from './plan.seed'; |
|||
|
|||
@Injectable() |
|||
export class AdminSeed { |
|||
constructor( |
|||
private readonly usersService: UsersService, |
|||
private readonly authService: AuthService, |
|||
private readonly billingService: BillingService, |
|||
@InjectModel(Plan.name) private planModel: Model<PlanDocument>, |
|||
private readonly planSeed: PlanSeed, |
|||
) {} |
|||
|
|||
async seed() { |
|||
await this.planSeed.seed(); |
|||
const adminEmail = process.env.ADMIN_EMAIL || 'admin@example.com'; |
|||
let adminUser = await this.usersService.findOne({ email: adminEmail }); |
|||
|
|||
if (!adminUser) { |
|||
const adminPassword = process.env.ADMIN_PASSWORD || 'password'; |
|||
const adminName = 'Admin'; |
|||
|
|||
// Register the user using AuthService.register
|
|||
const { user } = await this.authService.register({ |
|||
name: adminName, |
|||
email: adminEmail, |
|||
password: adminPassword, |
|||
}); |
|||
|
|||
// Assign ADMIN role
|
|||
user.role = UserRole.ADMIN; |
|||
adminUser = await user.save(); |
|||
|
|||
console.log('Admin user created successfully.'); |
|||
} |
|||
|
|||
// Check if the user has an active subscription
|
|||
const subscription = await this.billingService.getActiveSubscription(adminUser._id.toString()); |
|||
if (subscription && subscription.plan.name !== 'free') { |
|||
return; |
|||
} |
|||
|
|||
// Assign the best plan
|
|||
const bestPlan = await this.planModel.findOne({ name: 'mega' }); |
|||
if (bestPlan) { |
|||
await this.billingService.switchPlan({ |
|||
userId: adminUser._id.toString(), |
|||
newPlanName: bestPlan.name, |
|||
status: 'active', |
|||
amount: bestPlan.monthlyPrice, |
|||
}); |
|||
console.log(`Admin user subscribed to ${bestPlan.name} plan.`); |
|||
} else { |
|||
console.warn('No unlimited plan found to assign to admin user. Defaulting to free plan.'); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
import { Injectable } from '@nestjs/common'; |
|||
import { InjectModel } from '@nestjs/mongoose'; |
|||
import { Model } from 'mongoose'; |
|||
import { Plan, PlanDocument } from '../billing/schemas/plan.schema'; |
|||
|
|||
@Injectable() |
|||
export class PlanSeed { |
|||
constructor( |
|||
@InjectModel(Plan.name) private planModel: Model<PlanDocument>, |
|||
) {} |
|||
|
|||
async seed() { |
|||
// Check if free plan exists, if not create it
|
|||
const existingFreePlan = await this.planModel.findOne({ name: 'free' }); |
|||
if (!existingFreePlan) { |
|||
await this.planModel.create({ |
|||
name: 'free', |
|||
dailyLimit: 10, |
|||
monthlyLimit: 100, |
|||
bulkSendLimit: 10, |
|||
isActive: true, |
|||
monthlyPrice: 0, |
|||
yearlyPrice: 0, |
|||
polarProductId: 'textbee-free-plan', |
|||
polarMonthlyProductId: 'free', |
|||
polarYearlyProductId: 'free' |
|||
}); |
|||
console.log('Free plan created successfully.'); |
|||
} else { |
|||
// Update existing free plan
|
|||
await this.planModel.updateOne({ name: 'free' }, { |
|||
dailyLimit: 10, |
|||
monthlyLimit: 100, |
|||
bulkSendLimit: 10, |
|||
isActive: true, |
|||
monthlyPrice: 0, |
|||
yearlyPrice: 0, |
|||
polarProductId: 'textbee-free-plan', |
|||
polarMonthlyProductId: 'free', |
|||
polarYearlyProductId: 'free' |
|||
}); |
|||
console.log('Free plan updated successfully.'); |
|||
} |
|||
|
|||
// Check if mega plan exists, if not create it
|
|||
const existingMegaPlan = await this.planModel.findOne({ name: 'mega' }); |
|||
if (!existingMegaPlan) { |
|||
console.log("Mega creating") |
|||
await this.planModel.create({ |
|||
name: 'mega', |
|||
dailyLimit: -1, // unlimited
|
|||
monthlyLimit: -1, // unlimited
|
|||
bulkSendLimit: -1, // unlimited
|
|||
isActive: true, |
|||
monthlyPrice: 99900, // $999.00
|
|||
yearlyPrice: 999000, // $9990.00
|
|||
polarProductId: 'textbee-mega-plan', |
|||
polarMonthlyProductId: 'mega', |
|||
polarYearlyProductId: 'mega' |
|||
}); |
|||
console.log('Mega plan created successfully.'); |
|||
} else { |
|||
// Update existing mega plan
|
|||
console.log("Mega updating") |
|||
await this.planModel.updateOne({ name: 'mega' }, { |
|||
dailyLimit: -1, |
|||
monthlyLimit: -1, |
|||
bulkSendLimit: -1, |
|||
isActive: true, |
|||
monthlyPrice: 99900, |
|||
yearlyPrice: 999000, |
|||
polarProductId: 'textbee-mega-plan', |
|||
polarMonthlyProductId: 'mega', |
|||
polarYearlyProductId: 'mega' |
|||
}); |
|||
console.log('Mega plan updated successfully.'); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
import { Module } from '@nestjs/common'; |
|||
import { AdminSeed } from './admin.seed'; |
|||
import { UsersModule } from '../users/users.module'; |
|||
import { AuthModule } from '../auth/auth.module'; |
|||
import { BillingModule } from '../billing/billing.module'; |
|||
import { MongooseModule } from '@nestjs/mongoose'; |
|||
import { Plan, PlanSchema } from '../billing/schemas/plan.schema'; |
|||
import { PlanSeed } from './plan.seed'; |
|||
|
|||
@Module({ |
|||
imports: [ |
|||
UsersModule, |
|||
AuthModule, |
|||
BillingModule, |
|||
MongooseModule.forFeature([ |
|||
{ name: Plan.name, schema: PlanSchema }, |
|||
]), |
|||
], |
|||
providers: [AdminSeed, PlanSeed], |
|||
exports: [AdminSeed, PlanSeed], |
|||
}) |
|||
export class SeedModule {} |
|||
@ -1,4 +1,4 @@ |
|||
{ |
|||
"extends": "./tsconfig.json", |
|||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] |
|||
"include": ["src/**/*"] |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue