Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { WalletModule } from './wallet/wallet.module';
import { DifficultyScalingModule } from './difficulty-scaling/difficulty-scaling.module';
import { TournamentsModule } from './tournaments/tournaments.module';
import { RabbitMQModule } from './rabbitmq/rabbitmq.module';
import { TutorialModule } from './tutorial/tutorial.module';
import { ReferralsModule } from './referrals/referrals.module';
import { SaveGameModule } from './save-game/save-game.module';

Expand Down Expand Up @@ -75,6 +76,7 @@ import { PuzzleModule } from './puzzle/puzzle.module';
HintsModule,
DifficultyScalingModule,
TournamentsModule,
TutorialModule,
ReferralsModule,
SaveGameModule,

Expand Down
170 changes: 170 additions & 0 deletions src/tutorial/controllers/contextual-help.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
ParseUUIDPipe,
HttpCode,
HttpStatus,
Logger,
} from '@nestjs/common';
import { ContextualHelpService } from '../services/contextual-help.service';
import { LocalizationService } from '../services/localization.service';
import {
CreateContextualHelpDto,
UpdateContextualHelpDto,
ContextualHelpFilterDto,
TriggerContextualHelpDto,
RecordHelpInteractionDto,
} from '../dto';

@Controller('contextual-help')
export class ContextualHelpController {
private readonly logger = new Logger(ContextualHelpController.name);

constructor(
private readonly helpService: ContextualHelpService,
private readonly localizationService: LocalizationService,
) {}

// CRUD Operations
@Post()
async create(@Body() dto: CreateContextualHelpDto) {
this.logger.log(`Creating contextual help: ${dto.name}`);
return this.helpService.create(dto);
}

@Get()
async findAll(@Query() filters: ContextualHelpFilterDto) {
this.logger.log(`Fetching contextual help with filters: ${JSON.stringify(filters)}`);
return this.helpService.findAll(filters);
}

@Get(':id')
async findOne(
@Param('id', ParseUUIDPipe) id: string,
@Query('locale') locale?: string,
) {
this.logger.log(`Fetching contextual help: ${id}`);
const help = await this.helpService.findById(id);

if (locale) {
return this.localizationService.localizeHelp(help, locale);
}

return help;
}

@Patch(':id')
async update(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: UpdateContextualHelpDto,
) {
this.logger.log(`Updating contextual help: ${id}`);
return this.helpService.update(id, dto);
}

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
async delete(@Param('id', ParseUUIDPipe) id: string) {
this.logger.log(`Deleting contextual help: ${id}`);
await this.helpService.delete(id);
}

// Trigger and Display
@Post('user/:userId/trigger')
async triggerHelp(
@Param('userId', ParseUUIDPipe) userId: string,
@Body() dto: TriggerContextualHelpDto,
@Query('locale') locale?: string,
) {
this.logger.log(`Triggering help for user ${userId} in context: ${dto.context}`);
const help = await this.helpService.triggerHelp(userId, dto);

if (help && locale) {
return this.localizationService.localizeHelp(help, locale);
}

return help;
}

@Post('user/:userId/interaction')
async recordInteraction(
@Param('userId', ParseUUIDPipe) userId: string,
@Body() dto: RecordHelpInteractionDto,
) {
this.logger.log(`Recording interaction for user ${userId} on help ${dto.helpId}`);
await this.helpService.recordInteraction(userId, dto);
return { message: 'Interaction recorded successfully' };
}

@Get('user/:userId/history')
async getUserHistory(
@Param('userId', ParseUUIDPipe) userId: string,
@Query('helpId') helpId?: string,
) {
this.logger.log(`Fetching help history for user ${userId}`);
return this.helpService.getUserHelpHistory(userId, helpId);
}

// Integration Endpoints
@Get('puzzle-start/:userId/:puzzleType')
async getHelpForPuzzleStart(
@Param('userId', ParseUUIDPipe) userId: string,
@Param('puzzleType') puzzleType: string,
@Query('locale') locale?: string,
) {
this.logger.log(`Getting puzzle start help for user ${userId}, type: ${puzzleType}`);
const help = await this.helpService.getHelpForPuzzleStart(userId, puzzleType);

if (help && locale) {
return this.localizationService.localizeHelp(help, locale);
}

return help;
}

@Get('repeated-failure/:userId/:puzzleId')
async getHelpForRepeatedFailure(
@Param('userId', ParseUUIDPipe) userId: string,
@Param('puzzleId', ParseUUIDPipe) puzzleId: string,
@Query('attempts') attempts: number,
@Query('locale') locale?: string,
) {
this.logger.log(`Getting failure help for user ${userId}, puzzle: ${puzzleId}`);
const help = await this.helpService.getHelpForRepeatedFailure(userId, puzzleId, attempts);

if (help && locale) {
return this.localizationService.localizeHelp(help, locale);
}

return help;
}

@Get('feature/:userId/:feature')
async getHelpForFeature(
@Param('userId', ParseUUIDPipe) userId: string,
@Param('feature') feature: string,
@Query('locale') locale?: string,
) {
this.logger.log(`Getting feature help for user ${userId}, feature: ${feature}`);
const help = await this.helpService.getHelpForFeature(userId, feature);

if (help && locale) {
return this.localizationService.localizeHelp(help, locale);
}

return help;
}

// Analytics
@Get('analytics')
async getHelpAnalytics(@Query('helpId') helpId?: string) {
this.logger.log('Fetching contextual help analytics');
return this.helpService.getHelpAnalytics(helpId);
}
}
4 changes: 4 additions & 0 deletions src/tutorial/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './tutorial.controller';
export * from './tutorial-progress.controller';
export * from './contextual-help.controller';
export * from './tutorial-analytics.controller';
148 changes: 148 additions & 0 deletions src/tutorial/controllers/tutorial-analytics.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {
Controller,
Get,
Param,
Query,
ParseUUIDPipe,
Logger,
} from '@nestjs/common';
import { TutorialAnalyticsService } from '../services/tutorial-analytics.service';
import {
DateRangeDto,
TutorialAnalyticsFilterDto,
TutorialEffectivenessFilterDto,
AnalyticsExportFilterDto,
} from '../dto';

