diff --git a/backend-nest/src/modules/analytics/analytics.service.ts b/backend-nest/src/modules/analytics/analytics.service.ts index c98c19c..ad611c0 100644 --- a/backend-nest/src/modules/analytics/analytics.service.ts +++ b/backend-nest/src/modules/analytics/analytics.service.ts @@ -111,13 +111,13 @@ export class AnalyticsService { .createQueryBuilder('wipe') .leftJoinAndSelect('wipe.map', 'map') .select('map.id', 'map_id') - .addSelect('map.name', 'map_name') + .addSelect('map.display_name', 'map_name') .addSelect('COUNT(wipe.id)', 'usage_count') .where('wipe.license_id = :licenseId', { licenseId }) .andWhere('wipe.started_at >= :cutoff', { cutoff }) .andWhere('wipe.map_id IS NOT NULL') .groupBy('map.id') - .addGroupBy('map.name') + .addGroupBy('map.display_name') .getRawMany(); return { diff --git a/backend-nest/src/modules/setup/setup.service.ts b/backend-nest/src/modules/setup/setup.service.ts index 3c0ab37..2b65677 100644 --- a/backend-nest/src/modules/setup/setup.service.ts +++ b/backend-nest/src/modules/setup/setup.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, ServiceUnavailableException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -55,6 +55,13 @@ export class SetupService { if (dto.panel_api_key) { const encryptionKey = this.configService.get('encryption.key', ''); const keyBuffer = Buffer.from(encryptionKey, 'hex'); + // AES-256-GCM needs a 32-byte key. An unset/short ENCRYPTION_KEY would + // otherwise crash createCipheriv with an opaque "Invalid key length" 500. + if (keyBuffer.length !== 32) { + throw new ServiceUnavailableException( + 'Server encryption is not configured (ENCRYPTION_KEY must be 32 bytes / 64 hex chars). Contact the platform operator.', + ); + } const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-gcm', keyBuffer, iv); const encrypted = Buffer.concat([ @@ -82,9 +89,12 @@ export class SetupService { }); if (connection) { - // For bare metal, mark as connected immediately (waiting for agent) - if (connection.connection_type === 'bare_metal') { - connection.connection_status = 'connected'; + // Bare-metal stays 'offline' until the agent's first heartbeat flips it + // 'connected' (HostAgentConsumerService). Marking it connected here was a + // false positive — the dashboard showed a live server before any agent + // had checked in. + if (connection.connection_type === 'bare_metal' && connection.connection_status !== 'connected') { + connection.connection_status = 'offline'; connection.updated_at = new Date(); await this.connectionRepo.save(connection); } diff --git a/backend-nest/src/modules/store/store.service.ts b/backend-nest/src/modules/store/store.service.ts index 088bb1d..24e5c13 100644 --- a/backend-nest/src/modules/store/store.service.ts +++ b/backend-nest/src/modules/store/store.service.ts @@ -57,11 +57,17 @@ export class StoreService { throw new NotFoundException('Module not found'); } + // Beta: modules are granted free (no payment processing wired yet). Record + // it honestly as a beta grant at $0 rather than a fake `txn_*` id that + // implies a real charge occurred. + this.logger.log( + `Granting module ${moduleId} to license ${licenseId} free (Beta — no payment processing)`, + ); const purchase = this.purchaseRepo.create({ license_id: licenseId, module_id: moduleId, - transaction_id: `txn_${Date.now()}`, - amount_paid: parseFloat(module.price_usd.toString()), + transaction_id: 'beta-free-grant', + amount_paid: 0, }); return this.purchaseRepo.save(purchase); diff --git a/backend-nest/src/modules/webstore/webstore.service.ts b/backend-nest/src/modules/webstore/webstore.service.ts index a701b16..6f0543d 100644 --- a/backend-nest/src/modules/webstore/webstore.service.ts +++ b/backend-nest/src/modules/webstore/webstore.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, ServiceUnavailableException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { StoreConfig } from '../../entities/store-config.entity'; @@ -224,23 +224,13 @@ export class WebstoreService { throw new NotFoundException('Item not found'); } - const transaction = this.transactionRepo.create({ - license_id: license.id, - item_id: item.id, - steam_id: dto.steam_id, - player_name: dto.player_name, - paypal_order_id: `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - amount: parseFloat(item.price.toString()), - currency: 'USD', // Would get from config - status: 'pending', - }); - - await this.transactionRepo.save(transaction); - - // Return mock PayPal approval URL - return { - order_id: transaction.paypal_order_id, - approval_url: `https://www.sandbox.paypal.com/checkoutnow?token=${transaction.paypal_order_id}`, - }; + // Beta: real PayPal/Stripe processing is not wired yet. Refuse honestly + // instead of writing a pending transaction and handing the player a fake + // order token that resolves to nowhere. (item lookup above still validates + // the request so the storefront UI can show the catalogue.) + void item; + throw new ServiceUnavailableException( + 'Storefront checkout is not available yet — payment processing is coming soon.', + ); } }