fix(backend): resolve all 99 pre-existing TypeScript errors
- Add @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner dependencies - Add storage config section to config/index.ts - Fix users.controller.ts imports (db, AuthenticatedRequest, logger) - Update education.types.ts with backward-compatible alias properties - Add missing interfaces: LessonResource, QuizOption - Change QuizQuestion.options from Record to QuizOption[] - Fix education services to align with updated types - Export ML service types properly in ml.module.ts - Fix portfolio/snapshot.repository.ts type cast - Fix trading/order.service.ts number type Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d07427aa63
commit
ad51d5d5a8
1629
package-lock.json
generated
1629
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -58,6 +58,8 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.975.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.975.0",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/compression": "^1.7.5",
|
||||
|
||||
@ -47,6 +47,16 @@ export const config = {
|
||||
db: parseInt(process.env.REDIS_DB || '1', 10),
|
||||
},
|
||||
|
||||
storage: {
|
||||
provider: (process.env.STORAGE_PROVIDER as 's3' | 'r2') || 's3',
|
||||
bucket: process.env.STORAGE_BUCKET || 'trading-platform-dev',
|
||||
region: process.env.STORAGE_REGION || 'us-east-1',
|
||||
endpoint: process.env.STORAGE_ENDPOINT || '',
|
||||
accessKeyId: process.env.STORAGE_ACCESS_KEY_ID || '',
|
||||
secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY || '',
|
||||
cdnUrl: process.env.STORAGE_CDN_URL || '',
|
||||
},
|
||||
|
||||
stripe: {
|
||||
secretKey: process.env.STRIPE_SECRET_KEY || '',
|
||||
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || '',
|
||||
|
||||
@ -40,27 +40,28 @@ function transformCourse(row: Record<string, unknown>): Course {
|
||||
id: row.id as string,
|
||||
title: row.title as string,
|
||||
slug: row.slug as string,
|
||||
description: row.description as string | undefined,
|
||||
shortDescription: row.short_description as string | undefined,
|
||||
fullDescription: row.full_description as string | undefined,
|
||||
thumbnailUrl: row.thumbnail_url as string | undefined,
|
||||
previewVideoUrl: row.preview_video_url as string | undefined,
|
||||
categoryId: row.category_id as string | undefined,
|
||||
level: row.level as Course['level'],
|
||||
tags: (row.tags as string[]) || [],
|
||||
isFree: row.is_free as boolean,
|
||||
price: parseFloat(row.price as string) || 0,
|
||||
currency: row.currency as string,
|
||||
requiresSubscription: row.requires_subscription as boolean,
|
||||
minSubscriptionTier: row.min_subscription_tier as string | undefined,
|
||||
trailerUrl: row.trailer_url as string | undefined,
|
||||
categoryId: (row.category_id as string) || '',
|
||||
difficultyLevel: (row.difficulty_level || row.level || 'beginner') as Course['difficultyLevel'],
|
||||
level: (row.difficulty_level || row.level) as Course['level'],
|
||||
durationMinutes: row.duration_minutes as number | undefined,
|
||||
lessonsCount: row.lessons_count as number,
|
||||
enrolledCount: row.enrolled_count as number,
|
||||
averageRating: parseFloat(row.average_rating as string) || 0,
|
||||
ratingsCount: row.ratings_count as number,
|
||||
prerequisites: (row.prerequisites as string[]) || [],
|
||||
learningObjectives: (row.learning_objectives as string[]) || [],
|
||||
instructorId: (row.instructor_id as string) || '',
|
||||
instructorName: row.instructor_name as string | undefined,
|
||||
isFree: row.is_free as boolean,
|
||||
priceUsd: parseFloat(row.price_usd as string) || undefined,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
status: row.status as Course['status'],
|
||||
publishedAt: row.published_at ? new Date(row.published_at as string) : undefined,
|
||||
instructorId: row.instructor_id as string | undefined,
|
||||
aiGenerated: row.ai_generated as boolean,
|
||||
totalModules: (row.total_modules as number) || 0,
|
||||
totalLessons: (row.total_lessons || row.lessons_count) as number || 0,
|
||||
totalEnrollments: (row.total_enrollments || row.enrolled_count) as number || 0,
|
||||
avgRating: parseFloat(row.avg_rating as string) || parseFloat(row.average_rating as string) || 0,
|
||||
totalReviews: (row.total_reviews || row.ratings_count) as number || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
};
|
||||
@ -72,18 +73,19 @@ function transformLesson(row: Record<string, unknown>): Lesson {
|
||||
moduleId: row.module_id as string,
|
||||
courseId: row.course_id as string,
|
||||
title: row.title as string,
|
||||
slug: row.slug as string,
|
||||
description: row.description as string | undefined,
|
||||
contentType: row.content_type as Lesson['contentType'],
|
||||
videoUrl: row.video_url as string | undefined,
|
||||
videoDurationSeconds: row.video_duration_seconds as number | undefined,
|
||||
videoProvider: row.video_provider as string | undefined,
|
||||
contentMarkdown: row.content_markdown as string | undefined,
|
||||
contentHtml: row.content_html as string | undefined,
|
||||
videoId: row.video_id as string | undefined,
|
||||
articleContent: row.article_content as string | undefined,
|
||||
attachments: (row.attachments as Lesson['attachments']) || [],
|
||||
resources: (row.resources as Lesson['resources']) || [],
|
||||
sortOrder: row.sort_order as number,
|
||||
displayOrder: (row.display_order || row.sort_order) as number || 0,
|
||||
isPreview: row.is_preview as boolean,
|
||||
aiGenerated: row.ai_generated as boolean,
|
||||
aiSummary: row.ai_summary as string | undefined,
|
||||
isMandatory: (row.is_mandatory as boolean) ?? true,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
};
|
||||
@ -388,7 +390,7 @@ class CourseService {
|
||||
|
||||
async getCourseModules(courseId: string): Promise<ModuleWithLessons[]> {
|
||||
const modulesResult = await db.query<Record<string, unknown>>(
|
||||
`SELECT * FROM education.modules WHERE course_id = $1 ORDER BY sort_order`,
|
||||
`SELECT * FROM education.modules WHERE course_id = $1 ORDER BY display_order, sort_order`,
|
||||
[courseId]
|
||||
);
|
||||
|
||||
@ -400,8 +402,9 @@ class CourseService {
|
||||
courseId: row.course_id as string,
|
||||
title: row.title as string,
|
||||
description: row.description as string | undefined,
|
||||
displayOrder: (row.display_order || row.sort_order) as number || 0,
|
||||
sortOrder: row.sort_order as number,
|
||||
unlockAfterModuleId: row.unlock_after_module_id as string | undefined,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
lessons,
|
||||
@ -423,8 +426,9 @@ class CourseService {
|
||||
courseId: row.course_id as string,
|
||||
title: row.title as string,
|
||||
description: row.description as string | undefined,
|
||||
displayOrder: (row.display_order || row.sort_order) as number || 0,
|
||||
sortOrder: row.sort_order as number,
|
||||
unlockAfterModuleId: row.unlock_after_module_id as string | undefined,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
};
|
||||
@ -432,10 +436,10 @@ class CourseService {
|
||||
|
||||
async createModule(input: CreateModuleInput): Promise<Module> {
|
||||
const result = await db.query<Record<string, unknown>>(
|
||||
`INSERT INTO education.modules (course_id, title, description, sort_order, unlock_after_module_id)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`INSERT INTO education.modules (course_id, title, description, display_order)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *`,
|
||||
[input.courseId, input.title, input.description, input.sortOrder || 0, input.unlockAfterModuleId]
|
||||
[input.courseId, input.title, input.description, input.sortOrder || 0]
|
||||
);
|
||||
const row = result.rows[0];
|
||||
return {
|
||||
@ -443,8 +447,9 @@ class CourseService {
|
||||
courseId: row.course_id as string,
|
||||
title: row.title as string,
|
||||
description: row.description as string | undefined,
|
||||
displayOrder: (row.display_order || row.sort_order) as number || 0,
|
||||
sortOrder: row.sort_order as number,
|
||||
unlockAfterModuleId: row.unlock_after_module_id as string | undefined,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
};
|
||||
|
||||
@ -26,16 +26,16 @@ function transformEnrollment(row: Record<string, unknown>): Enrollment {
|
||||
courseId: row.course_id as string,
|
||||
status: row.status as Enrollment['status'],
|
||||
progressPercentage: parseFloat(row.progress_percentage as string) || 0,
|
||||
lessonsCompleted: row.lessons_completed as number,
|
||||
completedLessons: (row.completed_lessons || row.lessons_completed) as number || 0,
|
||||
totalLessons: (row.total_lessons as number) || 0,
|
||||
enrolledAt: new Date(row.enrolled_at as string),
|
||||
expiresAt: row.expires_at ? new Date(row.expires_at as string) : undefined,
|
||||
startedAt: row.started_at ? new Date(row.started_at as string) : undefined,
|
||||
completedAt: row.completed_at ? new Date(row.completed_at as string) : undefined,
|
||||
paymentId: row.payment_id as string | undefined,
|
||||
certificateIssued: row.certificate_issued as boolean,
|
||||
certificateUrl: row.certificate_url as string | undefined,
|
||||
certificateIssuedAt: row.certificate_issued_at ? new Date(row.certificate_issued_at as string) : undefined,
|
||||
expiresAt: row.expires_at ? new Date(row.expires_at as string) : undefined,
|
||||
totalXpEarned: (row.total_xp_earned as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
lessonsCompleted: (row.completed_lessons || row.lessons_completed) as number || 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -43,15 +43,17 @@ function transformLessonProgress(row: Record<string, unknown>): LessonProgress {
|
||||
return {
|
||||
id: row.id as string,
|
||||
userId: row.user_id as string,
|
||||
lessonId: row.lesson_id as string,
|
||||
enrollmentId: row.enrollment_id as string,
|
||||
videoWatchedSeconds: row.video_watched_seconds as number,
|
||||
videoCompleted: row.video_completed as boolean,
|
||||
startedAt: row.started_at ? new Date(row.started_at as string) : undefined,
|
||||
lessonId: row.lesson_id as string,
|
||||
isCompleted: (row.is_completed || row.video_completed) as boolean || false,
|
||||
videoProgressSeconds: row.video_progress_seconds as number | undefined,
|
||||
completedAt: row.completed_at ? new Date(row.completed_at as string) : undefined,
|
||||
userNotes: row.user_notes as string | undefined,
|
||||
xpEarned: (row.xp_earned as number) || 0,
|
||||
notes: row.notes as string | undefined,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
videoWatchedSeconds: row.video_watched_seconds as number,
|
||||
videoCompleted: (row.is_completed || row.video_completed) as boolean || false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -82,11 +84,22 @@ class EnrollmentService {
|
||||
title: row.title as string,
|
||||
slug: row.slug as string,
|
||||
thumbnailUrl: row.thumbnail_url as string | undefined,
|
||||
difficultyLevel: (row.difficulty_level || row.level || 'beginner') as Course['difficultyLevel'],
|
||||
level: row.level as Course['level'],
|
||||
lessonsCount: row.lessons_count as number,
|
||||
totalLessons: (row.total_lessons || row.lessons_count) as number || 0,
|
||||
durationMinutes: row.duration_minutes as number | undefined,
|
||||
instructorId: row.instructor_id as string | undefined,
|
||||
} as Course,
|
||||
instructorId: (row.instructor_id as string) || '',
|
||||
categoryId: (row.category_id as string) || '',
|
||||
isFree: (row.is_free as boolean) || false,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
status: (row.status as Course['status']) || 'draft',
|
||||
totalModules: (row.total_modules as number) || 0,
|
||||
totalEnrollments: (row.total_enrollments as number) || 0,
|
||||
avgRating: parseFloat(row.avg_rating as string) || 0,
|
||||
totalReviews: (row.total_reviews as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
} as unknown as Course,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -215,7 +228,7 @@ class EnrollmentService {
|
||||
throw new Error('Lesson not found');
|
||||
}
|
||||
|
||||
const enrollment = await this.getEnrollment(userId, lesson.courseId);
|
||||
const enrollment = await this.getEnrollment(userId, lesson.courseId || '');
|
||||
if (!enrollment) {
|
||||
throw new Error('Not enrolled in this course');
|
||||
}
|
||||
@ -253,7 +266,7 @@ class EnrollmentService {
|
||||
|
||||
// Update enrollment progress if lesson completed
|
||||
if (input.videoCompleted && !existing.videoCompleted) {
|
||||
await this.updateEnrollmentProgress(enrollment.id, lesson.courseId);
|
||||
await this.updateEnrollmentProgress(enrollment.id, lesson.courseId || '');
|
||||
}
|
||||
|
||||
return transformLessonProgress(result.rows[0]);
|
||||
@ -277,7 +290,7 @@ class EnrollmentService {
|
||||
);
|
||||
|
||||
if (input.videoCompleted) {
|
||||
await this.updateEnrollmentProgress(enrollment.id, lesson.courseId);
|
||||
await this.updateEnrollmentProgress(enrollment.id, lesson.courseId || '');
|
||||
}
|
||||
|
||||
return transformLessonProgress(result.rows[0]);
|
||||
|
||||
@ -23,16 +23,21 @@ import type {
|
||||
function transformQuiz(row: Record<string, unknown>): Quiz {
|
||||
return {
|
||||
id: row.id as string,
|
||||
moduleId: row.module_id as string | undefined,
|
||||
lessonId: row.lesson_id as string | undefined,
|
||||
courseId: row.course_id as string,
|
||||
courseId: row.course_id as string | undefined,
|
||||
title: row.title as string,
|
||||
description: row.description as string | undefined,
|
||||
passingScorePercentage: parseFloat(row.passing_score_percentage as string) || parseFloat(row.passing_score as string) || 70,
|
||||
passingScore: parseFloat(row.passing_score as string) || 70,
|
||||
maxAttempts: row.max_attempts as number | undefined,
|
||||
timeLimitMinutes: row.time_limit_minutes as number | undefined,
|
||||
shuffleQuestions: row.shuffle_questions as boolean,
|
||||
shuffleAnswers: (row.shuffle_answers as boolean) || false,
|
||||
showCorrectAnswers: row.show_correct_answers as boolean,
|
||||
aiGenerated: row.ai_generated as boolean,
|
||||
xpReward: (row.xp_reward as number) || 0,
|
||||
xpPerfectScoreBonus: (row.xp_perfect_score_bonus as number) || 0,
|
||||
isActive: (row.is_active as boolean) ?? true,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
};
|
||||
@ -46,8 +51,10 @@ function transformQuizQuestion(row: Record<string, unknown>): QuizQuestion {
|
||||
questionText: row.question_text as string,
|
||||
explanation: row.explanation as string | undefined,
|
||||
options: row.options as QuizQuestion['options'],
|
||||
correctAnswer: row.correct_answer as string | undefined,
|
||||
correctAnswers: row.correct_answers as string[] | undefined,
|
||||
points: row.points as number,
|
||||
points: (row.points as number) || 0,
|
||||
displayOrder: (row.display_order || row.sort_order) as number || 0,
|
||||
sortOrder: row.sort_order as number,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
};
|
||||
@ -59,13 +66,20 @@ function transformQuizAttempt(row: Record<string, unknown>): QuizAttempt {
|
||||
userId: row.user_id as string,
|
||||
quizId: row.quiz_id as string,
|
||||
enrollmentId: row.enrollment_id as string | undefined,
|
||||
score: parseFloat(row.score as string) || 0,
|
||||
passed: row.passed as boolean,
|
||||
answers: (row.answers as QuizAnswer[]) || [],
|
||||
isCompleted: (row.is_completed || row.submitted_at != null) as boolean || false,
|
||||
isPassed: (row.is_passed || row.passed) as boolean || false,
|
||||
userAnswers: (row.user_answers || row.answers) as QuizAnswer[] | undefined,
|
||||
scorePoints: (row.score_points as number) || parseFloat(row.score as string) || 0,
|
||||
maxPoints: (row.max_points as number) || 0,
|
||||
scorePercentage: (row.score_percentage as number) || parseFloat(row.score as string) || 0,
|
||||
startedAt: new Date(row.started_at as string),
|
||||
submittedAt: row.submitted_at ? new Date(row.submitted_at as string) : undefined,
|
||||
timeSpentSeconds: row.time_spent_seconds as number | undefined,
|
||||
completedAt: row.completed_at ? new Date(row.completed_at as string) : (row.submitted_at ? new Date(row.submitted_at as string) : undefined),
|
||||
timeTakenSeconds: row.time_taken_seconds as number | undefined,
|
||||
xpEarned: (row.xp_earned as number) || 0,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
score: parseFloat(row.score as string) || 0,
|
||||
submittedAt: row.submitted_at ? new Date(row.submitted_at as string) : undefined,
|
||||
answers: (row.user_answers || row.answers) as QuizAnswer[] | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -525,7 +539,7 @@ class QuizService {
|
||||
const questions = await this.getQuizQuestions(quiz.id, true);
|
||||
|
||||
// Calculate score
|
||||
const scoreResult = calculateScore(questions, input.answers, quiz.passingScore);
|
||||
const scoreResult = calculateScore(questions, input.answers, quiz.passingScore || quiz.passingScorePercentage || 70);
|
||||
|
||||
// Calculate time spent
|
||||
const timeSpentSeconds = Math.floor((Date.now() - attempt.startedAt.getTime()) / 1000);
|
||||
@ -537,6 +551,7 @@ class QuizService {
|
||||
questionId: a.questionId,
|
||||
answer: a.answer,
|
||||
isCorrect: result?.isCorrect || false,
|
||||
points: result?.pointsEarned || 0,
|
||||
};
|
||||
});
|
||||
|
||||
@ -603,7 +618,8 @@ class QuizService {
|
||||
|
||||
// Rebuild results from stored answers
|
||||
const results: QuestionResult[] = [];
|
||||
for (const answer of attempt.answers) {
|
||||
const attemptAnswers = attempt.answers || attempt.userAnswers || [];
|
||||
for (const answer of attemptAnswers) {
|
||||
const question = questions.find(q => q.id === answer.questionId);
|
||||
if (question) {
|
||||
results.push({
|
||||
|
||||
@ -58,7 +58,7 @@ export enum EnrollmentStatusEnum {
|
||||
}
|
||||
|
||||
// Alineado con education.question_type (DDL)
|
||||
export type QuestionType = 'multiple_choice' | 'true_false' | 'multiple_select' | 'fill_blank' | 'code_challenge';
|
||||
export type QuestionType = 'multiple_choice' | 'true_false' | 'multiple_select' | 'fill_blank' | 'code_challenge' | 'multiple_answer' | 'short_answer';
|
||||
|
||||
export enum QuestionTypeEnum {
|
||||
MULTIPLE_CHOICE = 'multiple_choice',
|
||||
@ -116,6 +116,8 @@ export interface Course {
|
||||
fullDescription?: string;
|
||||
categoryId: string;
|
||||
difficultyLevel: DifficultyLevel;
|
||||
/** @deprecated Use difficultyLevel instead - alias for backward compatibility */
|
||||
level?: DifficultyLevel;
|
||||
thumbnailUrl?: string;
|
||||
trailerUrl?: string;
|
||||
durationMinutes?: number;
|
||||
@ -195,6 +197,9 @@ export interface Module {
|
||||
title: string;
|
||||
description?: string;
|
||||
displayOrder: number;
|
||||
/** @deprecated Alias for displayOrder - use displayOrder instead */
|
||||
sortOrder?: number;
|
||||
unlockAfterModuleId?: string;
|
||||
xpReward: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@ -202,6 +207,9 @@ export interface Module {
|
||||
|
||||
export interface ModuleWithLessons extends Module {
|
||||
lessons: Lesson[];
|
||||
/** @deprecated Alias for displayOrder - use displayOrder instead */
|
||||
sortOrder?: number;
|
||||
unlockAfterModuleId?: string;
|
||||
}
|
||||
|
||||
export interface CreateModuleInput {
|
||||
@ -219,7 +227,10 @@ export interface CreateModuleInput {
|
||||
export interface Lesson {
|
||||
id: string;
|
||||
moduleId: string;
|
||||
/** @deprecated Alias for moduleId - some services pass courseId directly */
|
||||
courseId?: string;
|
||||
title: string;
|
||||
slug?: string;
|
||||
description?: string;
|
||||
contentType: LessonContentType;
|
||||
videoUrl?: string;
|
||||
@ -228,6 +239,8 @@ export interface Lesson {
|
||||
videoId?: string;
|
||||
articleContent?: string;
|
||||
attachments?: LessonAttachment[];
|
||||
/** @deprecated Alias for attachments - some services use resources */
|
||||
resources?: LessonResource[];
|
||||
displayOrder: number;
|
||||
isPreview: boolean;
|
||||
isMandatory: boolean;
|
||||
@ -240,6 +253,15 @@ export interface LessonWithProgress extends Lesson {
|
||||
progress?: LessonProgress;
|
||||
}
|
||||
|
||||
export interface LessonResource {
|
||||
id?: string;
|
||||
type: 'pdf' | 'video' | 'link' | 'download';
|
||||
title: string;
|
||||
url: string;
|
||||
size?: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface CreateLessonInput {
|
||||
moduleId: string;
|
||||
courseId: string;
|
||||
@ -263,9 +285,13 @@ export interface Quiz {
|
||||
id: string;
|
||||
moduleId?: string;
|
||||
lessonId?: string;
|
||||
/** @deprecated Alias for getting course via module/lesson - use moduleId or lessonId instead */
|
||||
courseId?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
passingScorePercentage: number;
|
||||
/** @deprecated Alias for passingScorePercentage - use passingScorePercentage instead */
|
||||
passingScore?: number;
|
||||
maxAttempts?: number;
|
||||
timeLimitMinutes?: number;
|
||||
shuffleQuestions: boolean;
|
||||
@ -274,6 +300,7 @@ export interface Quiz {
|
||||
xpReward: number;
|
||||
xpPerfectScoreBonus: number;
|
||||
isActive: boolean;
|
||||
aiGenerated?: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -287,12 +314,14 @@ export interface QuizQuestion {
|
||||
quizId: string;
|
||||
questionType: QuestionType;
|
||||
questionText: string;
|
||||
options?: Record<string, unknown>;
|
||||
options?: QuizOption[];
|
||||
correctAnswer?: string;
|
||||
correctAnswers?: string[];
|
||||
explanation?: string;
|
||||
points: number;
|
||||
displayOrder: number;
|
||||
/** @deprecated Alias for displayOrder - use displayOrder instead */
|
||||
sortOrder?: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
@ -312,6 +341,12 @@ export interface CreateQuizInput {
|
||||
showCorrectAnswers?: boolean;
|
||||
}
|
||||
|
||||
export interface QuizOption {
|
||||
id: string;
|
||||
text: string;
|
||||
isCorrect: boolean;
|
||||
}
|
||||
|
||||
export interface CreateQuizQuestionInput {
|
||||
quizId: string;
|
||||
questionType?: QuestionType;
|
||||
@ -340,8 +375,13 @@ export interface Enrollment {
|
||||
completedAt?: Date;
|
||||
expiresAt?: Date;
|
||||
totalXpEarned: number;
|
||||
paymentId?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
// Backward-compatible alias
|
||||
/** @deprecated Use completedLessons instead */
|
||||
lessonsCompleted?: number;
|
||||
}
|
||||
|
||||
export interface EnrollmentWithCourse extends Enrollment {
|
||||
@ -367,10 +407,17 @@ export interface LessonProgress {
|
||||
isCompleted: boolean;
|
||||
videoProgressSeconds?: number;
|
||||
completedAt?: Date;
|
||||
startedAt?: Date;
|
||||
xpEarned: number;
|
||||
notes?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
// Backward-compatible aliases
|
||||
/** @deprecated Use videoProgressSeconds instead */
|
||||
videoWatchedSeconds?: number;
|
||||
/** @deprecated Use isCompleted instead */
|
||||
videoCompleted?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateLessonProgressInput {
|
||||
@ -406,6 +453,14 @@ export interface QuizAttempt {
|
||||
timeTakenSeconds?: number;
|
||||
xpEarned: number;
|
||||
createdAt: Date;
|
||||
|
||||
// Backward-compatible aliases
|
||||
/** @deprecated Use scorePoints instead */
|
||||
score?: number;
|
||||
/** @deprecated Use completedAt instead */
|
||||
submittedAt?: Date;
|
||||
/** @deprecated Use userAnswers instead */
|
||||
answers?: QuizUserAnswer[];
|
||||
}
|
||||
|
||||
export interface SubmitQuizInput {
|
||||
|
||||
@ -17,6 +17,24 @@ import { mlOverlayService } from './services/ml-overlay.service';
|
||||
import { mlSignalStreamService } from './services/ml-signal-stream.service';
|
||||
import { logger } from '../../shared/utils/logger';
|
||||
|
||||
// ============================================================================
|
||||
// Service Type Exports (for getter return type)
|
||||
// ============================================================================
|
||||
|
||||
export type MLIntegrationService = typeof mlIntegrationService;
|
||||
export type MLDataService = typeof mlDataService;
|
||||
export type MLModelRegistryService = typeof mlModelRegistryService;
|
||||
export type MLOverlayService = typeof mlOverlayService;
|
||||
export type MLSignalStreamService = typeof mlSignalStreamService;
|
||||
|
||||
export interface MLServices {
|
||||
integration: MLIntegrationService;
|
||||
data: MLDataService;
|
||||
modelRegistry: MLModelRegistryService;
|
||||
overlay: MLOverlayService;
|
||||
signalStream: MLSignalStreamService;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Module Configuration
|
||||
// ============================================================================
|
||||
@ -183,7 +201,7 @@ class MLModule {
|
||||
/**
|
||||
* Access to individual services
|
||||
*/
|
||||
get services() {
|
||||
get services(): MLServices {
|
||||
return {
|
||||
integration: mlIntegrationService,
|
||||
data: mlDataService,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { db } from '../../../shared/database';
|
||||
import type { PortfolioSnapshot, PerformanceDataPoint } from '../types/portfolio.types';
|
||||
import type { PortfolioSnapshot, PerformanceDataPoint, SnapshotAllocationData } from '../types/portfolio.types';
|
||||
|
||||
// ============================================================================
|
||||
// Database Row Types (snake_case from DB)
|
||||
@ -55,7 +55,7 @@ function mapRowToSnapshot(row: SnapshotRow): PortfolioSnapshot {
|
||||
unrealizedPnlPercent: parseFloat(row.unrealized_pnl_percent || '0'),
|
||||
dayChange: parseFloat(row.day_change || '0'),
|
||||
dayChangePercent: parseFloat(row.day_change_percent || '0'),
|
||||
allocations: row.allocations,
|
||||
allocations: row.allocations as Record<string, SnapshotAllocationData> | null,
|
||||
createdAt: row.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ export class OrderService {
|
||||
let price = data.price;
|
||||
if (data.type === OrderTypeEnum.MARKET && !price) {
|
||||
const ticker = await marketService.getTicker(data.symbol);
|
||||
price = parseFloat(ticker.lastPrice);
|
||||
price = ticker.lastPrice;
|
||||
}
|
||||
|
||||
// Validate limit/stop orders have price
|
||||
|
||||
@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { pool } from '../../../shared/database';
|
||||
import { AuthenticatedRequest } from '../../../core/types';
|
||||
import { db } from '../../../shared/database';
|
||||
import { AuthenticatedRequest } from '../../../core/guards/auth.guard';
|
||||
import { User, Profile, UserRole, UserStatus } from '../../auth/types/auth.types';
|
||||
import { logger } from '../../../shared/logger';
|
||||
import { logger } from '../../../shared/utils/logger';
|
||||
|
||||
/**
|
||||
* Get current user profile
|
||||
@ -42,7 +42,7 @@ export const getCurrentUser = async (
|
||||
WHERE u.id = $1
|
||||
`;
|
||||
|
||||
const result = await pool.query(userQuery, [userId]);
|
||||
const result = await db.query(userQuery, [userId]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
res.status(404).json({
|
||||
@ -143,7 +143,7 @@ export const updateCurrentUser = async (
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await pool.query(upsertQuery, [
|
||||
const result = await db.query(upsertQuery, [
|
||||
userId,
|
||||
firstName,
|
||||
lastName,
|
||||
@ -203,7 +203,7 @@ export const getUserById = async (
|
||||
WHERE u.id = $1
|
||||
`;
|
||||
|
||||
const result = await pool.query(userQuery, [id]);
|
||||
const result = await db.query(userQuery, [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
res.status(404).json({
|
||||
@ -292,7 +292,7 @@ export const listUsers = async (
|
||||
${whereClause}
|
||||
`;
|
||||
|
||||
const countResult = await pool.query(countQuery, params);
|
||||
const countResult = await db.query(countQuery, params);
|
||||
const total = parseInt(countResult.rows[0].total, 10);
|
||||
|
||||
// Data query
|
||||
@ -309,9 +309,9 @@ export const listUsers = async (
|
||||
`;
|
||||
|
||||
params.push(limitNum, offset);
|
||||
const dataResult = await pool.query(dataQuery, params);
|
||||
const dataResult = await db.query(dataQuery, params);
|
||||
|
||||
const users = dataResult.rows.map(row => ({
|
||||
const users = dataResult.rows.map((row: any) => ({
|
||||
id: row.id,
|
||||
email: row.email,
|
||||
emailVerified: row.email_verified,
|
||||
@ -367,7 +367,7 @@ export const updateUser = async (
|
||||
RETURNING id, email, role, status, updated_at
|
||||
`;
|
||||
|
||||
const result = await pool.query(updateQuery, [id, role, status]);
|
||||
const result = await db.query(updateQuery, [id, role, status]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
res.status(404).json({
|
||||
@ -418,7 +418,7 @@ export const deleteUser = async (
|
||||
RETURNING id, email
|
||||
`;
|
||||
|
||||
const result = await pool.query(deleteQuery, [id]);
|
||||
const result = await db.query(deleteQuery, [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
res.status(404).json({
|
||||
|
||||
@ -386,7 +386,7 @@ export class StorageService {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.Contents.map((obj) => ({
|
||||
return response.Contents.map((obj: any) => ({
|
||||
key: obj.Key || '',
|
||||
size: obj.Size || 0,
|
||||
lastModified: obj.LastModified || new Date(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user