fragment-ts 1.0.16 → 1.0.18
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/SETUP.md +570 -0
- package/changes/1.md +420 -0
- package/dist/cli/commands/init.command.js +4 -4
- package/dist/cli/commands/init.command.js.map +1 -1
- package/dist/cli/commands/test.command.d.ts +6 -0
- package/dist/cli/commands/test.command.d.ts.map +1 -0
- package/dist/cli/commands/test.command.js +311 -0
- package/dist/cli/commands/test.command.js.map +1 -0
- package/dist/cli/index.js +6 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/container/di-container.d.ts +5 -0
- package/dist/core/container/di-container.d.ts.map +1 -1
- package/dist/core/container/di-container.js +121 -21
- package/dist/core/container/di-container.js.map +1 -1
- package/dist/core/decorators/controller.decorator.js +5 -5
- package/dist/core/decorators/injection.decorators.d.ts +44 -1
- package/dist/core/decorators/injection.decorators.d.ts.map +1 -1
- package/dist/core/decorators/injection.decorators.js +92 -1
- package/dist/core/decorators/injection.decorators.js.map +1 -1
- package/dist/core/metadata/metadata-keys.d.ts +29 -17
- package/dist/core/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/core/metadata/metadata-keys.js +35 -17
- package/dist/core/metadata/metadata-keys.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/init.command.ts +4 -4
- package/src/cli/commands/test.command.ts +289 -0
- package/src/cli/index.ts +16 -11
- package/src/core/container/di-container.ts +166 -31
- package/src/core/decorators/DECORATOR_USAGE.md +326 -0
- package/src/core/decorators/controller.decorator.ts +9 -9
- package/src/core/decorators/injection.decorators.ts +129 -5
- package/src/core/metadata/metadata-keys.ts +44 -18
- package/src/index.ts +1 -0
- package/src/testing/TEST.md +321 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { METADATA_KEYS } from
|
|
2
|
-
import { Scope } from
|
|
1
|
+
import { METADATA_KEYS } from "../metadata/metadata-keys";
|
|
2
|
+
import { Scope } from "../decorators/injectable.decorator";
|
|
3
3
|
|
|
4
4
|
export class DIContainer {
|
|
5
5
|
private static instance: DIContainer;
|
|
6
6
|
private singletons: Map<any, any> = new Map();
|
|
7
7
|
private transients: Map<any, any> = new Map();
|
|
8
8
|
private factories: Map<any, () => any> = new Map();
|
|
9
|
+
private constructing: Set<any> = new Set(); // Prevent circular dependencies
|
|
9
10
|
|
|
10
11
|
static getInstance(): DIContainer {
|
|
11
12
|
if (!DIContainer.instance) {
|
|
@@ -23,78 +24,212 @@ export class DIContainer {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
resolve<T>(token: any): T {
|
|
27
|
+
// Check for circular dependency
|
|
28
|
+
if (this.constructing.has(token)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Circular dependency detected for ${token.name || token}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return existing singleton
|
|
26
35
|
if (this.singletons.has(token)) {
|
|
27
36
|
return this.singletons.get(token);
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
// Use factory if available
|
|
30
40
|
if (this.factories.has(token)) {
|
|
31
41
|
const factory = this.factories.get(token)!;
|
|
32
42
|
const instance = factory();
|
|
33
|
-
const scope =
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
const scope =
|
|
44
|
+
Reflect.getMetadata(METADATA_KEYS.SCOPE, token) || "singleton";
|
|
45
|
+
|
|
46
|
+
if (scope === "singleton") {
|
|
36
47
|
this.singletons.set(token, instance);
|
|
37
48
|
}
|
|
38
|
-
|
|
49
|
+
|
|
39
50
|
return instance;
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
53
|
+
// Create new instance
|
|
54
|
+
if (typeof token === "function") {
|
|
55
|
+
this.constructing.add(token);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const instance = this.createInstance(token);
|
|
59
|
+
const scope =
|
|
60
|
+
Reflect.getMetadata(METADATA_KEYS.SCOPE, token) || "singleton";
|
|
61
|
+
|
|
62
|
+
if (scope === "singleton") {
|
|
63
|
+
this.singletons.set(token, instance);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.constructing.delete(token);
|
|
67
|
+
return instance;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this.constructing.delete(token);
|
|
70
|
+
throw error;
|
|
48
71
|
}
|
|
49
|
-
|
|
50
|
-
return instance;
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
throw new Error(`Cannot resolve dependency: ${token}`);
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
private createInstance(target: any): any {
|
|
57
|
-
|
|
58
|
-
const
|
|
78
|
+
// Get constructor parameter types
|
|
79
|
+
const paramTypes = Reflect.getMetadata("design:paramtypes", target) || [];
|
|
80
|
+
|
|
81
|
+
// Resolve constructor dependencies
|
|
82
|
+
const params = paramTypes.map((type: any) => {
|
|
83
|
+
if (!type || type === Object) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return this.resolve(type);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Create instance
|
|
59
90
|
const instance = new target(...params);
|
|
60
|
-
|
|
91
|
+
|
|
92
|
+
// Inject properties AFTER construction
|
|
61
93
|
this.injectProperties(instance);
|
|
62
|
-
|
|
94
|
+
|
|
95
|
+
// Call post-construct hook if it exists
|
|
96
|
+
if (typeof instance.onInit === "function") {
|
|
97
|
+
instance.onInit();
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
return instance;
|
|
64
101
|
}
|
|
65
102
|
|
|
66
103
|
private injectProperties(instance: any): void {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
105
|
+
|
|
106
|
+
// Get all property keys from the prototype chain
|
|
107
|
+
const properties = this.getAllPropertyKeys(instance);
|
|
108
|
+
|
|
109
|
+
properties.forEach((prop) => {
|
|
110
|
+
// Check for @Autowired
|
|
111
|
+
const autowired = Reflect.getMetadata(
|
|
112
|
+
METADATA_KEYS.AUTOWIRED,
|
|
113
|
+
prototype,
|
|
114
|
+
prop,
|
|
115
|
+
);
|
|
74
116
|
if (autowired) {
|
|
75
117
|
instance[prop] = this.resolve(autowired);
|
|
76
|
-
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for @Inject
|
|
122
|
+
const inject = Reflect.getMetadata(METADATA_KEYS.INJECT, prototype, prop);
|
|
123
|
+
if (inject) {
|
|
77
124
|
instance[prop] = this.resolve(inject);
|
|
78
|
-
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for @Value
|
|
129
|
+
const value = Reflect.getMetadata(METADATA_KEYS.VALUE, prototype, prop);
|
|
130
|
+
if (value !== undefined) {
|
|
79
131
|
instance[prop] = this.resolveValue(value);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check for @InjectRepository - special TypeORM handling
|
|
136
|
+
const repositoryEntity = Reflect.getMetadata(
|
|
137
|
+
METADATA_KEYS.INJECT_REPOSITORY,
|
|
138
|
+
prototype,
|
|
139
|
+
prop,
|
|
140
|
+
);
|
|
141
|
+
if (repositoryEntity) {
|
|
142
|
+
instance[prop] = this.resolveRepository(repositoryEntity);
|
|
143
|
+
return;
|
|
80
144
|
}
|
|
81
145
|
});
|
|
82
146
|
}
|
|
83
147
|
|
|
148
|
+
private getAllPropertyKeys(obj: any): string[] {
|
|
149
|
+
const keys = new Set<string>();
|
|
150
|
+
let current = obj;
|
|
151
|
+
|
|
152
|
+
while (current && current !== Object.prototype) {
|
|
153
|
+
Object.getOwnPropertyNames(current).forEach((key) => keys.add(key));
|
|
154
|
+
current = Object.getPrototypeOf(current);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return Array.from(keys);
|
|
158
|
+
}
|
|
159
|
+
|
|
84
160
|
private resolveValue(expression: string): any {
|
|
85
|
-
|
|
161
|
+
// Handle environment variable syntax: ${VAR_NAME} or $VAR_NAME
|
|
162
|
+
const match = expression.match(/\$\{([^}]+)\}|\$([A-Z_][A-Z0-9_]*)/i);
|
|
86
163
|
if (match) {
|
|
87
|
-
const key = match[1];
|
|
88
|
-
|
|
164
|
+
const key = match[1] || match[2];
|
|
165
|
+
const value = process.env[key];
|
|
166
|
+
|
|
167
|
+
if (value === undefined) {
|
|
168
|
+
console.warn(
|
|
169
|
+
`⚠️ Warning: Environment variable "${key}" is not defined`,
|
|
170
|
+
);
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Try to parse as JSON for complex types
|
|
175
|
+
if (value.startsWith("{") || value.startsWith("[")) {
|
|
176
|
+
try {
|
|
177
|
+
return JSON.parse(value);
|
|
178
|
+
} catch {
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Parse numbers
|
|
184
|
+
if (/^\d+$/.test(value)) {
|
|
185
|
+
return parseInt(value, 10);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Parse booleans
|
|
189
|
+
if (value.toLowerCase() === "true") return true;
|
|
190
|
+
if (value.toLowerCase() === "false") return false;
|
|
191
|
+
|
|
192
|
+
return value;
|
|
89
193
|
}
|
|
194
|
+
|
|
90
195
|
return expression;
|
|
91
196
|
}
|
|
92
197
|
|
|
198
|
+
private resolveRepository(entity: any): any {
|
|
199
|
+
try {
|
|
200
|
+
// Import TypeORM module dynamically to avoid circular dependencies
|
|
201
|
+
const { TypeORMModule } = require("../../typeorm/typeorm-module");
|
|
202
|
+
const dataSource = TypeORMModule.getDataSource();
|
|
203
|
+
return dataSource.getRepository(entity);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Failed to resolve repository for entity ${entity.name}: ${(error as Error)?.message}. ` +
|
|
207
|
+
`Make sure TypeORM is initialized before using @InjectRepository.`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
93
212
|
has(token: any): boolean {
|
|
94
|
-
return
|
|
213
|
+
return (
|
|
214
|
+
this.singletons.has(token) ||
|
|
215
|
+
this.factories.has(token) ||
|
|
216
|
+
typeof token === "function"
|
|
217
|
+
);
|
|
95
218
|
}
|
|
96
219
|
|
|
97
220
|
getAllInstances(): any[] {
|
|
98
221
|
return Array.from(this.singletons.values());
|
|
99
222
|
}
|
|
223
|
+
|
|
224
|
+
clear(): void {
|
|
225
|
+
this.singletons.clear();
|
|
226
|
+
this.transients.clear();
|
|
227
|
+
this.factories.clear();
|
|
228
|
+
this.constructing.clear();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// For testing purposes
|
|
232
|
+
reset(): void {
|
|
233
|
+
this.clear();
|
|
234
|
+
}
|
|
100
235
|
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// Example: Complete Fragment Application
|
|
3
|
+
// Demonstrates all decorators working together
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
|
7
|
+
import {
|
|
8
|
+
FragmentApplication,
|
|
9
|
+
Controller,
|
|
10
|
+
Service,
|
|
11
|
+
Repository,
|
|
12
|
+
Injectable,
|
|
13
|
+
Get,
|
|
14
|
+
Post,
|
|
15
|
+
Body,
|
|
16
|
+
Param,
|
|
17
|
+
Autowired,
|
|
18
|
+
InjectRepository,
|
|
19
|
+
Value,
|
|
20
|
+
PostConstruct,
|
|
21
|
+
} from 'fragment-ts';
|
|
22
|
+
import { Repository as TypeORMRepository } from 'typeorm';
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// 1. Entity Definition
|
|
26
|
+
// ============================================
|
|
27
|
+
@Entity()
|
|
28
|
+
export class User {
|
|
29
|
+
@PrimaryGeneratedColumn()
|
|
30
|
+
id!: number;
|
|
31
|
+
|
|
32
|
+
@Column()
|
|
33
|
+
name!: string;
|
|
34
|
+
|
|
35
|
+
@Column()
|
|
36
|
+
email!: string;
|
|
37
|
+
|
|
38
|
+
@Column({ default: true })
|
|
39
|
+
isActive!: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ============================================
|
|
43
|
+
// 2. Repository Layer
|
|
44
|
+
// ============================================
|
|
45
|
+
@Repository()
|
|
46
|
+
export class UserRepository {
|
|
47
|
+
// TypeORM repository is automatically injected
|
|
48
|
+
@InjectRepository(User)
|
|
49
|
+
private repo!: TypeORMRepository<User>;
|
|
50
|
+
|
|
51
|
+
async findAll(): Promise<User[]> {
|
|
52
|
+
return this.repo.find();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async findById(id: number): Promise<User | null> {
|
|
56
|
+
return this.repo.findOne({ where: { id } });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async create(data: Partial<User>): Promise<User> {
|
|
60
|
+
const user = this.repo.create(data);
|
|
61
|
+
return this.repo.save(user);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async update(id: number, data: Partial<User>): Promise<User | null> {
|
|
65
|
+
await this.repo.update(id, data);
|
|
66
|
+
return this.findById(id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async delete(id: number): Promise<boolean> {
|
|
70
|
+
const result = await this.repo.delete(id);
|
|
71
|
+
return (result.affected ?? 0) > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
75
|
+
return this.repo.findOne({ where: { email } });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================
|
|
80
|
+
// 3. Service Layer with Configuration
|
|
81
|
+
// ============================================
|
|
82
|
+
@Injectable()
|
|
83
|
+
export class EmailService {
|
|
84
|
+
@Value('${EMAIL_FROM}')
|
|
85
|
+
private fromEmail!: string;
|
|
86
|
+
|
|
87
|
+
@Value('${EMAIL_ENABLED:true}')
|
|
88
|
+
private enabled!: boolean;
|
|
89
|
+
|
|
90
|
+
@PostConstruct()
|
|
91
|
+
init() {
|
|
92
|
+
console.log(`📧 Email service initialized`);
|
|
93
|
+
console.log(` From: ${this.fromEmail}`);
|
|
94
|
+
console.log(` Enabled: ${this.enabled}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async sendWelcomeEmail(email: string, name: string): Promise<void> {
|
|
98
|
+
if (!this.enabled) {
|
|
99
|
+
console.log(`Email disabled, skipping welcome email to ${email}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(`Sending welcome email to ${email}`);
|
|
104
|
+
// Actual email sending logic would go here
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async sendPasswordReset(email: string, token: string): Promise<void> {
|
|
108
|
+
if (!this.enabled) {
|
|
109
|
+
console.log(`Email disabled, skipping reset email to ${email}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`Sending password reset to ${email} with token ${token}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================
|
|
118
|
+
// 4. Business Logic Service
|
|
119
|
+
// ============================================
|
|
120
|
+
@Service()
|
|
121
|
+
export class UserService {
|
|
122
|
+
// Dependencies are automatically injected using @Autowired
|
|
123
|
+
@Autowired()
|
|
124
|
+
private userRepository!: UserRepository;
|
|
125
|
+
|
|
126
|
+
@Autowired()
|
|
127
|
+
private emailService!: EmailService;
|
|
128
|
+
|
|
129
|
+
@Value('${MAX_USERS:1000}')
|
|
130
|
+
private maxUsers!: number;
|
|
131
|
+
|
|
132
|
+
@PostConstruct()
|
|
133
|
+
init() {
|
|
134
|
+
console.log(`👤 User service initialized (max users: ${this.maxUsers})`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getAllUsers(): Promise<User[]> {
|
|
138
|
+
return this.userRepository.findAll();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getUserById(id: number): Promise<User | null> {
|
|
142
|
+
return this.userRepository.findById(id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async createUser(data: Partial<User>): Promise<User> {
|
|
146
|
+
// Check max users limit
|
|
147
|
+
const users = await this.userRepository.findAll();
|
|
148
|
+
if (users.length >= this.maxUsers) {
|
|
149
|
+
throw new Error(`Maximum user limit (${this.maxUsers}) reached`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check if email already exists
|
|
153
|
+
if (data.email) {
|
|
154
|
+
const existing = await this.userRepository.findByEmail(data.email);
|
|
155
|
+
if (existing) {
|
|
156
|
+
throw new Error(`User with email ${data.email} already exists`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Create user
|
|
161
|
+
const user = await this.userRepository.create(data);
|
|
162
|
+
|
|
163
|
+
// Send welcome email
|
|
164
|
+
if (user.email && user.name) {
|
|
165
|
+
await this.emailService.sendWelcomeEmail(user.email, user.name);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return user;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async updateUser(id: number, data: Partial<User>): Promise<User | null> {
|
|
172
|
+
const user = await this.userRepository.findById(id);
|
|
173
|
+
if (!user) {
|
|
174
|
+
throw new Error(`User with id ${id} not found`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this.userRepository.update(id, data);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async deleteUser(id: number): Promise<boolean> {
|
|
181
|
+
const user = await this.userRepository.findById(id);
|
|
182
|
+
if (!user) {
|
|
183
|
+
throw new Error(`User with id ${id} not found`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this.userRepository.delete(id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async requestPasswordReset(email: string): Promise<void> {
|
|
190
|
+
const user = await this.userRepository.findByEmail(email);
|
|
191
|
+
if (!user) {
|
|
192
|
+
throw new Error(`User with email ${email} not found`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Generate reset token (simplified)
|
|
196
|
+
const token = Math.random().toString(36).substring(7);
|
|
197
|
+
await this.emailService.sendPasswordReset(email, token);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================
|
|
202
|
+
// 5. Controller Layer
|
|
203
|
+
// ============================================
|
|
204
|
+
@Controller('/api/users')
|
|
205
|
+
export class UserController {
|
|
206
|
+
// Service is automatically injected
|
|
207
|
+
@Autowired()
|
|
208
|
+
private userService!: UserService;
|
|
209
|
+
|
|
210
|
+
@Get('/')
|
|
211
|
+
async getAllUsers() {
|
|
212
|
+
const users = await this.userService.getAllUsers();
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
data: users,
|
|
216
|
+
count: users.length,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@Get('/:id')
|
|
221
|
+
async getUserById(@Param('id') id: string) {
|
|
222
|
+
const userId = parseInt(id, 10);
|
|
223
|
+
const user = await this.userService.getUserById(userId);
|
|
224
|
+
|
|
225
|
+
if (!user) {
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
error: 'User not found',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
data: user,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@Post('/')
|
|
239
|
+
async createUser(@Body() data: any) {
|
|
240
|
+
try {
|
|
241
|
+
const user = await this.userService.createUser(data);
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
data: user,
|
|
245
|
+
message: 'User created successfully',
|
|
246
|
+
};
|
|
247
|
+
} catch (error: any) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
error: error.message,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@Post('/:id')
|
|
256
|
+
async updateUser(@Param('id') id: string, @Body() data: any) {
|
|
257
|
+
try {
|
|
258
|
+
const userId = parseInt(id, 10);
|
|
259
|
+
const user = await this.userService.updateUser(userId, data);
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
data: user,
|
|
263
|
+
message: 'User updated successfully',
|
|
264
|
+
};
|
|
265
|
+
} catch (error: any) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: error.message,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@Post('/:id/delete')
|
|
274
|
+
async deleteUser(@Param('id') id: string) {
|
|
275
|
+
try {
|
|
276
|
+
const userId = parseInt(id, 10);
|
|
277
|
+
const deleted = await this.userService.deleteUser(userId);
|
|
278
|
+
return {
|
|
279
|
+
success: deleted,
|
|
280
|
+
message: deleted ? 'User deleted successfully' : 'Failed to delete user',
|
|
281
|
+
};
|
|
282
|
+
} catch (error: any) {
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: error.message,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@Post('/password-reset')
|
|
291
|
+
async requestPasswordReset(@Body() data: { email: string }) {
|
|
292
|
+
try {
|
|
293
|
+
await this.userService.requestPasswordReset(data.email);
|
|
294
|
+
return {
|
|
295
|
+
success: true,
|
|
296
|
+
message: 'Password reset email sent',
|
|
297
|
+
};
|
|
298
|
+
} catch (error: any) {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: error.message,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ============================================
|
|
308
|
+
// 6. Application Entry Point
|
|
309
|
+
// ============================================
|
|
310
|
+
@FragmentApplication({
|
|
311
|
+
port: 3000,
|
|
312
|
+
host: '0.0.0.0',
|
|
313
|
+
})
|
|
314
|
+
export class App {}
|
|
315
|
+
|
|
316
|
+
// ============================================
|
|
317
|
+
// 7. Bootstrap
|
|
318
|
+
// ============================================
|
|
319
|
+
import { FragmentWebApplication } from 'fragment-ts';
|
|
320
|
+
|
|
321
|
+
async function bootstrap() {
|
|
322
|
+
const app = new FragmentWebApplication();
|
|
323
|
+
await app.bootstrap(App);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
bootstrap().catch(console.error);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { Injectable } from
|
|
2
|
-
import { METADATA_KEYS } from
|
|
3
|
-
import { MetadataStorage } from
|
|
1
|
+
import { Injectable } from "./injectable.decorator";
|
|
2
|
+
import { METADATA_KEYS } from "../metadata/metadata-keys";
|
|
3
|
+
import { MetadataStorage } from "../metadata/metadata-storage";
|
|
4
4
|
|
|
5
|
-
export function Controller(path: string =
|
|
5
|
+
export function Controller(path: string = ""): ClassDecorator {
|
|
6
6
|
return (target: any) => {
|
|
7
|
-
Injectable(
|
|
7
|
+
Injectable("singleton")(target);
|
|
8
8
|
Reflect.defineMetadata(METADATA_KEYS.CONTROLLER, path, target);
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
const storage = MetadataStorage.getInstance();
|
|
11
11
|
storage.addClass({
|
|
12
12
|
target,
|
|
13
|
-
type:
|
|
14
|
-
scope:
|
|
15
|
-
path
|
|
13
|
+
type: "controller",
|
|
14
|
+
scope: "singleton",
|
|
15
|
+
path,
|
|
16
16
|
});
|
|
17
17
|
};
|
|
18
18
|
}
|