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
-
13api/src/billing/billing.service.ts
-
6api/src/billing/schemas/plan.schema.ts
-
3api/src/gateway/gateway.controller.ts
-
6api/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", |
"extends": "./tsconfig.json", |
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] |
|
||||
|
"include": ["src/**/*"] |
||||
} |
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue