Browse Source

fix

pull/123/head
Yafeng Wang 7 months ago
parent
commit
81c5c06d5c
  1. 3
      .gitignore
  2. 5
      api/package.json
  3. 78
      api/scripts/fix-database.js
  4. 30
      api/scripts/seed-database.js
  5. 30
      api/scripts/seed-database.ts
  6. 38
      api/scripts/seed.sh
  7. 2
      api/src/app.module.ts
  8. 12
      api/src/auth/auth.controller.ts
  9. 11
      api/src/billing/billing.service.ts
  10. 6
      api/src/billing/schemas/plan.schema.ts
  11. 3
      api/src/gateway/gateway.controller.ts
  12. 4
      api/src/main.ts
  13. 12
      api/src/seed.ts
  14. 65
      api/src/seeds/admin.seed.ts
  15. 79
      api/src/seeds/plan.seed.ts
  16. 22
      api/src/seeds/seed.module.ts
  17. 2
      api/tsconfig.build.json
  18. 3
      api/tsconfig.json
  19. 26
      docker-compose.yaml
  20. 2
      web/lib/httpServerClient.ts

3
.gitignore

@ -5,3 +5,6 @@ android/key.properties
.idea/
*.keystore
android/app/google-services.json
web/.env
api/.env
.env

5
api/package.json

@ -17,7 +17,10 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"seed": "node dist/seed.js",
"seed:build": "npm run build && npm run seed",
"seed:docker": "echo '🌱 Starting database seeding...' && node dist/seed.js && echo '✅ Database seeding completed!'"
},
"dependencies": {
"@nest-modules/mailer": "^1.3.22",

78
api/scripts/fix-database.js

@ -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();

30
api/scripts/seed-database.js

@ -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();

30
api/scripts/seed-database.ts

@ -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();

38
api/scripts/seed.sh

@ -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

2
api/src/app.module.ts

@ -19,6 +19,7 @@ import { BillingModule } from './billing/billing.module'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { BullModule } from '@nestjs/bull'
import { SupportModule } from './support/support.module'
import { SeedModule } from './seeds/seed.module'
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
@ -58,6 +59,7 @@ export class LoggerMiddleware implements NestMiddleware {
WebhookModule,
BillingModule,
SupportModule,
SeedModule,
],
controllers: [],
providers: [

12
api/src/auth/auth.controller.ts

@ -43,12 +43,12 @@ export class AuthController {
return { data }
}
@ApiOperation({ summary: 'Register' })
@Post('/register')
async register(@Body() input: RegisterInputDTO) {
const data = await this.authService.register(input)
return { data }
}
// @ApiOperation({ summary: 'Register' })
// @Post('/register')
// async register(@Body() input: RegisterInputDTO) {
// const data = await this.authService.register(input)
// return { data }
// }
@ApiOperation({ summary: 'Get current logged in user' })
@ApiBearerAuth()

11
api/src/billing/billing.service.ts

@ -199,7 +199,8 @@ export class BillingService {
const plans = await this.planModel.find()
const customPlans = plans.filter((plan) => plan.name?.startsWith('custom'))
const proPlan = plans.find((plan) => plan.name === 'pro')
console.log('plans', plans);
const megaPlan = plans.find((plan) => plan.name === 'mega')
const freePlan = plans.find((plan) => plan.name === 'free')
const customPlanSubscription = await this.subscriptionModel.findOne({
@ -212,14 +213,14 @@ export class BillingService {
return customPlanSubscription.populate('plan')
}
const proPlanSubscription = await this.subscriptionModel.findOne({
const megaPlanSubscription = await this.subscriptionModel.findOne({
user: user._id,
plan: proPlan._id,
plan: megaPlan._id,
isActive: true,
})
if (proPlanSubscription) {
return proPlanSubscription.populate('plan')
if (megaPlanSubscription) {
return megaPlanSubscription.populate('plan')
}
const freePlanSubscription = await this.subscriptionModel.findOne({

6
api/src/billing/schemas/plan.schema.ts

@ -23,13 +23,13 @@ export class Plan {
@Prop({})
yearlyPrice: number // in cents
@Prop({ type: String, unique: true })
@Prop({ type: String })
polarProductId?: string
@Prop({ type: String, unique: true })
@Prop({ type: String })
polarMonthlyProductId?: string
@Prop({ type: String, unique: true })
@Prop({ type: String })
polarYearlyProductId?: string
@Prop({ type: Boolean, default: true })

3
api/src/gateway/gateway.controller.ts

@ -47,6 +47,7 @@ export class GatewayController {
@ApiOperation({ summary: 'Register device' })
@Post('/devices')
async registerDevice(@Body() input: RegisterDeviceInputDTO, @Request() req) {
console.log('Hello World 2')
const data = await this.gatewayService.registerDevice(input, req.user)
return { data }
}
@ -55,6 +56,7 @@ export class GatewayController {
@ApiOperation({ summary: 'List of registered devices' })
@Get('/devices')
async getDevices(@Request() req) {
console.log('Hello World 1')
const data = await this.gatewayService.getDevicesForUser(req.user)
return { data }
}
@ -66,6 +68,7 @@ export class GatewayController {
@Param('id') deviceId: string,
@Body() input: RegisterDeviceInputDTO,
) {
console.log('Hello World')
const data = await this.gatewayService.updateDevice(deviceId, input)
return { data }
}

4
api/src/main.ts

@ -5,7 +5,8 @@ import { AppModule } from './app.module'
import * as firebase from 'firebase-admin'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import * as express from 'express'
import { NestExpressApplication } from '@nestjs/platform-express'
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app: NestExpressApplication = await NestFactory.create(AppModule)
@ -58,6 +59,7 @@ async function bootstrap() {
)
app.useBodyParser('json', { limit: '2mb' });
app.enableCors()
await app.listen(PORT)
}
bootstrap()

12
api/src/seed.ts

@ -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();

65
api/src/seeds/admin.seed.ts

@ -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.');
}
}
}

79
api/src/seeds/plan.seed.ts

@ -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.');
}
}
}

22
api/src/seeds/seed.module.ts

@ -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 {}

2
api/tsconfig.build.json

@ -1,4 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
"include": ["src/**/*"]
}

3
api/tsconfig.json

@ -18,5 +18,6 @@
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
},
"include": ["src/**/*"]
}

26
docker-compose.yaml

@ -5,8 +5,8 @@ services:
image: mongo:latest
restart: always
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_ROOT_USER:-adminUser}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASS:-adminPassword}
- MONGO_INITDB_ROOT_USERNAME=${MONGO_ROOT_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASS}
- MONGO_INITDB_DATABASE=textbee
volumes:
# - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
@ -30,8 +30,8 @@ services:
ports:
- "${MONGO_EXPRESS_PORT:-8081}:8081"
environment:
- ME_CONFIG_MONGODB_ADMINUSERNAME=${MONGO_ROOT_USER:-adminUser}
- ME_CONFIG_MONGODB_ADMINPASSWORD=${MONGO_ROOT_PASS:-adminPassword}
- ME_CONFIG_MONGODB_ADMINUSERNAME=${MONGO_ROOT_USER}
- ME_CONFIG_MONGODB_ADMINPASSWORD=${MONGO_ROOT_PASS}
- ME_CONFIG_MONGODB_SERVER=textbee-db
depends_on:
textbee-db:
@ -48,12 +48,18 @@ services:
dockerfile: Dockerfile
restart: always
ports:
- "${PORT:-3001}:3001"
- "${API_PORT}:4001"
env_file:
- ./api/.env
environment:
- PORT=${PORT:-3001}
- REDIS_URL=${REDIS_URL:-redis://textbee-redis:6379}
- PORT=${API_PORT}
- REDIS_URL=redis://textbee-redis:6379
healthcheck:
test: ["CMD", "ps", "aux", "| grep", "node"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
depends_on:
textbee-db:
@ -70,12 +76,12 @@ services:
dockerfile: Dockerfile
restart: always
ports:
- "${PORT:-3000}:3000"
- "${WEB_PORT}:3000"
env_file:
- ./web/.env
environment:
- PORT=${PORT:-3000}
- NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL:-http://localhost:3001/api/v1}
- PORT=3000
- NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}
depends_on:
- textbee-api

2
web/lib/httpServerClient.ts

@ -9,7 +9,7 @@ const getServerSideBaseUrl = () => {
// When running server-side in Docker, use the service name from docker-compose
if (process.env.CONTAINER_RUNTIME === 'docker') {
console.log('Running in Docker container')
return 'http://textbee-api:3001/api/v1'
return 'http://textbee-api:4001/api/v1'
}
// Otherwise use the public URL
return process.env.NEXT_PUBLIC_API_BASE_URL || ''

Loading…
Cancel
Save