student-help 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +44 -0
- package/bin/student-help.js +14 -0
- package/cli.mjs +1415 -0
- package/commands/db.ts +59 -0
- package/commands/dev.ts +23 -0
- package/commands/generate/auth.ts +31 -0
- package/commands/generate/install.ts +34 -0
- package/commands/generate/resource.ts +184 -0
- package/commands/new.ts +179 -0
- package/package.json +62 -0
- package/templates/backend/controller.ts +41 -0
- package/templates/backend/dto.ts +20 -0
- package/templates/backend/module.ts +16 -0
- package/templates/backend/service.ts +52 -0
- package/templates/backend/student-help.controller.ts +332 -0
- package/templates/backend/student-help.module.ts +9 -0
- package/templates/backend/student-help.service.ts +697 -0
- package/templates/frontend/page.tsx +13 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import * as bcrypt from 'bcrypt';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ============================================
|
|
7
|
+
* STUDENT HELP SERVICE - Comprehensive Utilities
|
|
8
|
+
* ============================================
|
|
9
|
+
* Global service with functions to solve various problems
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ========== INTERFACES ==========
|
|
13
|
+
export interface ValidationResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
errors?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FileUploadResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
message: string;
|
|
22
|
+
filename?: string;
|
|
23
|
+
size?: number;
|
|
24
|
+
path?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExportResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
message: string;
|
|
30
|
+
data?: string;
|
|
31
|
+
filename?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface EmailNotification {
|
|
35
|
+
to: string;
|
|
36
|
+
subject: string;
|
|
37
|
+
message: string;
|
|
38
|
+
html?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface StudentUser {
|
|
42
|
+
id?: string;
|
|
43
|
+
email: string;
|
|
44
|
+
name: string;
|
|
45
|
+
password?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AuthResponse {
|
|
49
|
+
success: boolean;
|
|
50
|
+
message: string;
|
|
51
|
+
token?: string;
|
|
52
|
+
user?: StudentUser;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ========== MAIN SERVICE ==========
|
|
56
|
+
@Injectable()
|
|
57
|
+
export class StudentHelpService {
|
|
58
|
+
private users: Map<string, StudentUser> = new Map();
|
|
59
|
+
private sessions: Map<string, AuthResponse> = new Map();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ========================================
|
|
63
|
+
* 🔐 AUTHENTICATION & SECURITY
|
|
64
|
+
* ========================================
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Hash a password with bcrypt
|
|
69
|
+
*/
|
|
70
|
+
async hashPassword(password: string, rounds: number = 10): Promise<string> {
|
|
71
|
+
return bcrypt.hash(password, rounds);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Compare password with hash
|
|
76
|
+
*/
|
|
77
|
+
async comparePassword(password: string, hash: string): Promise<boolean> {
|
|
78
|
+
return bcrypt.compare(password, hash);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate JWT-like token
|
|
83
|
+
*/
|
|
84
|
+
generateToken(payload: any, secret: string = process.env.JWT_SECRET || 'default-secret'): string {
|
|
85
|
+
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
|
|
86
|
+
const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
87
|
+
const signature = crypto
|
|
88
|
+
.createHmac('sha256', secret)
|
|
89
|
+
.update(`${header}.${body}`)
|
|
90
|
+
.digest('base64url');
|
|
91
|
+
|
|
92
|
+
return `${header}.${body}.${signature}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate random token
|
|
97
|
+
*/
|
|
98
|
+
generateRandomToken(length: number = 32): string {
|
|
99
|
+
return crypto.randomBytes(length).toString('hex');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Encrypt text with AES-256
|
|
104
|
+
*/
|
|
105
|
+
encryptText(text: string, key: string = process.env.ENCRYPTION_KEY || 'default-key'): string {
|
|
106
|
+
const cipher = crypto.createCipher('aes-256-cbc', key);
|
|
107
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
108
|
+
encrypted += cipher.final('hex');
|
|
109
|
+
return encrypted;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Decrypt text
|
|
114
|
+
*/
|
|
115
|
+
decryptText(encrypted: string, key: string = process.env.ENCRYPTION_KEY || 'default-key'): string {
|
|
116
|
+
const decipher = crypto.createDecipher('aes-256-cbc', key);
|
|
117
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
118
|
+
decrypted += decipher.final('utf8');
|
|
119
|
+
return decrypted;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate API Key
|
|
124
|
+
*/
|
|
125
|
+
generateApiKey(): string {
|
|
126
|
+
return 'sk_' + crypto.randomBytes(32).toString('hex');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Register a new user
|
|
131
|
+
*/
|
|
132
|
+
async register(email: string, name: string, password: string): Promise<AuthResponse> {
|
|
133
|
+
try {
|
|
134
|
+
if (this._getUserByEmail(email)) {
|
|
135
|
+
return { success: false, message: 'User already exists' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
139
|
+
const userId = `user_${Date.now()}`;
|
|
140
|
+
const user: StudentUser = { id: userId, email, name, password: hashedPassword };
|
|
141
|
+
|
|
142
|
+
this.users.set(userId, user);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
message: 'User registered successfully',
|
|
147
|
+
user: { id: userId, email, name }
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return { success: false, message: `Registration failed: ${error.message}` };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Login a user
|
|
156
|
+
*/
|
|
157
|
+
async login(email: string, password: string): Promise<AuthResponse> {
|
|
158
|
+
try {
|
|
159
|
+
const user = this._getUserByEmail(email);
|
|
160
|
+
|
|
161
|
+
if (!user || !user.password) {
|
|
162
|
+
return { success: false, message: 'Invalid credentials' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isPasswordValid = await bcrypt.compare(password, user.password);
|
|
166
|
+
|
|
167
|
+
if (!isPasswordValid) {
|
|
168
|
+
return { success: false, message: 'Invalid credentials' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const response: AuthResponse = {
|
|
172
|
+
success: true,
|
|
173
|
+
message: 'Login successful',
|
|
174
|
+
user: { id: user.id, email: user.email, name: user.name }
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
this.sessions.set(user.id || '', response);
|
|
178
|
+
return response;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return { success: false, message: `Login failed: ${error.message}` };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Logout a user
|
|
186
|
+
*/
|
|
187
|
+
async logout(userId: string): Promise<AuthResponse> {
|
|
188
|
+
try {
|
|
189
|
+
this.sessions.delete(userId);
|
|
190
|
+
return { success: true, message: 'Logout successful' };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return { success: false, message: `Logout failed: ${error.message}` };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* ========================================
|
|
198
|
+
* 📧 EMAIL & NOTIFICATIONS
|
|
199
|
+
* ========================================
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Send email notification
|
|
204
|
+
*/
|
|
205
|
+
async sendEmail(notification: EmailNotification): Promise<{ success: boolean; message: string }> {
|
|
206
|
+
try {
|
|
207
|
+
console.log(`📧 Email sent to ${notification.to}`);
|
|
208
|
+
return { success: true, message: `Email sent to ${notification.to}` };
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return { success: false, message: `Failed to send email: ${error.message}` };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Send SMS notification
|
|
216
|
+
*/
|
|
217
|
+
async sendSMS(phoneNumber: string, message: string): Promise<{ success: boolean; message: string }> {
|
|
218
|
+
try {
|
|
219
|
+
console.log(`📱 SMS sent to ${phoneNumber}`);
|
|
220
|
+
return { success: true, message: `SMS sent to ${phoneNumber}` };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return { success: false, message: `Failed to send SMS: ${error.message}` };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Send webhook notification
|
|
228
|
+
*/
|
|
229
|
+
async sendWebhook(url: string, data: any): Promise<{ success: boolean; message: string }> {
|
|
230
|
+
try {
|
|
231
|
+
console.log(`🔔 Webhook sent to ${url}`);
|
|
232
|
+
return { success: true, message: `Webhook sent to ${url}` };
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return { success: false, message: `Failed to send webhook: ${error.message}` };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* ========================================
|
|
240
|
+
* 📄 DATA VALIDATION
|
|
241
|
+
* ========================================
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Validate email
|
|
246
|
+
*/
|
|
247
|
+
validateEmail(email: string): ValidationResult {
|
|
248
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
249
|
+
const valid = emailRegex.test(email);
|
|
250
|
+
|
|
251
|
+
return { valid, message: valid ? 'Valid email' : 'Invalid email' };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Validate password strength
|
|
256
|
+
*/
|
|
257
|
+
validatePasswordStrength(password: string): ValidationResult {
|
|
258
|
+
const errors: string[] = [];
|
|
259
|
+
|
|
260
|
+
if (password.length < 8) errors.push('Password must be at least 8 characters');
|
|
261
|
+
if (!/[A-Z]/.test(password)) errors.push('Password must contain uppercase letters');
|
|
262
|
+
if (!/[a-z]/.test(password)) errors.push('Password must contain lowercase letters');
|
|
263
|
+
if (!/[0-9]/.test(password)) errors.push('Password must contain numbers');
|
|
264
|
+
if (!/[!@#$%^&*]/.test(password)) errors.push('Password must contain special characters');
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
valid: errors.length === 0,
|
|
268
|
+
message: errors.length === 0 ? 'Strong password' : 'Weak password',
|
|
269
|
+
errors
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validate CPF (Brazilian)
|
|
275
|
+
*/
|
|
276
|
+
validateCPF(cpf: string): ValidationResult {
|
|
277
|
+
const cleanCPF = cpf.replace(/\D/g, '');
|
|
278
|
+
|
|
279
|
+
if (cleanCPF.length !== 11 || /^(\d)\1{10}$/.test(cleanCPF)) {
|
|
280
|
+
return { valid: false, message: 'Invalid CPF' };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const calculate = (s: number): number => {
|
|
284
|
+
let sum = 0;
|
|
285
|
+
let multiplier = s + 1;
|
|
286
|
+
for (let i = 0; i < s; i++) {
|
|
287
|
+
sum += parseInt(cleanCPF.charAt(i)) * multiplier--;
|
|
288
|
+
}
|
|
289
|
+
const remainder = (sum * 10) % 11;
|
|
290
|
+
return remainder === 10 ? 0 : remainder;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const digit1 = calculate(9);
|
|
294
|
+
const digit2 = calculate(10);
|
|
295
|
+
const isValid = parseInt(cleanCPF.charAt(9)) === digit1 && parseInt(cleanCPF.charAt(10)) === digit2;
|
|
296
|
+
|
|
297
|
+
return { valid: isValid, message: isValid ? 'Valid CPF' : 'Invalid CPF' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Validate phone number
|
|
302
|
+
*/
|
|
303
|
+
validatePhoneNumber(phone: string): ValidationResult {
|
|
304
|
+
const cleanPhone = phone.replace(/\D/g, '');
|
|
305
|
+
const valid = cleanPhone.length >= 10 && cleanPhone.length <= 15;
|
|
306
|
+
|
|
307
|
+
return { valid, message: valid ? 'Valid phone number' : 'Invalid phone number' };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Validate URL
|
|
312
|
+
*/
|
|
313
|
+
validateURL(url: string): ValidationResult {
|
|
314
|
+
try {
|
|
315
|
+
new URL(url);
|
|
316
|
+
return { valid: true, message: 'Valid URL' };
|
|
317
|
+
} catch {
|
|
318
|
+
return { valid: false, message: 'Invalid URL' };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* ========================================
|
|
324
|
+
* 📝 DATA TRANSFORMATION & SANITIZATION
|
|
325
|
+
* ========================================
|
|
326
|
+
*/
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Sanitize string
|
|
330
|
+
*/
|
|
331
|
+
sanitizeString(str: string): string {
|
|
332
|
+
return str.replace(/<[^>]*>/g, '').trim().substring(0, 1000);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Capitalize first letter
|
|
337
|
+
*/
|
|
338
|
+
capitalize(str: string): string {
|
|
339
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Convert to slug
|
|
344
|
+
*/
|
|
345
|
+
toSlug(str: string): string {
|
|
346
|
+
return str
|
|
347
|
+
.toLowerCase()
|
|
348
|
+
.replace(/[^\w\s-]/g, '')
|
|
349
|
+
.replace(/[\s_]+/g, '-')
|
|
350
|
+
.replace(/^-+|-+$/g, '');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Format currency
|
|
355
|
+
*/
|
|
356
|
+
formatCurrency(amount: number, currency: string = 'BRL'): string {
|
|
357
|
+
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency }).format(amount);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Format date
|
|
362
|
+
*/
|
|
363
|
+
formatDate(date: Date, format: string = 'DD/MM/YYYY'): string {
|
|
364
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
365
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
366
|
+
const year = date.getFullYear();
|
|
367
|
+
|
|
368
|
+
return format.replace('DD', day).replace('MM', month).replace('YYYY', String(year));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Parse JSON safely
|
|
373
|
+
*/
|
|
374
|
+
safeParseJSON(jsonString: string, defaultValue: any = null): any {
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(jsonString);
|
|
377
|
+
} catch {
|
|
378
|
+
return defaultValue;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* ========================================
|
|
384
|
+
* 📊 DATA EXPORT
|
|
385
|
+
* ========================================
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Generate CSV from array of objects
|
|
390
|
+
*/
|
|
391
|
+
generateCSV(data: any[], filename: string = 'export.csv'): ExportResult {
|
|
392
|
+
try {
|
|
393
|
+
if (!data || data.length === 0) {
|
|
394
|
+
return { success: false, message: 'No data to export' };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const keys = Object.keys(data[0]);
|
|
398
|
+
const header = keys.join(',');
|
|
399
|
+
const rows = data.map(obj =>
|
|
400
|
+
keys.map(key => `"${String(obj[key] || '').replace(/"/g, '""')}"`).join(',')
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const csv = [header, ...rows].join('\n');
|
|
404
|
+
|
|
405
|
+
return { success: true, message: 'CSV generated successfully', data: csv, filename };
|
|
406
|
+
} catch (error) {
|
|
407
|
+
return { success: false, message: `Failed to generate CSV: ${error.message}` };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Generate JSON from data
|
|
413
|
+
*/
|
|
414
|
+
generateJSON(data: any, filename: string = 'export.json'): ExportResult {
|
|
415
|
+
try {
|
|
416
|
+
const json = JSON.stringify(data, null, 2);
|
|
417
|
+
return { success: true, message: 'JSON generated successfully', data: json, filename };
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return { success: false, message: `Failed to generate JSON: ${error.message}` };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate basic report
|
|
425
|
+
*/
|
|
426
|
+
generateReport(title: string, content: any): ExportResult {
|
|
427
|
+
try {
|
|
428
|
+
const report = `
|
|
429
|
+
===============================================
|
|
430
|
+
${title}
|
|
431
|
+
===============================================
|
|
432
|
+
Generated at: ${new Date().toLocaleString('pt-BR')}
|
|
433
|
+
|
|
434
|
+
${JSON.stringify(content, null, 2)}
|
|
435
|
+
|
|
436
|
+
===============================================
|
|
437
|
+
`.trim();
|
|
438
|
+
|
|
439
|
+
return { success: true, message: 'Report generated successfully', data: report, filename: `report_${Date.now()}.txt` };
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return { success: false, message: `Failed to generate report: ${error.message}` };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* ========================================
|
|
447
|
+
* 📁 FILE UPLOAD & PROCESSING
|
|
448
|
+
* ========================================
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Validate file upload
|
|
453
|
+
*/
|
|
454
|
+
validateFileUpload(filename: string, size: number, allowedTypes: string[] = ['jpg', 'png', 'pdf']): FileUploadResult {
|
|
455
|
+
const maxSize = 10 * 1024 * 1024;
|
|
456
|
+
|
|
457
|
+
if (size > maxSize) {
|
|
458
|
+
return { success: false, message: `File size exceeds limit (${maxSize / 1024 / 1024}MB)` };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
462
|
+
|
|
463
|
+
if (!allowedTypes.includes(ext)) {
|
|
464
|
+
return { success: false, message: `File type not allowed. Allowed: ${allowedTypes.join(', ')}` };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return { success: true, message: 'File is valid' };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Generate safe filename
|
|
472
|
+
*/
|
|
473
|
+
generateSafeFilename(originalFilename: string): string {
|
|
474
|
+
const sanitized = originalFilename.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
475
|
+
const timestamp = Date.now();
|
|
476
|
+
const ext = sanitized.split('.').pop();
|
|
477
|
+
|
|
478
|
+
return `${timestamp}_${sanitized.split('.')[0]}.${ext}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Process image
|
|
483
|
+
*/
|
|
484
|
+
async processImage(filename: string, width?: number, height?: number): Promise<FileUploadResult> {
|
|
485
|
+
return { success: true, message: 'Image processed successfully', filename: `resized_${filename}` };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* ========================================
|
|
490
|
+
* 🧮 MATHEMATICAL & CONVERSION UTILITIES
|
|
491
|
+
* ========================================
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Calculate percentage
|
|
496
|
+
*/
|
|
497
|
+
calculatePercentage(value: number, total: number): number {
|
|
498
|
+
return total === 0 ? 0 : (value / total) * 100;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Calculate discount
|
|
503
|
+
*/
|
|
504
|
+
calculateDiscount(originalPrice: number, discountPercent: number): number {
|
|
505
|
+
return originalPrice * (1 - discountPercent / 100);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Calculate compound interest
|
|
510
|
+
*/
|
|
511
|
+
calculateCompoundInterest(principal: number, rate: number, time: number, compounds: number = 12): number {
|
|
512
|
+
return principal * Math.pow(1 + rate / 100 / compounds, compounds * time);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Convert temperature
|
|
517
|
+
*/
|
|
518
|
+
convertTemperature(value: number, from: 'C' | 'F' | 'K', to: 'C' | 'F' | 'K'): number {
|
|
519
|
+
let celsius: number;
|
|
520
|
+
|
|
521
|
+
if (from === 'C') {
|
|
522
|
+
celsius = value;
|
|
523
|
+
} else if (from === 'F') {
|
|
524
|
+
celsius = (value - 32) * (5 / 9);
|
|
525
|
+
} else {
|
|
526
|
+
celsius = value - 273.15;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (to === 'C') {
|
|
530
|
+
return Math.round(celsius * 100) / 100;
|
|
531
|
+
} else if (to === 'F') {
|
|
532
|
+
return Math.round((celsius * 9) / 5 + 32 * 100) / 100;
|
|
533
|
+
} else {
|
|
534
|
+
return Math.round((celsius + 273.15) * 100) / 100;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Convert distance units
|
|
540
|
+
*/
|
|
541
|
+
convertDistance(value: number, from: 'mm' | 'cm' | 'm' | 'km', to: 'mm' | 'cm' | 'm' | 'km'): number {
|
|
542
|
+
const toMm = { mm: 1, cm: 10, m: 1000, km: 1000000 };
|
|
543
|
+
const valueInMm = value * toMm[from];
|
|
544
|
+
return valueInMm / toMm[to];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Generate random number
|
|
549
|
+
*/
|
|
550
|
+
randomNumber(min: number, max: number): number {
|
|
551
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Generate random string
|
|
556
|
+
*/
|
|
557
|
+
randomString(length: number = 10): string {
|
|
558
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
559
|
+
let result = '';
|
|
560
|
+
|
|
561
|
+
for (let i = 0; i < length; i++) {
|
|
562
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* ========================================
|
|
570
|
+
* 🛠️ GENERAL UTILITIES
|
|
571
|
+
* ========================================
|
|
572
|
+
*/
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Delay execution (sleep)
|
|
576
|
+
*/
|
|
577
|
+
sleep(milliseconds: number): Promise<void> {
|
|
578
|
+
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Retry function with exponential backoff
|
|
583
|
+
*/
|
|
584
|
+
async retry<T>(fn: () => Promise<T>, maxAttempts: number = 3, delayMs: number = 1000): Promise<T> {
|
|
585
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
586
|
+
try {
|
|
587
|
+
return await fn();
|
|
588
|
+
} catch (error) {
|
|
589
|
+
if (attempt === maxAttempts) throw error;
|
|
590
|
+
await this.sleep(delayMs * attempt);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
throw new Error('Max retries exceeded');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Get user by ID
|
|
598
|
+
*/
|
|
599
|
+
getUser(userId: string): StudentUser | null {
|
|
600
|
+
const user = this.users.get(userId);
|
|
601
|
+
if (user) {
|
|
602
|
+
const { password, ...userWithoutPassword } = user;
|
|
603
|
+
return userWithoutPassword as StudentUser;
|
|
604
|
+
}
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Get all users
|
|
610
|
+
*/
|
|
611
|
+
getAllUsers(): StudentUser[] {
|
|
612
|
+
const users: StudentUser[] = [];
|
|
613
|
+
this.users.forEach((user) => {
|
|
614
|
+
const { password, ...userWithoutPassword } = user;
|
|
615
|
+
users.push(userWithoutPassword as StudentUser);
|
|
616
|
+
});
|
|
617
|
+
return users;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Update user profile
|
|
622
|
+
*/
|
|
623
|
+
async updateUser(userId: string, updateData: Partial<StudentUser>): Promise<AuthResponse> {
|
|
624
|
+
try {
|
|
625
|
+
const user = this.users.get(userId);
|
|
626
|
+
if (!user) {
|
|
627
|
+
return { success: false, message: 'User not found' };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (updateData.name) user.name = updateData.name;
|
|
631
|
+
if (updateData.email) user.email = updateData.email;
|
|
632
|
+
|
|
633
|
+
return { success: true, message: 'User updated successfully', user: { id: user.id, email: user.email, name: user.name } };
|
|
634
|
+
} catch (error) {
|
|
635
|
+
return { success: false, message: `Update failed: ${error.message}` };
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Delete user
|
|
641
|
+
*/
|
|
642
|
+
async deleteUser(userId: string): Promise<AuthResponse> {
|
|
643
|
+
try {
|
|
644
|
+
const user = this.users.get(userId);
|
|
645
|
+
if (!user) {
|
|
646
|
+
return { success: false, message: 'User not found' };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
this.users.delete(userId);
|
|
650
|
+
this.sessions.delete(userId);
|
|
651
|
+
|
|
652
|
+
return { success: true, message: 'User deleted successfully' };
|
|
653
|
+
} catch (error) {
|
|
654
|
+
return { success: false, message: `Delete failed: ${error.message}` };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Check if user is authenticated
|
|
660
|
+
*/
|
|
661
|
+
isAuthenticated(userId: string): boolean {
|
|
662
|
+
return this.sessions.has(userId);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Get user by email (private helper)
|
|
667
|
+
*/
|
|
668
|
+
private _getUserByEmail(email: string): StudentUser | null {
|
|
669
|
+
for (const user of this.users.values()) {
|
|
670
|
+
if (user.email === email) {
|
|
671
|
+
return user;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Get service info
|
|
679
|
+
*/
|
|
680
|
+
getInfo() {
|
|
681
|
+
return {
|
|
682
|
+
name: 'StudentHelp',
|
|
683
|
+
version: '2.0.0',
|
|
684
|
+
description: 'Complete utility service with multiple helpful functions',
|
|
685
|
+
modules: [
|
|
686
|
+
'🔐 Authentication & Security (hash, encrypt, tokens)',
|
|
687
|
+
'📧 Email & Notifications (email, SMS, webhooks)',
|
|
688
|
+
'📄 Data Validation (email, password, CPF, phone, URL)',
|
|
689
|
+
'📝 Data Transformation (sanitize, format, transform)',
|
|
690
|
+
'📊 Data Export (CSV, JSON, reports)',
|
|
691
|
+
'📁 File Upload (validation, processing)',
|
|
692
|
+
'🧮 Math & Conversions (percentage, interest, temperature, distance)',
|
|
693
|
+
'🛠️ General Utilities (sleep, retry, random)'
|
|
694
|
+
]
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Página de listagem de Resource
|
|
3
|
+
* =====================================
|
|
4
|
+
* Esta página exibe todos os registros de Resource.
|
|
5
|
+
* Expansão:
|
|
6
|
+
* - Adicione filtros
|
|
7
|
+
* - Adicione paginação
|
|
8
|
+
* - Integre com auth se necessário
|
|
9
|
+
*/
|
|
10
|
+
import React from 'react';
|
|
11
|
+
export default function ResourcePage() {
|
|
12
|
+
return <div>Resource CRUD Page</div>;
|
|
13
|
+
}
|