@Controller('tutorial-analytics')
export class TutorialAnalyticsController {
private readonly logger = new Logger(TutorialAnalyticsController.name);

constructor(private readonly analyticsService: TutorialAnalyticsService) {}

// Completion Rates
@Get('completion-rate/:tutorialId')
async getCompletionRate(
@Param('tutorialId', ParseUUIDPipe) tutorialId: string,
@Query() dateRange: DateRangeDto,
) {
this.logger.log(`Getting completion rate for tutorial: ${tutorialId}`);
const rate = await this.analyticsService.getCompletionRate(tutorialId, dateRange);
return { tutorialId, rate };
}

@Get('completion-rates')
async getAllCompletionRates(@Query() filters: TutorialAnalyticsFilterDto) {
this.logger.log('Getting all tutorial completion rates');
return this.analyticsService.getAllCompletionRates(filters);
}

// Step Completion Rates
@Get('step-completion-rates/:tutorialId')
async getStepCompletionRates(
@Param('tutorialId', ParseUUIDPipe) tutorialId: string,
) {
this.logger.log(`Getting step completion rates for tutorial: ${tutorialId}`);
return this.analyticsService.getStepCompletionRates(tutorialId);
}

// Drop-off Analysis
@Get('drop-off/:tutorialId')
async getDropOffAnalysis(
@Param('tutorialId', ParseUUIDPipe) tutorialId: string,
) {
this.logger.log(`Getting drop-off analysis for tutorial: ${tutorialId}`);
return this.analyticsService.getDropOffAnalysis(tutorialId);
}

@Get('drop-off-points')
async getCommonDropOffPoints() {
this.logger.log('Getting common drop-off points across all tutorials');
return this.analyticsService.getCommonDropOffPoints();
}

// Effectiveness Reports
@Get('effectiveness/:tutorialId')
async getEffectivenessReport(
@Param('tutorialId', ParseUUIDPipe) tutorialId: string,
@Query() filters: TutorialEffectivenessFilterDto,
) {
this.logger.log(`Getting effectiveness report for tutorial: ${tutorialId}`);
return this.analyticsService.getEffectivenessReport(tutorialId, filters);
}

@Get('step-effectiveness/:stepId')
async getStepEffectiveness(@Param('stepId', ParseUUIDPipe) stepId: string) {
this.logger.log(`Getting effectiveness for step: ${stepId}`);
return this.analyticsService.getStepEffectiveness(stepId);
}

// User Analytics
@Get('user/:userId/learning-profile')
async getUserLearningProfile(@Param('userId', ParseUUIDPipe) userId: string) {
this.logger.log(`Getting learning profile for user: ${userId}`);
return this.analyticsService.getUserLearningProfile(userId);
}

// Average Time Metrics
@Get('average-time/:tutorialId')
async getAverageCompletionTime(
@Param('tutorialId', ParseUUIDPipe) tutorialId: string,
) {
this.logger.log(`Getting average completion time for tutorial: ${tutorialId}`);
const time = await this.analyticsService.getAverageTimeToCompletion(tutorialId);
return { tutorialId, averageCompletionTimeSeconds: time };
}

// Hint Usage Analytics
@Get('hint-usage/:tutorialId')
async getHintUsageAnalytics(
@Param('tutorialId', ParseUUIDPipe) tutorialId: string,
) {
this.logger.log(`Getting hint usage analytics for tutorial: ${tutorialId}`);
return this.analyticsService.getHintUsageAnalytics(tutorialId);
}

// Error Patterns
@Get('error-patterns/:tutorialId')
async getErrorPatterns(@Param('tutorialId', ParseUUIDPipe) tutorialId: string) {
this.logger.log(`Getting error patterns for tutorial: ${tutorialId}`);
return this.analyticsService.getErrorPatterns(tutorialId);
}

// Dashboard
@Get('dashboard')
async getDashboardReport(@Query() dateRange: DateRangeDto) {
this.logger.log('Generating tutorial analytics dashboard');
return this.analyticsService.generateDashboardReport(dateRange);
}

// Real-time Metrics
@Get('active-users')
async getActiveUsers() {
this.logger.log('Getting active tutorial users count');
const count = await this.analyticsService.getActiveUsers();
return { count };
}

@Get('completions/:interval')
async getCurrentCompletions(@Param('interval') interval: 'hour' | 'day') {
this.logger.log(`Getting completions for interval: ${interval}`);
const count = await this.analyticsService.getCurrentCompletions(interval);
return { interval, count };
}

// Export
@Get('export')
async exportAnalytics(@Query() filters: AnalyticsExportFilterDto) {
this.logger.log('Exporting tutorial analytics');
return this.analyticsService.exportAnalyticsData(filters);
}

// Event Query
@Get('events')
async getEvents(@Query() filters: TutorialAnalyticsFilterDto) {
this.logger.log(`Querying tutorial analytics events`);
return this.analyticsService.queryEvents(filters);
}
}
Loading