first commit
This commit is contained in:
320
src/app/core/models/admin.model.ts
Normal file
320
src/app/core/models/admin.model.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* Admin Statistics Models
|
||||
* Type definitions for admin statistics and analytics
|
||||
*/
|
||||
|
||||
/**
|
||||
* User growth data point for chart
|
||||
*/
|
||||
export interface UserGrowthData {
|
||||
date: string;
|
||||
newUsers: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category popularity data for chart
|
||||
*/
|
||||
export interface CategoryPopularity {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
icon: any;
|
||||
color: string;
|
||||
quizCount: number;
|
||||
averageScore: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* System-wide statistics response
|
||||
*/
|
||||
export interface AdminStatistics {
|
||||
users: AdminStatisticsUsers;
|
||||
quizzes: AdminStatisticsQuizzes;
|
||||
content: AdminStatisticsContent;
|
||||
quizActivity: QuizActivity[];
|
||||
userGrowth: UserGrowthData[];
|
||||
popularCategories: CategoryPopularity[];
|
||||
}
|
||||
export interface AdminStatisticsContent {
|
||||
totalCategories: number;
|
||||
totalQuestions: number;
|
||||
questionsByDifficulty: {
|
||||
easy: number;
|
||||
medium: number;
|
||||
hard: number;
|
||||
};
|
||||
}
|
||||
export interface AdminStatisticsQuizzes {
|
||||
totalSessions: number;
|
||||
averageScore: number;
|
||||
averageScorePercentage: number;
|
||||
passRate: number;
|
||||
passedQuizzes: number;
|
||||
failedQuizzes: number;
|
||||
}
|
||||
export interface AdminStatisticsUsers {
|
||||
total: number;
|
||||
active: number;
|
||||
inactiveLast7Days: number;
|
||||
}
|
||||
/**
|
||||
* API response wrapper for statistics
|
||||
*/
|
||||
export interface AdminStatisticsResponse {
|
||||
success: boolean;
|
||||
data: AdminStatistics;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface QuizActivity {
|
||||
date: string;
|
||||
quizzesCompleted: number;
|
||||
}
|
||||
/**
|
||||
* Date range filter for statistics
|
||||
*/
|
||||
export interface DateRangeFilter {
|
||||
startDate: Date | null;
|
||||
endDate: Date | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache entry for admin data
|
||||
*/
|
||||
export interface AdminCacheEntry<T> {
|
||||
data: T;
|
||||
timestamp: number;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest session timeline data point
|
||||
*/
|
||||
// export interface GuestSessionTimelineData {
|
||||
// date: string;
|
||||
// activeSessions: number;
|
||||
// newSessions: number;
|
||||
// convertedSessions: number;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Conversion funnel stage
|
||||
*/
|
||||
// export interface ConversionFunnelStage {
|
||||
// stage: string;
|
||||
// count: number;
|
||||
// percentage: number;
|
||||
// dropoff?: number;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Guest analytics data
|
||||
*/
|
||||
|
||||
export interface GuestAnalyticsOverview {
|
||||
totalGuestSessions: number;
|
||||
activeGuestSessions: number;
|
||||
expiredGuestSessions: number;
|
||||
convertedGuestSessions: number;
|
||||
conversionRate: number;
|
||||
}
|
||||
export interface GuestAnalyticsQuizActivity {
|
||||
totalGuestQuizzes: number;
|
||||
completedGuestQuizzes: number;
|
||||
guestQuizCompletionRate: number;
|
||||
avgQuizzesPerGuest: number;
|
||||
avgQuizzesBeforeConversion: number;
|
||||
}
|
||||
|
||||
export interface GuestAnalyticsBehavior {
|
||||
bounceRate: number;
|
||||
avgSessionDurationMinutes: number;
|
||||
}
|
||||
export interface GuestAnalyticsRecentActivity {
|
||||
last30Days: {
|
||||
newGuestSessions: number;
|
||||
conversions: number;
|
||||
};
|
||||
}
|
||||
export interface GuestAnalytics {
|
||||
overview: GuestAnalyticsOverview;
|
||||
quizActivity: GuestAnalyticsQuizActivity;
|
||||
behavior: GuestAnalyticsBehavior;
|
||||
recentActivity: GuestAnalyticsRecentActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* API response wrapper for guest analytics
|
||||
*/
|
||||
export interface GuestAnalyticsResponse {
|
||||
success: boolean;
|
||||
data: GuestAnalytics;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest access settings
|
||||
*/
|
||||
export interface GuestSettings {
|
||||
guestAccessEnabled: boolean;
|
||||
maxQuizzesPerDay: number;
|
||||
maxQuestionsPerQuiz: number;
|
||||
sessionExpiryHours: number;
|
||||
upgradePromptMessage: string;
|
||||
allowedCategories?: string[];
|
||||
features?: {
|
||||
canBookmark: boolean;
|
||||
canViewHistory: boolean;
|
||||
canExportResults: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* API response wrapper for guest settings
|
||||
*/
|
||||
export interface GuestSettingsResponse {
|
||||
success: boolean;
|
||||
data: GuestSettings;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin user data
|
||||
*/
|
||||
export interface AdminUser {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
role: 'user' | 'admin';
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
lastLoginAt?: string;
|
||||
profilePicture?: string | null;
|
||||
quizzesTaken?: number;
|
||||
averageScore?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User list query parameters
|
||||
*/
|
||||
export interface UserListParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
role?: 'all' | 'user' | 'admin';
|
||||
isActive?: 'all' | 'active' | 'inactive';
|
||||
sortBy?: 'username' | 'email' | 'createdAt' | 'lastLoginAt';
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated user list response
|
||||
*/
|
||||
export interface AdminUserListResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
users: AdminUser[];
|
||||
pagination: {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
totalItems: number;
|
||||
itemsPerPage: number;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
};
|
||||
};
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User activity entry
|
||||
*/
|
||||
export interface UserActivity {
|
||||
id: string;
|
||||
type:
|
||||
| 'login'
|
||||
| 'quiz_start'
|
||||
| 'quiz_complete'
|
||||
| 'bookmark'
|
||||
| 'profile_update'
|
||||
| 'role_change';
|
||||
description: string;
|
||||
timestamp: string;
|
||||
metadata?: {
|
||||
categoryName?: string;
|
||||
score?: number;
|
||||
questionCount?: number;
|
||||
oldRole?: string;
|
||||
newRole?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz history entry for user detail
|
||||
*/
|
||||
export interface UserQuizHistoryEntry {
|
||||
id: string;
|
||||
categoryId: string;
|
||||
categoryName: string;
|
||||
score: number;
|
||||
totalQuestions: number;
|
||||
percentage: number;
|
||||
timeTaken: number; // seconds
|
||||
completedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User statistics for detail view
|
||||
*/
|
||||
export interface UserStatistics {
|
||||
totalQuizzes: number;
|
||||
averageScore: number;
|
||||
totalQuestionsAnswered: number;
|
||||
correctAnswers: number;
|
||||
accuracy: number;
|
||||
currentStreak: number;
|
||||
longestStreak: number;
|
||||
totalTimeSpent: number; // seconds
|
||||
favoriteCategory?: {
|
||||
id: string;
|
||||
name: string;
|
||||
quizCount: number;
|
||||
};
|
||||
recentActivity: {
|
||||
lastQuizDate?: string;
|
||||
lastLoginDate?: string;
|
||||
quizzesThisWeek: number;
|
||||
quizzesThisMonth: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed user profile
|
||||
*/
|
||||
export interface AdminUserDetail {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
role: 'user' | 'admin';
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
lastLoginAt?: string;
|
||||
statistics: UserStatistics;
|
||||
quizHistory: UserQuizHistoryEntry[];
|
||||
activityTimeline: UserActivity[];
|
||||
metadata?: {
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
registrationMethod?: 'direct' | 'guest_conversion';
|
||||
guestSessionId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* API response wrapper for user detail
|
||||
*/
|
||||
export interface AdminUserDetailResponse {
|
||||
success: boolean;
|
||||
data: AdminUserDetail;
|
||||
message?: string;
|
||||
}
|
||||
57
src/app/core/models/bookmark.model.ts
Normal file
57
src/app/core/models/bookmark.model.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Bookmark Interface
|
||||
* Represents a bookmarked question
|
||||
*/
|
||||
export interface Bookmark {
|
||||
id: string;
|
||||
userId: string;
|
||||
questionId: string;
|
||||
question: BookmarkedQuestion;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmarked Question Details
|
||||
*/
|
||||
export interface BookmarkedQuestion {
|
||||
id: string;
|
||||
questionText: string;
|
||||
questionType: 'multiple-choice' | 'true-false' | 'written';
|
||||
difficulty: 'easy' | 'medium' | 'hard';
|
||||
categoryId: string;
|
||||
categoryName: string;
|
||||
options?: string[];
|
||||
correctAnswer: string;
|
||||
explanation?: string;
|
||||
points: number;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmarks Response
|
||||
*/
|
||||
export interface BookmarksResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
bookmarks: Bookmark[];
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Bookmark Request
|
||||
*/
|
||||
export interface AddBookmarkRequest {
|
||||
questionId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Bookmark Response
|
||||
*/
|
||||
export interface AddBookmarkResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
bookmark: Bookmark;
|
||||
};
|
||||
message: string;
|
||||
}
|
||||
82
src/app/core/models/category.model.ts
Normal file
82
src/app/core/models/category.model.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Category Interface
|
||||
* Represents a quiz category
|
||||
*/
|
||||
export interface Category {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
questionCount: number;
|
||||
displayOrder?: number;
|
||||
isActive: boolean;
|
||||
guestAccessible: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category Detail with Stats
|
||||
*/
|
||||
export interface CategoryDetail extends Category {
|
||||
questionPreview?: QuestionPreview[];
|
||||
stats?: CategoryStats;
|
||||
difficultyBreakdown?: {
|
||||
easy: number;
|
||||
medium: number;
|
||||
hard: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Category Statistics
|
||||
*/
|
||||
export interface CategoryStats {
|
||||
totalQuestions: number;
|
||||
questionsByDifficulty: {
|
||||
easy: number;
|
||||
medium: number;
|
||||
hard: number;
|
||||
};
|
||||
totalAttempts: number;
|
||||
totalCorrect: number;
|
||||
averageAccuracy: number;
|
||||
averageScore?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Question Preview (limited info)
|
||||
*/
|
||||
export interface QuestionPreview {
|
||||
id: string;
|
||||
questionText: string;
|
||||
questionType: QuestionType;
|
||||
difficulty: Difficulty;
|
||||
points: number;
|
||||
accuracy?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Question Types
|
||||
*/
|
||||
export type QuestionType = 'multiple' | 'trueFalse' | 'written';
|
||||
|
||||
/**
|
||||
* Difficulty Levels
|
||||
*/
|
||||
export type Difficulty = 'easy' | 'medium' | 'hard';
|
||||
|
||||
/**
|
||||
* Category Create/Update Request
|
||||
*/
|
||||
export interface CategoryFormData {
|
||||
name: string;
|
||||
slug?: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
displayOrder?: number;
|
||||
guestAccessible: boolean;
|
||||
}
|
||||
246
src/app/core/models/dashboard.model.ts
Normal file
246
src/app/core/models/dashboard.model.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { User } from './user.model';
|
||||
import { QuizSession, QuizSessionHistory } from './quiz.model';
|
||||
|
||||
/**
|
||||
* User Dashboard Response
|
||||
*/
|
||||
|
||||
export interface UserDataDashboard {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
role: string;
|
||||
profileImage: string | null;
|
||||
memberSince: string;
|
||||
}
|
||||
export interface StatsDashboard {
|
||||
totalQuizzes: number
|
||||
quizzesPassed: number
|
||||
passRate: number
|
||||
totalQuestionsAnswered: number
|
||||
correctAnswers: number
|
||||
overallAccuracy: number
|
||||
currentStreak: number
|
||||
longestStreak: number
|
||||
streakStatus: string;
|
||||
lastActiveDate: string | null
|
||||
|
||||
}
|
||||
export interface RecentSessionsScoreDashboard {
|
||||
earned: number
|
||||
total: number
|
||||
percentage: number
|
||||
}
|
||||
export interface RecentSessionsCategoryDashboard {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
icon: any
|
||||
color: string
|
||||
}
|
||||
export interface RecentSessionsDashboard {
|
||||
id: string
|
||||
category: RecentSessionsCategoryDashboard
|
||||
quizType: string
|
||||
difficulty: string
|
||||
status: string
|
||||
score: RecentSessionsScoreDashboard
|
||||
isPassed: boolean
|
||||
questionsAnswered: number
|
||||
correctAnswers: number
|
||||
accuracy: number
|
||||
timeSpent: number
|
||||
completedAt: string
|
||||
}
|
||||
export interface CategoryPerformanceStats {
|
||||
quizzesTaken: number
|
||||
quizzesPassed: number
|
||||
passRate: number
|
||||
averageScore: number
|
||||
totalQuestions: number
|
||||
correctAnswers: number
|
||||
accuracy: number
|
||||
}
|
||||
export interface CategoryPerformanceDashboard {
|
||||
category: RecentSessionsCategoryDashboard
|
||||
stats: CategoryPerformanceStats
|
||||
lastAttempt: string
|
||||
}
|
||||
|
||||
export interface RecentActivityDashboard {
|
||||
date: string
|
||||
quizzesCompleted: number
|
||||
}
|
||||
export interface UserDashboardResponse {
|
||||
success: boolean;
|
||||
data: UserDashboard
|
||||
}
|
||||
export interface UserDashboard {
|
||||
user: UserDataDashboard;
|
||||
stats: StatsDashboard;
|
||||
recentSessions: RecentSessionsDashboard[]
|
||||
categoryPerformance: CategoryPerformanceDashboard[]
|
||||
recentActivity: RecentActivityDashboard[]
|
||||
// totalQuizzes: number;
|
||||
// totalQuestionsAnswered: number;
|
||||
// overallAccuracy: number;
|
||||
// currentStreak: number;
|
||||
// longestStreak: number;
|
||||
// averageScore: number;
|
||||
// recentQuizzes: QuizSession[];
|
||||
// categoryPerformance: CategoryPerformance[];
|
||||
// achievements?: Achievement[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Category Performance
|
||||
*/
|
||||
export interface CategoryPerformance {
|
||||
categoryId: string;
|
||||
categoryName: string;
|
||||
quizzesTaken: number;
|
||||
averageScore: number;
|
||||
accuracy: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Quiz History
|
||||
*/
|
||||
export interface QuizHistoryResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
sessions: QuizSessionHistory[];
|
||||
pagination: PaginationInfo;
|
||||
filters: {
|
||||
category: null,
|
||||
status: null,
|
||||
startDate: null,
|
||||
endDate: null
|
||||
}
|
||||
sorting: {
|
||||
sortBy: string
|
||||
sortOrder: string
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination Info
|
||||
*/
|
||||
export interface PaginationInfo {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
totalItems: number;
|
||||
itemsPerPage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement
|
||||
*/
|
||||
export interface Achievement {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
earnedAt?: string;
|
||||
progress?: number;
|
||||
maxProgress?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Profile Update Request
|
||||
*/
|
||||
export interface UserProfileUpdate {
|
||||
username?: string;
|
||||
email?: string;
|
||||
currentPassword?: string;
|
||||
newPassword?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Profile Update Response
|
||||
*/
|
||||
export interface UserProfileUpdateResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
user: User;
|
||||
};
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmark
|
||||
*/
|
||||
export interface Bookmark {
|
||||
id: string;
|
||||
userId: string;
|
||||
questionId: string;
|
||||
question?: any; // Will use Question type
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmarks Response
|
||||
*/
|
||||
export interface BookmarksResponse {
|
||||
success: boolean;
|
||||
bookmarks: any[]; // Will contain Question objects
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Statistics
|
||||
*/
|
||||
export interface AdminStatistics {
|
||||
totalUsers: number;
|
||||
activeUsers: number;
|
||||
totalQuizSessions: number;
|
||||
totalQuestions: number;
|
||||
totalCategories: number;
|
||||
mostPopularCategories: PopularCategory[];
|
||||
averageQuizScore: number;
|
||||
userGrowth: UserGrowthData[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Popular Category
|
||||
*/
|
||||
export interface PopularCategory {
|
||||
categoryId: string;
|
||||
categoryName: string;
|
||||
quizzesTaken: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Growth Data
|
||||
*/
|
||||
export interface UserGrowthData {
|
||||
date: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Users List Response
|
||||
*/
|
||||
export interface AdminUsersResponse {
|
||||
success: boolean;
|
||||
users: User[];
|
||||
pagination: PaginationInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin User Details
|
||||
*/
|
||||
export interface AdminUserDetails extends User {
|
||||
quizHistory?: QuizSession[];
|
||||
activityTimeline?: ActivityEvent[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity Event
|
||||
*/
|
||||
export interface ActivityEvent {
|
||||
id: string;
|
||||
type: 'quiz_completed' | 'achievement_earned' | 'profile_updated';
|
||||
description: string;
|
||||
timestamp: string;
|
||||
}
|
||||
104
src/app/core/models/guest.model.ts
Normal file
104
src/app/core/models/guest.model.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Guest Session Interface
|
||||
* Represents a temporary guest user session
|
||||
*/
|
||||
export interface GuestSession {
|
||||
guestId: string;
|
||||
sessionToken: string;
|
||||
deviceId?: string;
|
||||
quizzesTaken: number;
|
||||
maxQuizzes: number;
|
||||
remainingQuizzes: number;
|
||||
expiresAt: string;
|
||||
isConverted: boolean;
|
||||
convertedUserId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest Session Start Response
|
||||
*/
|
||||
export interface GuestSessionStartResponse {
|
||||
success: boolean;
|
||||
sessionToken: string;
|
||||
guestId: string;
|
||||
expiresAt: string;
|
||||
maxQuizzes: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest Quiz Limit Response
|
||||
*/
|
||||
export interface GuestQuizLimitResponse {
|
||||
success: boolean;
|
||||
remainingQuizzes: number;
|
||||
maxQuizzes: number;
|
||||
quizzesTaken: number;
|
||||
expiresAt: string;
|
||||
upgradePrompt?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest to User Conversion Request
|
||||
*/
|
||||
export interface GuestConversionRequest {
|
||||
guestSessionId: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest Settings (Admin)
|
||||
*/
|
||||
export interface GuestSettings {
|
||||
id: string;
|
||||
guestAccessEnabled: boolean;
|
||||
maxQuizzesPerDay: number;
|
||||
maxQuestionsPerQuiz: number;
|
||||
sessionExpiryHours: number;
|
||||
upgradePromptMessage: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest Analytics (Admin)
|
||||
*/
|
||||
export interface GuestAnalytics {
|
||||
totalGuestSessions: number;
|
||||
activeGuestSessions: number;
|
||||
guestToUserConversionRate: number;
|
||||
averageQuizzesPerGuest: number;
|
||||
totalGuestQuizzes: number;
|
||||
conversionFunnel?: {
|
||||
totalSessions: number;
|
||||
startedQuiz: number;
|
||||
completedQuiz: number;
|
||||
converted: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest Quiz Limit
|
||||
* Tracks remaining quiz attempts for guest
|
||||
*/
|
||||
export interface GuestLimit {
|
||||
maxQuizzes: number;
|
||||
quizzesTaken: number;
|
||||
quizzesRemaining: number;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guest State (for signal management)
|
||||
*/
|
||||
export interface GuestState {
|
||||
session: GuestSession | null;
|
||||
isGuest: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
quizLimit: GuestLimit | null;
|
||||
}
|
||||
90
src/app/core/models/index.ts
Normal file
90
src/app/core/models/index.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* API Response Wrapper
|
||||
*/
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Error Response
|
||||
*/
|
||||
export interface ApiError {
|
||||
message: string;
|
||||
error?: string;
|
||||
statusCode?: number;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP Error
|
||||
*/
|
||||
export interface HttpError {
|
||||
status: number;
|
||||
statusText: string;
|
||||
message: string;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loading State
|
||||
*/
|
||||
export interface LoadingState {
|
||||
isLoading: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toast Notification
|
||||
*/
|
||||
export interface ToastNotification {
|
||||
id?: string;
|
||||
type: 'success' | 'error' | 'warning' | 'info';
|
||||
message: string;
|
||||
duration?: number;
|
||||
action?: ToastAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toast Action
|
||||
*/
|
||||
export interface ToastAction {
|
||||
label: string;
|
||||
callback: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort Options
|
||||
*/
|
||||
export interface SortOptions {
|
||||
sortBy: string;
|
||||
sortOrder: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Options
|
||||
*/
|
||||
export interface FilterOptions {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Options
|
||||
*/
|
||||
export interface SearchOptions {
|
||||
query: string;
|
||||
filters?: FilterOptions;
|
||||
sort?: SortOptions;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
// Export all models
|
||||
export * from './user.model';
|
||||
export * from './category.model';
|
||||
export * from './question.model';
|
||||
export * from './quiz.model';
|
||||
export * from './guest.model';
|
||||
export * from './dashboard.model';
|
||||
79
src/app/core/models/question.model.ts
Normal file
79
src/app/core/models/question.model.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { QuestionType, Difficulty } from './category.model';
|
||||
|
||||
/**
|
||||
* Question Interface
|
||||
* Represents a quiz question
|
||||
*/
|
||||
export interface Question {
|
||||
id: string;
|
||||
questionText: string;
|
||||
questionType: QuestionType;
|
||||
difficulty: Difficulty;
|
||||
categoryId: string;
|
||||
categoryName?: string;
|
||||
category?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
guestAccessible?: boolean;
|
||||
};
|
||||
options?: string[] | { id: string; text: string }[]; // For multiple choice
|
||||
correctAnswer: string | string[];
|
||||
explanation: string;
|
||||
points: number;
|
||||
timeLimit?: number; // in seconds
|
||||
tags?: string[];
|
||||
keywords?: string[];
|
||||
isActive: boolean;
|
||||
isPublic: boolean;
|
||||
timesAttempted?: number;
|
||||
timesCorrect?: number;
|
||||
accuracy?: number;
|
||||
createdBy?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Question Create/Update Request
|
||||
*/
|
||||
export interface QuestionFormData {
|
||||
questionText: string;
|
||||
questionType: QuestionType;
|
||||
difficulty: Difficulty;
|
||||
categoryId: string;
|
||||
options?: string[];
|
||||
correctAnswer: string | string[];
|
||||
explanation: string;
|
||||
points?: number;
|
||||
timeLimit?: number;
|
||||
tags?: string[];
|
||||
keywords?: string[];
|
||||
isPublic: boolean;
|
||||
isGuestAccessible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Question Search Filters
|
||||
*/
|
||||
export interface QuestionSearchFilters {
|
||||
q?: string; // search query
|
||||
category?: string;
|
||||
difficulty?: Difficulty;
|
||||
questionType?: QuestionType;
|
||||
isPublic?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Question Search Response
|
||||
*/
|
||||
export interface QuestionSearchResponse {
|
||||
results: Question[];
|
||||
totalCount: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
288
src/app/core/models/quiz.model.ts
Normal file
288
src/app/core/models/quiz.model.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { Category } from './category.model';
|
||||
import { Question } from './question.model';
|
||||
|
||||
export interface QuizSessionHistory {
|
||||
|
||||
time: {
|
||||
spent: number,
|
||||
limit: number | null,
|
||||
percentage: number
|
||||
},
|
||||
createdAt: string;
|
||||
id: string;
|
||||
|
||||
category?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
};
|
||||
quizType: QuizType;
|
||||
difficulty: string;
|
||||
questions: {
|
||||
answered: number,
|
||||
total: number,
|
||||
correct: number,
|
||||
accuracy: number
|
||||
};
|
||||
score: {
|
||||
earned: number
|
||||
total: number
|
||||
percentage: number
|
||||
};
|
||||
|
||||
status: QuizStatus;
|
||||
startedAt: string;
|
||||
completedAt?: string;
|
||||
isPassed?: boolean;
|
||||
}
|
||||
/**
|
||||
* Quiz Session Interface
|
||||
* Represents an active or completed quiz session
|
||||
*/
|
||||
export interface QuizSession {
|
||||
id: string;
|
||||
userId?: string;
|
||||
guestSessionId?: string;
|
||||
categoryId: string;
|
||||
categoryName?: string;
|
||||
quizType: QuizType;
|
||||
difficulty: string;
|
||||
totalQuestions: number;
|
||||
currentQuestionIndex: number;
|
||||
score: number;
|
||||
correctAnswers: number;
|
||||
incorrectAnswers: number;
|
||||
skippedAnswers: number;
|
||||
status: QuizStatus;
|
||||
startedAt: string;
|
||||
completedAt?: string;
|
||||
timeSpent?: number; // in seconds
|
||||
isPassed?: boolean;
|
||||
passingScore?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Types
|
||||
*/
|
||||
export type QuizType = 'practice' | 'timed' | 'exam';
|
||||
|
||||
/**
|
||||
* Quiz Status
|
||||
*/
|
||||
export type QuizStatus = 'in_progress' | 'completed' | 'abandoned';
|
||||
|
||||
/**
|
||||
* Quiz Start Request
|
||||
*/
|
||||
export interface QuizStartRequest {
|
||||
success: true;
|
||||
data: {
|
||||
categoryId: string;
|
||||
questionCount: number;
|
||||
difficulty?: string; // 'easy', 'medium', 'hard', 'mixed'
|
||||
quizType?: QuizType;
|
||||
};
|
||||
}
|
||||
export interface QuizStartFormRequest {
|
||||
|
||||
categoryId: string;
|
||||
questionCount: number;
|
||||
difficulty?: string; // 'easy', 'medium', 'hard', 'mixed'
|
||||
quizType?: QuizType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Start Response
|
||||
*/
|
||||
export interface QuizStartResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
sessionId: string;
|
||||
questions: Question[];
|
||||
totalQuestions: number;
|
||||
message?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Answer Submission
|
||||
*/
|
||||
export interface QuizAnswerSubmission {
|
||||
questionId: string;
|
||||
userAnswer: string | string[];
|
||||
quizSessionId: string;
|
||||
timeSpent?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Answer Response
|
||||
*/
|
||||
export interface QuizAnswerResponse {
|
||||
success: boolean;
|
||||
isCorrect: boolean;
|
||||
correctAnswer: string | string[];
|
||||
explanation: string;
|
||||
points: number;
|
||||
score: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Results
|
||||
*/
|
||||
export interface QuizResults {
|
||||
success: boolean;
|
||||
score: number;
|
||||
totalQuestions: number;
|
||||
correctAnswers: number;
|
||||
incorrectAnswers: number;
|
||||
skippedAnswers: number;
|
||||
percentage: number;
|
||||
timeSpent: number;
|
||||
isPassed: boolean;
|
||||
performanceMessage: string;
|
||||
questions: QuizQuestionResult[];
|
||||
}
|
||||
|
||||
|
||||
// Response from /complete endpoint - questions are statistics
|
||||
export interface CompletedQuizResult {
|
||||
sessionId: string;
|
||||
status: string;
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
icon: string;
|
||||
color: string
|
||||
},
|
||||
quizType: string
|
||||
difficulty: string
|
||||
score: {
|
||||
earned: number,
|
||||
total: number,
|
||||
percentage: number
|
||||
},
|
||||
questions: {
|
||||
total: number,
|
||||
answered: number,
|
||||
correct: number,
|
||||
incorrect: number,
|
||||
unanswered: number
|
||||
},
|
||||
accuracy: number,
|
||||
isPassed: boolean,
|
||||
time: {
|
||||
started: string,
|
||||
completed: string,
|
||||
taken: number,
|
||||
limit: number,
|
||||
isTimeout: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompletedQuizResponse {
|
||||
success: boolean
|
||||
data: CompletedQuizResult
|
||||
}
|
||||
|
||||
// Response from /review endpoint - questions are detailed array
|
||||
export interface QuizReviewResult {
|
||||
session: {
|
||||
id: string;
|
||||
status: string;
|
||||
quizType: string;
|
||||
difficulty: string;
|
||||
category: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
};
|
||||
startedAt: string;
|
||||
completedAt: string;
|
||||
timeSpent: number;
|
||||
};
|
||||
summary: {
|
||||
score: {
|
||||
earned: number;
|
||||
total: number;
|
||||
percentage: number;
|
||||
};
|
||||
questions: {
|
||||
total: number;
|
||||
answered: number;
|
||||
correct: number;
|
||||
incorrect: number;
|
||||
unanswered: number;
|
||||
};
|
||||
accuracy: number;
|
||||
isPassed: boolean;
|
||||
timeStatistics: {
|
||||
totalTime: number;
|
||||
averageTimePerQuestion: number;
|
||||
timeLimit: number | null;
|
||||
wasTimedOut: boolean;
|
||||
};
|
||||
};
|
||||
questions: QuizQuestionResult[];
|
||||
}
|
||||
|
||||
export interface QuizReviewResponse {
|
||||
success: boolean;
|
||||
data: QuizReviewResult;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Question Result
|
||||
*/
|
||||
export interface QuizQuestionResult {
|
||||
id: string;
|
||||
questionText: string;
|
||||
questionType: string;
|
||||
options: any;
|
||||
difficulty: string;
|
||||
points: number;
|
||||
explanation: string;
|
||||
tags: string[];
|
||||
order: number;
|
||||
correctAnswer: string | string[];
|
||||
userAnswer: string | string[] | null;
|
||||
isCorrect: boolean | null;
|
||||
resultStatus: 'correct' | 'incorrect' | 'unanswered';
|
||||
pointsEarned: number;
|
||||
pointsPossible: number;
|
||||
timeTaken: number | null;
|
||||
answeredAt: string | null;
|
||||
showExplanation: boolean;
|
||||
wasAnswered: boolean;
|
||||
// Legacy support
|
||||
questionId?: string;
|
||||
timeSpent?: number;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Session State (for signal management)
|
||||
*/
|
||||
export interface QuizSessionState {
|
||||
session: QuizSession | null;
|
||||
questions: Question[];
|
||||
currentQuestionIndex: number;
|
||||
answers: Map<string, QuizAnswerResponse>;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quiz Review Response
|
||||
*/
|
||||
export interface QuizReviewResponse {
|
||||
success: boolean;
|
||||
session: QuizSession;
|
||||
questions: QuizQuestionResult[];
|
||||
}
|
||||
64
src/app/core/models/user.model.ts
Normal file
64
src/app/core/models/user.model.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* User Interface
|
||||
* Represents a registered user in the system
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
role: 'user' | 'admin';
|
||||
isActive: boolean;
|
||||
totalQuizzesTaken?: number;
|
||||
totalQuestionsAnswered?: number;
|
||||
totalCorrectAnswers?: number;
|
||||
currentStreak?: number;
|
||||
longestStreak?: number;
|
||||
averageScore?: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Registration Request
|
||||
*/
|
||||
export interface UserRegistration {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
guestSessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Login Request
|
||||
*/
|
||||
export interface UserLogin {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth Response
|
||||
*/
|
||||
export interface AuthResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
user: User;
|
||||
token: string;
|
||||
};
|
||||
|
||||
message?: string;
|
||||
migratedStats?: {
|
||||
quizzesTaken: number;
|
||||
score: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth State (for signal management)
|
||||
*/
|
||||
export interface AuthState {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user