create-lyrajs 1.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 +38 -0
- package/README.md +351 -0
- package/cli.js +4 -0
- package/package.json +22 -0
- package/src/createApp.js +107 -0
- package/template/.env.example +30 -0
- package/template/.gitignore +51 -0
- package/template/.prettierignore +2 -0
- package/template/.prettierrc +8 -0
- package/template/README.md +39 -0
- package/template/config/database.yaml +6 -0
- package/template/config/mailer.yaml +6 -0
- package/template/config/parameters.yaml +7 -0
- package/template/config/router.yaml +2 -0
- package/template/config/security.yaml +22 -0
- package/template/eslint.config.js +53 -0
- package/template/migrations/migration_1752052338457.sql +3 -0
- package/template/package.json +43 -0
- package/template/src/controller/AuthController.ts +124 -0
- package/template/src/controller/UserController.ts +91 -0
- package/template/src/entity/User.ts +29 -0
- package/template/src/fixtures/AppFixtures.ts +49 -0
- package/template/src/middleware/YourMiddleware.ts +6 -0
- package/template/src/repository/UserRepository.ts +10 -0
- package/template/src/router/index.ts +14 -0
- package/template/src/router/routes/authRoutes.ts +10 -0
- package/template/src/router/routes/index.ts +9 -0
- package/template/src/router/routes/userRoutes.ts +10 -0
- package/template/src/server.ts +44 -0
- package/template/src/services/YourService.ts +5 -0
- package/template/src/tests/exemple.test.ts +11 -0
- package/template/src/types/ExempleType.ts +4 -0
- package/template/tsconfig.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2025 Matthieu Fergola
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
CONTRIBUTOR LICENSE AGREEMENT
|
|
22
|
+
|
|
23
|
+
By submitting a contribution to this project, you agree that:
|
|
24
|
+
|
|
25
|
+
1. You grant Matthieu Fergola (the project owner) a perpetual, worldwide,
|
|
26
|
+
non-exclusive, royalty-free, irrevocable license to use, reproduce,
|
|
27
|
+
modify, display, perform, sublicense, and distribute your contribution
|
|
28
|
+
under any license terms (including proprietary licenses).
|
|
29
|
+
|
|
30
|
+
2. You grant Matthieu Fergola the right to relicense the entire codebase
|
|
31
|
+
(including your contributions) under different terms in the future.
|
|
32
|
+
|
|
33
|
+
3. You certify that your contribution is your original work or you have
|
|
34
|
+
the right to submit it under these terms.
|
|
35
|
+
|
|
36
|
+
4. You understand that your contributions will be publicly available
|
|
37
|
+
under GPL-3.0 to end users, but Matthieu Fergola retains the right
|
|
38
|
+
to use them in proprietary versions.
|
package/README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# create-lyrajs
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/create-lyrajs)
|
|
4
|
+
[](https://cla-assistant.io/devway-eu/lyrajs-template)
|
|
5
|
+
[](https://lyrajs.dev)
|
|
6
|
+
[](https://discord.gg/cWvUh8pQNU)
|
|
7
|
+
|
|
8
|
+
The official project template and scaffolding tool for LyraJS, a lightweight TypeScript framework for building robust APIs.
|
|
9
|
+
|
|
10
|
+
## About
|
|
11
|
+
|
|
12
|
+
`create-lyrajs` is the quickest way to start a new LyraJS project. It's a CLI tool that scaffolds a fully-configured LyraJS application with best practices, pre-built authentication, and example code to get you started immediately.
|
|
13
|
+
|
|
14
|
+
## Role in the Framework
|
|
15
|
+
|
|
16
|
+
This repository serves as the **project starter** for LyraJS. It provides:
|
|
17
|
+
|
|
18
|
+
- **CLI Tool** - Interactive project creation via `npm create lyrajs`
|
|
19
|
+
- **Project Template** - Pre-configured application structure with working examples
|
|
20
|
+
- **Best Practices** - Demonstrates recommended patterns and conventions
|
|
21
|
+
- **Starting Point** - Complete foundation for building production APIs
|
|
22
|
+
|
|
23
|
+
When developers run `npm create lyrajs`, this tool copies the template folder to create a new project with everything needed to start building an API.
|
|
24
|
+
|
|
25
|
+
## What's Included in the Template
|
|
26
|
+
|
|
27
|
+
### Pre-built Features
|
|
28
|
+
|
|
29
|
+
1. **Authentication System**
|
|
30
|
+
- User registration and login
|
|
31
|
+
- JWT token generation (access + refresh tokens)
|
|
32
|
+
- Password hashing with bcrypt
|
|
33
|
+
- Protected routes via configuration
|
|
34
|
+
|
|
35
|
+
2. **User Management**
|
|
36
|
+
- User entity with role support
|
|
37
|
+
- UserRepository with custom queries
|
|
38
|
+
- UserController with CRUD operations
|
|
39
|
+
- Complete user routes
|
|
40
|
+
|
|
41
|
+
3. **Database Setup**
|
|
42
|
+
- MySQL/MariaDB configuration
|
|
43
|
+
- Migration system ready to use
|
|
44
|
+
- Example entity (User)
|
|
45
|
+
- Fixture system for seed data
|
|
46
|
+
|
|
47
|
+
4. **Configuration Files**
|
|
48
|
+
- `database.yaml` - Database connection settings
|
|
49
|
+
- `security.yaml` - JWT and access control rules
|
|
50
|
+
- `router.yaml` - API base path configuration
|
|
51
|
+
- `parameters.yaml` - Application metadata
|
|
52
|
+
- `mailer.yaml` - Email service settings
|
|
53
|
+
- `.env` - Environment variables
|
|
54
|
+
|
|
55
|
+
5. **Project Structure**
|
|
56
|
+
- TypeScript configuration
|
|
57
|
+
- ESLint and Prettier setup
|
|
58
|
+
- Organized folder structure (controller, entity, repository, router, etc.)
|
|
59
|
+
- Example code demonstrating patterns
|
|
60
|
+
|
|
61
|
+
6. **Development Tools**
|
|
62
|
+
- Hot reload with `npm run dev`
|
|
63
|
+
- Build scripts
|
|
64
|
+
- Type definitions
|
|
65
|
+
- Path aliases configured
|
|
66
|
+
|
|
67
|
+
### Project Structure
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
my-project/
|
|
71
|
+
├── src/
|
|
72
|
+
│ ├── controller/ # HTTP request handlers
|
|
73
|
+
│ │ ├── AuthController.ts # Registration, login, logout
|
|
74
|
+
│ │ └── UserController.ts # User CRUD operations
|
|
75
|
+
│ ├── entity/ # Database models
|
|
76
|
+
│ │ └── User.ts # User entity with decorators
|
|
77
|
+
│ ├── repository/ # Data access layer
|
|
78
|
+
│ │ └── UserRepository.ts # User database operations
|
|
79
|
+
│ ├── router/ # Route definitions
|
|
80
|
+
│ │ ├── index.ts # Main router setup
|
|
81
|
+
│ │ └── routes/
|
|
82
|
+
│ │ ├── authRoutes.ts # Auth endpoints
|
|
83
|
+
│ │ └── userRoutes.ts # User endpoints
|
|
84
|
+
│ ├── middleware/ # Custom middleware
|
|
85
|
+
│ │ └── YourMiddleware.ts # Example middleware
|
|
86
|
+
│ ├── services/ # Business logic services
|
|
87
|
+
│ │ └── YourService.ts # Example service
|
|
88
|
+
│ ├── fixtures/ # Seed data
|
|
89
|
+
│ │ └── AppFixtures.ts # Sample user data
|
|
90
|
+
│ ├── tests/ # Test files
|
|
91
|
+
│ │ └── exemple.test.ts # Example test
|
|
92
|
+
│ ├── types/ # TypeScript types
|
|
93
|
+
│ │ └── ExempleType.ts # Example types
|
|
94
|
+
│ └── server.ts # Application entry point
|
|
95
|
+
├── config/ # YAML configuration files
|
|
96
|
+
│ ├── database.yaml
|
|
97
|
+
│ ├── router.yaml
|
|
98
|
+
│ ├── security.yaml
|
|
99
|
+
│ ├── parameters.yaml
|
|
100
|
+
│ └── mailer.yaml
|
|
101
|
+
├── migrations/ # SQL migration files
|
|
102
|
+
├── .env # Environment variables
|
|
103
|
+
├── .prettierrc # Code formatting rules
|
|
104
|
+
├── eslint.config.js # Linting configuration
|
|
105
|
+
├── package.json # Dependencies and scripts
|
|
106
|
+
├── tsconfig.json # TypeScript configuration
|
|
107
|
+
└── README.md # Project documentation
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Usage
|
|
111
|
+
|
|
112
|
+
### Create a New Project
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Using npm (recommended)
|
|
116
|
+
npm create lyrajs
|
|
117
|
+
|
|
118
|
+
# Using npx
|
|
119
|
+
npx create-lyrajs
|
|
120
|
+
|
|
121
|
+
# Using yarn
|
|
122
|
+
yarn create lyrajs
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The CLI will prompt you for a project name:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
Project name: my-api
|
|
129
|
+
✅ Project "my-api" created.
|
|
130
|
+
➡ cd my-api
|
|
131
|
+
➡ npm install
|
|
132
|
+
➡ npm run dev
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Next Steps After Creation
|
|
136
|
+
|
|
137
|
+
1. **Configure environment variables** in `.env`:
|
|
138
|
+
```env
|
|
139
|
+
DB_HOST=127.0.0.1
|
|
140
|
+
DB_PORT=3306
|
|
141
|
+
DB_USER=root
|
|
142
|
+
DB_PASSWORD=your_password
|
|
143
|
+
DB_NAME=my_database
|
|
144
|
+
JWT_SECRET=your_secret_key
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
2. **Create the database**:
|
|
148
|
+
```bash
|
|
149
|
+
npx maestro create:database
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
3. **Run migrations**:
|
|
153
|
+
```bash
|
|
154
|
+
npx maestro migration:migrate
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
4. **Load sample data** (optional):
|
|
158
|
+
```bash
|
|
159
|
+
npx maestro fixtures:load
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
5. **Start developing**:
|
|
163
|
+
```bash
|
|
164
|
+
npm run dev
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Your API will be available at `http://localhost:3333/api`
|
|
168
|
+
|
|
169
|
+
### Pre-built Endpoints
|
|
170
|
+
|
|
171
|
+
The template includes these working endpoints:
|
|
172
|
+
|
|
173
|
+
**Authentication:**
|
|
174
|
+
- `POST /api/auth/sign-up` - User registration
|
|
175
|
+
- `POST /api/auth/sign-in` - User login
|
|
176
|
+
- `GET /api/auth/user` - Get authenticated user (protected)
|
|
177
|
+
- `GET /api/auth/sign-out` - Logout (protected)
|
|
178
|
+
|
|
179
|
+
**User Management:**
|
|
180
|
+
- `GET /api/user/all` - List all users
|
|
181
|
+
- `GET /api/user/:id` - Get user by ID
|
|
182
|
+
- `PUT /api/user/` - Create new user
|
|
183
|
+
- `PATCH /api/user/:id` - Update user
|
|
184
|
+
- `DELETE /api/user/:id` - Delete user
|
|
185
|
+
|
|
186
|
+
## Contributing
|
|
187
|
+
|
|
188
|
+
We welcome contributions to improve the template! Here's how you can help:
|
|
189
|
+
|
|
190
|
+
### Getting Started
|
|
191
|
+
|
|
192
|
+
1. **Fork the repository**
|
|
193
|
+
```bash
|
|
194
|
+
git clone https://github.com/your-username/lyrajs-template.git
|
|
195
|
+
cd lyrajs-template
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. **Test the CLI locally**
|
|
199
|
+
```bash
|
|
200
|
+
npm link
|
|
201
|
+
|
|
202
|
+
# Create a test project
|
|
203
|
+
cd /path/to/test-location
|
|
204
|
+
create-lyrajs
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
3. **Make changes to the template**
|
|
208
|
+
- Edit files in the `template/` folder
|
|
209
|
+
- Update CLI logic in `src/createApp.js`
|
|
210
|
+
- Test thoroughly before submitting
|
|
211
|
+
|
|
212
|
+
### Contribution Guidelines
|
|
213
|
+
|
|
214
|
+
- **Read the guidelines** - See [CONTRIBUTING.md](https://github.com/devway-eu/lyrajs/blob/main/lyrajs-core/CONTRIBUTING.md)
|
|
215
|
+
- **Maintain consistency** - Follow existing patterns and conventions
|
|
216
|
+
- **Test thoroughly** - Ensure the generated project works correctly
|
|
217
|
+
- **Document changes** - Update the template README if needed
|
|
218
|
+
- **Keep it simple** - The template should be easy to understand for beginners
|
|
219
|
+
|
|
220
|
+
### Areas for Contribution
|
|
221
|
+
|
|
222
|
+
We particularly welcome contributions in these areas:
|
|
223
|
+
|
|
224
|
+
1. **Template Improvements**
|
|
225
|
+
- Additional example code
|
|
226
|
+
- Better default configuration
|
|
227
|
+
- Improved project structure
|
|
228
|
+
- More comprehensive README in generated projects
|
|
229
|
+
|
|
230
|
+
2. **Pre-built Features**
|
|
231
|
+
- Additional authentication methods
|
|
232
|
+
- Example middleware implementations
|
|
233
|
+
- Sample services and business logic
|
|
234
|
+
- Test examples
|
|
235
|
+
|
|
236
|
+
3. **Developer Experience**
|
|
237
|
+
- Better error messages
|
|
238
|
+
- Improved CLI prompts
|
|
239
|
+
- Setup wizard enhancements
|
|
240
|
+
- Documentation improvements
|
|
241
|
+
|
|
242
|
+
4. **Configuration**
|
|
243
|
+
- Environment-specific configs
|
|
244
|
+
- Docker setup
|
|
245
|
+
- CI/CD examples
|
|
246
|
+
- Deployment guides
|
|
247
|
+
|
|
248
|
+
5. **Code Quality**
|
|
249
|
+
- ESLint rule improvements
|
|
250
|
+
- Prettier configuration
|
|
251
|
+
- TypeScript strictness
|
|
252
|
+
- Code comments and documentation
|
|
253
|
+
|
|
254
|
+
### Pull Request Process
|
|
255
|
+
|
|
256
|
+
1. **Create a feature branch**
|
|
257
|
+
```bash
|
|
258
|
+
git checkout -b feature/improve-template
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
2. **Make your changes**
|
|
262
|
+
- Modify files in `template/` folder
|
|
263
|
+
- Update CLI if needed
|
|
264
|
+
- Test the generated project
|
|
265
|
+
|
|
266
|
+
3. **Test the changes**
|
|
267
|
+
```bash
|
|
268
|
+
npm link
|
|
269
|
+
cd /tmp
|
|
270
|
+
create-lyrajs
|
|
271
|
+
cd my-test-project
|
|
272
|
+
npm install
|
|
273
|
+
npm run dev
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
4. **Commit and push**
|
|
277
|
+
```bash
|
|
278
|
+
git add .
|
|
279
|
+
git commit -m "Improve template with [description]"
|
|
280
|
+
git push origin feature/improve-template
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
5. **Open a Pull Request**
|
|
284
|
+
- Describe what you changed and why
|
|
285
|
+
- Include screenshots if UI-related
|
|
286
|
+
- Ensure the template generates a working project
|
|
287
|
+
|
|
288
|
+
### Testing Checklist
|
|
289
|
+
|
|
290
|
+
Before submitting a PR, verify:
|
|
291
|
+
|
|
292
|
+
- ✅ Generated project installs without errors
|
|
293
|
+
- ✅ `npm run dev` starts successfully
|
|
294
|
+
- ✅ Authentication endpoints work
|
|
295
|
+
- ✅ Database migrations execute properly
|
|
296
|
+
- ✅ TypeScript compiles without errors
|
|
297
|
+
- ✅ ESLint passes
|
|
298
|
+
- ✅ All configuration files are valid
|
|
299
|
+
- ✅ README is clear and accurate
|
|
300
|
+
|
|
301
|
+
### Reporting Issues
|
|
302
|
+
|
|
303
|
+
Found a problem with the template?
|
|
304
|
+
|
|
305
|
+
- **Check existing issues** - Search for similar problems first
|
|
306
|
+
- **Create a new issue** - Use the [issue tracker](https://github.com/devway-eu/lyrajs-template/issues)
|
|
307
|
+
- **Provide details** - Include:
|
|
308
|
+
- Node.js and npm versions
|
|
309
|
+
- Operating system
|
|
310
|
+
- Steps to reproduce
|
|
311
|
+
- Expected vs actual behavior
|
|
312
|
+
- Error messages or logs
|
|
313
|
+
|
|
314
|
+
## Template Customization
|
|
315
|
+
|
|
316
|
+
When creating your own LyraJS project, you can customize the template by:
|
|
317
|
+
|
|
318
|
+
1. **Modifying entities** - Add your own entities and relationships
|
|
319
|
+
2. **Adjusting routes** - Change API endpoints and structure
|
|
320
|
+
3. **Customizing authentication** - Adapt to your security requirements
|
|
321
|
+
4. **Adding features** - Extend with your own controllers and services
|
|
322
|
+
5. **Updating configuration** - Adjust settings for your environment
|
|
323
|
+
|
|
324
|
+
## Links
|
|
325
|
+
|
|
326
|
+
- **GitHub Repository**: [github.com/devway-eu/lyrajs-template](https://github.com/devway-eu/lyrajs-template)
|
|
327
|
+
- **npm Package**: [npmjs.com/package/create-lyrajs](https://www.npmjs.com/package/create-lyrajs)
|
|
328
|
+
- **Main Repository**: [github.com/devway-eu/lyrajs](https://github.com/devway-eu/lyrajs)
|
|
329
|
+
- **Core Package**: [github.com/devway-eu/lyrajs-core](https://github.com/devway-eu/lyrajs-core)
|
|
330
|
+
- **Documentation**: [lyrajs.dev](https://lyrajs.dev)
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
LyraJS is licensed under the [GPL-3.0 License](./LICENSE).
|
|
335
|
+
|
|
336
|
+
## Authors
|
|
337
|
+
|
|
338
|
+
- **Matthieu Fergola** - Core Developer
|
|
339
|
+
- **Anthony Dewitte** - Core Developer
|
|
340
|
+
|
|
341
|
+
## Acknowledgments
|
|
342
|
+
|
|
343
|
+
Built with dedication by the Devway team and our amazing contributors.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
**Need help?** Check the [documentation](https://lyrajs.dev) or join our [Discussions](https://github.com/devway-eu/lyrajs/discussions).
|
|
348
|
+
|
|
349
|
+
## Contributors ❤️
|
|
350
|
+
|
|
351
|
+

|
package/cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-lyrajs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to create new LyraJS projects",
|
|
5
|
+
"keywords": ["create", "lyrajs", "framework", "NodeJS", "api", "typescript", "cli", "scaffold"],
|
|
6
|
+
"author": "Matthieu Fergola & Anthony Dewitte",
|
|
7
|
+
"license": "GPL-3.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/devway-eu/lyrajs-template"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-lyrajs": "cli.js"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"files": [
|
|
17
|
+
"cli.js",
|
|
18
|
+
"src",
|
|
19
|
+
"template",
|
|
20
|
+
"template/.gitignore"
|
|
21
|
+
]
|
|
22
|
+
}
|
package/src/createApp.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { fileURLToPath } from "url"
|
|
4
|
+
import { mkdir, cp } from "fs/promises"
|
|
5
|
+
import readline from "readline"
|
|
6
|
+
import { spawn } from "child_process"
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
export async function createApp() {
|
|
11
|
+
const projectName = await prompt("Project name: ")
|
|
12
|
+
|
|
13
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
14
|
+
const templateDir = path.resolve(__dirname, "../template")
|
|
15
|
+
|
|
16
|
+
if (fs.existsSync(targetDir)) {
|
|
17
|
+
console.error(`❌ Directory ${projectName} already exists.`)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`\nCreating project "${projectName}"...`)
|
|
22
|
+
|
|
23
|
+
await mkdir(targetDir)
|
|
24
|
+
await cp(templateDir, targetDir, { recursive: true })
|
|
25
|
+
|
|
26
|
+
const envExample = path.join(targetDir, '.env.example')
|
|
27
|
+
if (fs.existsSync(envExample)) {
|
|
28
|
+
await cp(envExample, path.join(targetDir, '.env'))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`\n✔ Project "${projectName}" created.`)
|
|
32
|
+
|
|
33
|
+
const shouldInstall = await confirm("\nInstall dependencies now? (Y/n) ")
|
|
34
|
+
|
|
35
|
+
if(shouldInstall)
|
|
36
|
+
{
|
|
37
|
+
console.log(`\n➟ Installing dependencies...`)
|
|
38
|
+
|
|
39
|
+
const installSuccess = await installDependencies(targetDir)
|
|
40
|
+
|
|
41
|
+
if(installSuccess)
|
|
42
|
+
{
|
|
43
|
+
console.log(`\n✔ Dependencies installed successfully!\n`)
|
|
44
|
+
console.log(`You're all set! Run these commands to start:\n`)
|
|
45
|
+
console.log(`➡ cd ${projectName}`)
|
|
46
|
+
console.log(`➡ npm run dev`)
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
{
|
|
50
|
+
console.log(`➡ cd ${projectName}`)
|
|
51
|
+
console.log(`➡ npm install`)
|
|
52
|
+
console.log(`➡ npm run dev`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
{
|
|
57
|
+
console.log(`➡ cd ${projectName}`)
|
|
58
|
+
console.log(`➡ npm install`)
|
|
59
|
+
console.log(`➡ npm run dev`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Simple readline prompt
|
|
64
|
+
function prompt(question) {
|
|
65
|
+
const rl = readline.createInterface({
|
|
66
|
+
input: process.stdin,
|
|
67
|
+
output: process.stdout
|
|
68
|
+
})
|
|
69
|
+
return new Promise((resolve) => rl.question(question, (ans) => {
|
|
70
|
+
rl.close()
|
|
71
|
+
resolve(ans.trim())
|
|
72
|
+
}))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function installDependencies(targetDir) {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"
|
|
78
|
+
const install = spawn(npmCommand, ["install"], {
|
|
79
|
+
cwd: targetDir,
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
shell: true
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
install.on("close", (code) => {
|
|
85
|
+
resolve(code === 0)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
install.on("error", () => {
|
|
89
|
+
resolve(false)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function confirm(question) {
|
|
95
|
+
const rl = readline.createInterface({
|
|
96
|
+
input: process.stdin,
|
|
97
|
+
output: process.stdout
|
|
98
|
+
})
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
rl.question(question, (ans) => {
|
|
101
|
+
rl.close()
|
|
102
|
+
const answer = ans.trim().toLowerCase()
|
|
103
|
+
// Default to yes if empty or starts with 'y'
|
|
104
|
+
resolve(answer === "" || answer === "y" || answer === "yes")
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Node environment
|
|
2
|
+
NODE_ENV=dev
|
|
3
|
+
# Time zone
|
|
4
|
+
TZ=Europe/Paris
|
|
5
|
+
# Limits
|
|
6
|
+
REQUEST_MAX_SIZE=10mb
|
|
7
|
+
# API host
|
|
8
|
+
HOST=localhost
|
|
9
|
+
# API port
|
|
10
|
+
PORT=3333
|
|
11
|
+
# Database
|
|
12
|
+
DB_HOST=127.0.0.1
|
|
13
|
+
DB_PORT=3306
|
|
14
|
+
DB_USER=root
|
|
15
|
+
DB_PASSWORD=
|
|
16
|
+
DB_NAME=your_database_name
|
|
17
|
+
# Session driver
|
|
18
|
+
SESSION_DRIVER=cookie
|
|
19
|
+
# Client app url
|
|
20
|
+
CLIENT_APP_URL=http://localhost:5173
|
|
21
|
+
# JWT
|
|
22
|
+
JWT_SECRET=your_very_long_random_secret_key_here
|
|
23
|
+
JWT_SECRET_REFRESH=your_different_very_long_refresh_secret_here
|
|
24
|
+
JWT_ALGORITHM=HS256
|
|
25
|
+
# Mailer
|
|
26
|
+
MAILER_HOST=mailer_dns
|
|
27
|
+
MAILER_PORT=465
|
|
28
|
+
MAILER_USER=mailer_user
|
|
29
|
+
MAILER_PASS=mailer_password
|
|
30
|
+
MAILER_SENDER=donotrespond@mail.com
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
package-lock.json
|
|
4
|
+
|
|
5
|
+
# Environment variables
|
|
6
|
+
.env
|
|
7
|
+
.env.local
|
|
8
|
+
.env.*.local
|
|
9
|
+
|
|
10
|
+
# Build output
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
*.tsbuildinfo
|
|
14
|
+
|
|
15
|
+
# Logs
|
|
16
|
+
logs/
|
|
17
|
+
*.log
|
|
18
|
+
npm-debug.log*
|
|
19
|
+
yarn-debug.log*
|
|
20
|
+
yarn-error.log*
|
|
21
|
+
lerna-debug.log*
|
|
22
|
+
|
|
23
|
+
# Runtime data
|
|
24
|
+
pids/
|
|
25
|
+
*.pid
|
|
26
|
+
*.seed
|
|
27
|
+
*.pid.lock
|
|
28
|
+
|
|
29
|
+
# Coverage directory
|
|
30
|
+
coverage/
|
|
31
|
+
.nyc_output/
|
|
32
|
+
|
|
33
|
+
# IDE and editors
|
|
34
|
+
.vscode/
|
|
35
|
+
.idea/
|
|
36
|
+
*.swp
|
|
37
|
+
*.swo
|
|
38
|
+
*~
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
41
|
+
|
|
42
|
+
# Optional npm cache directory
|
|
43
|
+
.npm
|
|
44
|
+
|
|
45
|
+
# Optional eslint cache
|
|
46
|
+
.eslintcache
|
|
47
|
+
|
|
48
|
+
# Temporary files
|
|
49
|
+
tmp/
|
|
50
|
+
temp/
|
|
51
|
+
*.tmp
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# LyraJS
|
|
2
|
+
|
|
3
|
+
A simple framework for building web API with Node.js.
|
|
4
|
+
|
|
5
|
+
We wanted to create a framework as similar as possible to the Symfony framework, with a CLI to help you create your API faster.
|
|
6
|
+
|
|
7
|
+
By default, the framework provides :
|
|
8
|
+
- Routing
|
|
9
|
+
- Middlewares
|
|
10
|
+
- User and role entities, repositories, controllers and routes
|
|
11
|
+
- Authentication with JWT
|
|
12
|
+
- CORS
|
|
13
|
+
- ORM for MySQL database managment
|
|
14
|
+
- Error handling
|
|
15
|
+
- CLI to help you create your API faster
|
|
16
|
+
|
|
17
|
+
Our CLI includes :
|
|
18
|
+
- A command to create a new entity and repository
|
|
19
|
+
- A command to create a new controller
|
|
20
|
+
- A command to create a new migration
|
|
21
|
+
- A command to apply last migration to make database tables corresponding to entities
|
|
22
|
+
- A command to load fixtures data in database
|
|
23
|
+
- A command "help" to display all available commands
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/devway-eu/LyraJS.git
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Run services
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run dev
|
|
39
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
security:
|
|
2
|
+
jwt:
|
|
3
|
+
secret_key: "%env(JWT_SECRET)%"
|
|
4
|
+
token_expiration: 3600
|
|
5
|
+
secret_key_refresh: "%env(JWT_SECRET_REFRESH)%"
|
|
6
|
+
refresh_token_expiration: 86400
|
|
7
|
+
algorithm: "%env(JWT_ALGORITHM)%"
|
|
8
|
+
auth_routes:
|
|
9
|
+
sign_out: "/api/v1/sign-out"
|
|
10
|
+
limits:
|
|
11
|
+
request_max_size: "%env(REQUEST_MAX_SIZE)%"
|
|
12
|
+
rate_limiter:
|
|
13
|
+
time: 900000 # 15 minutes (ms)
|
|
14
|
+
max_attempts: 5
|
|
15
|
+
message: 'Too many login attempts, please try again later'
|
|
16
|
+
access_control:
|
|
17
|
+
- { path: /api/v1/auth/sign-out, roles: [ROLE_USER] }
|
|
18
|
+
- { path: /api/v1/auth/user, roles: [ROLE_USER] }
|
|
19
|
+
- { path: /api/v1/admin, roles: [ROLE_ADMIN] }
|
|
20
|
+
role_hierarchy:
|
|
21
|
+
ROLE_ADMIN: ROLE_USER
|
|
22
|
+
ROLE_SUPER_ADMIN: ROLE_ADMIN
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import js from "@eslint/js"
|
|
2
|
+
import globals from "globals"
|
|
3
|
+
import tseslint from "typescript-eslint"
|
|
4
|
+
import eslintConfigPrettier from "eslint-config-prettier"
|
|
5
|
+
import eslintPluginPrettier from "eslint-plugin-prettier"
|
|
6
|
+
import eslintPluginSimpleImportSort from "eslint-plugin-simple-import-sort"
|
|
7
|
+
|
|
8
|
+
export default tseslint.config(
|
|
9
|
+
{ ignores: ["dist"] },
|
|
10
|
+
{
|
|
11
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended, eslintConfigPrettier],
|
|
12
|
+
files: ["**/*.{ts,tsx}"],
|
|
13
|
+
languageOptions: {
|
|
14
|
+
ecmaVersion: 2020,
|
|
15
|
+
globals: globals.node
|
|
16
|
+
},
|
|
17
|
+
plugins: {
|
|
18
|
+
prettier: eslintPluginPrettier,
|
|
19
|
+
"simple-import-sort": eslintPluginSimpleImportSort
|
|
20
|
+
},
|
|
21
|
+
rules: {
|
|
22
|
+
"no-useless-catch": "off",
|
|
23
|
+
"prettier/prettier": "warn",
|
|
24
|
+
"@typescript-eslint/no-unused-vars": [
|
|
25
|
+
"error",
|
|
26
|
+
{
|
|
27
|
+
argsIgnorePattern: "^_",
|
|
28
|
+
varsIgnorePattern: "^_",
|
|
29
|
+
caughtErrorsIgnorePattern: "^_"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"@typescript-eslint/no-throw-literal": "off",
|
|
33
|
+
"no-throw-literal": "off",
|
|
34
|
+
"@typescript-eslint/only-throw-error": "off",
|
|
35
|
+
"@typescript-eslint/prefer-promise-reject-errors": "off",
|
|
36
|
+
"simple-import-sort/imports": [
|
|
37
|
+
"error",
|
|
38
|
+
{
|
|
39
|
+
groups: [
|
|
40
|
+
["^\\u0000"],
|
|
41
|
+
["^@?\\w"],
|
|
42
|
+
[
|
|
43
|
+
"^(@app|@config|@core|@controllers|@fixtures|@middlewares|@entity|@repository|@router|@services|@tests|@types)(/.*|$)"
|
|
44
|
+
],
|
|
45
|
+
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
|
|
46
|
+
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
|
|
47
|
+
["^.+\\.s?css$"]
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
USE `nodejs_sql`;
|
|
2
|
+
DROP TABLE IF EXISTS `user`;
|
|
3
|
+
CREATE TABLE IF NOT EXISTS `user` (`id` BIGINT AUTO_INCREMENT NOT NULL, `username` VARCHAR (255) NOT NULL , `firstname` VARCHAR (255) NOT NULL , `lastname` VARCHAR (255) NOT NULL , `email` VARCHAR (255) NOT NULL UNIQUE, `password` VARCHAR (255) NOT NULL , `role` VARCHAR (255) NOT NULL , `refresh_token` VARCHAR (255) , `created_at` TIMESTAMP NOT NULL , `updated_at` TIMESTAMP NOT NULL , PRIMARY KEY (`id`));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "LyraJS API",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "tsx watch src/server.ts",
|
|
6
|
+
"start": "tsx src/server.ts",
|
|
7
|
+
"maestro": "maestro"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"bcrypt": "^6.0.0",
|
|
11
|
+
"cookie-parser": "^1.4.7",
|
|
12
|
+
"cors": "^2.8.5",
|
|
13
|
+
"dotenv": "^16.5.0",
|
|
14
|
+
"express": "^5.1.0",
|
|
15
|
+
"express-rate-limit": "^8.2.1",
|
|
16
|
+
"jsonwebtoken": "^9.0.2",
|
|
17
|
+
"mysql2": "^3.14.1",
|
|
18
|
+
"nodemailer": "^7.0.7",
|
|
19
|
+
"reflect-metadata": "^0.2.2",
|
|
20
|
+
"yaml": "^2.8.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@lyrajs/core": "^1.0.0",
|
|
24
|
+
"@types/bcrypt": "^5.0.2",
|
|
25
|
+
"@types/cookie-parser": "^1.4.8",
|
|
26
|
+
"@types/cors": "^2.8.18",
|
|
27
|
+
"@types/dotenv": "^8.2.3",
|
|
28
|
+
"@types/express": "^5.0.1",
|
|
29
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
30
|
+
"@types/node": "^22.15.17",
|
|
31
|
+
"@types/nodemailer": "^6.4.17",
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^8.23.0",
|
|
33
|
+
"@typescript-eslint/parser": "^8.23.0",
|
|
34
|
+
"eslint": "^9.20.0",
|
|
35
|
+
"eslint-config-prettier": "^10.0.1",
|
|
36
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
37
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
38
|
+
"inquirer": "^12.7.0",
|
|
39
|
+
"tsx": "^4.19.4",
|
|
40
|
+
"typescript": "^5.7.3",
|
|
41
|
+
"typescript-eslint": "^8.23.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import bcrypt from "bcrypt"
|
|
2
|
+
import { NextFunction, Request, Response } from "express"
|
|
3
|
+
import jwt from "jsonwebtoken"
|
|
4
|
+
|
|
5
|
+
import { SecurityConfig } from "@lyrajs/core"
|
|
6
|
+
import { AuthenticatedRequest, UnauthorizedException, Validator } from "@lyrajs/core"
|
|
7
|
+
import { User } from "@entity/User"
|
|
8
|
+
import { userRepository } from "@repository/UserRepository"
|
|
9
|
+
|
|
10
|
+
const securityConfig = new SecurityConfig().getConfig()
|
|
11
|
+
|
|
12
|
+
export class AuthController {
|
|
13
|
+
static signUp = async (req: Request, res: Response, next: NextFunction) => {
|
|
14
|
+
try {
|
|
15
|
+
const { username, firstname, lastname, email, password } = req.body
|
|
16
|
+
|
|
17
|
+
if (!username || !firstname || !lastname || !email || !password) {
|
|
18
|
+
throw new Error("Missing required fields")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!Validator.isUsernameValid(username)) {
|
|
22
|
+
throw new Error("Invalid username")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!Validator.isEmailValid(email)) {
|
|
26
|
+
throw new Error("Invalid email")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const isEmailUsed = await userRepository.findOneBy({ email })
|
|
30
|
+
|
|
31
|
+
if (isEmailUsed) {
|
|
32
|
+
throw new Error("Email already in use")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!Validator.isPasswordValid(password)) {
|
|
36
|
+
throw new Error("Invalid password")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const user = new User()
|
|
40
|
+
const hashedPassword = await bcrypt.hash(password, 10)
|
|
41
|
+
|
|
42
|
+
user.username = username
|
|
43
|
+
user.firstname = firstname
|
|
44
|
+
user.lastname = lastname
|
|
45
|
+
user.email = email
|
|
46
|
+
user.password = hashedPassword
|
|
47
|
+
user.role = 'ROLE_USER'
|
|
48
|
+
|
|
49
|
+
await userRepository.save(user)
|
|
50
|
+
|
|
51
|
+
const registeredUser = await userRepository.findOneBy({ email })
|
|
52
|
+
|
|
53
|
+
delete registeredUser?.password
|
|
54
|
+
|
|
55
|
+
res.status(201).json({ message: "User registered successfully", user: registeredUser })
|
|
56
|
+
} catch (error) {
|
|
57
|
+
next(error)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static signIn = async (req: Request, res: Response, next: NextFunction) => {
|
|
62
|
+
try {
|
|
63
|
+
const { email, password } = req.body
|
|
64
|
+
|
|
65
|
+
if (!email || !password) {
|
|
66
|
+
throw new Error("Missing required fields")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const user = await userRepository.findOneBy({ email })
|
|
70
|
+
|
|
71
|
+
if (!user || !(user && (await bcrypt.compare(password, user.password)))) {
|
|
72
|
+
throw new Error("Invalid credentials")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const token = jwt.sign({ id: user.id }, securityConfig.jwt.secret_key as string, {
|
|
76
|
+
algorithm: securityConfig.jwt.algorithm as string,
|
|
77
|
+
expiresIn: securityConfig.jwt.token_expiration
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
user.refresh_token = jwt.sign({ id: user.id }, securityConfig.jwt.secret_key_refresh as string, {
|
|
81
|
+
algorithm: securityConfig.jwt.algorithm as string,
|
|
82
|
+
expiresIn: securityConfig.jwt.refresh_token_expiration
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
await userRepository.save(user)
|
|
86
|
+
|
|
87
|
+
res.cookie("Token", token, {
|
|
88
|
+
sameSite: "Lax",
|
|
89
|
+
httpOnly: true,
|
|
90
|
+
secure: process.env.ENV === "production",
|
|
91
|
+
maxAge: 1000 * 60 * 60 * 24,
|
|
92
|
+
partitioned: false
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
delete user.password
|
|
96
|
+
|
|
97
|
+
res.status(200).json({ message: "User authenticated in successfully", user, token })
|
|
98
|
+
} catch (error) {
|
|
99
|
+
next(error)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static getAuthenticatedUser = async (req: AuthenticatedRequest<Request>, res: Response, next: NextFunction) => {
|
|
104
|
+
try {
|
|
105
|
+
const user = req.user as User
|
|
106
|
+
|
|
107
|
+
if (!user) throw new UnauthorizedException()
|
|
108
|
+
|
|
109
|
+
res.status(200).json({
|
|
110
|
+
firstname: user.firstname,
|
|
111
|
+
lastname: user.lastname,
|
|
112
|
+
email: user.email,
|
|
113
|
+
role: user.role
|
|
114
|
+
})
|
|
115
|
+
} catch (error) {
|
|
116
|
+
next(error)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static signOut = async (_req: Request, res: Response) => {
|
|
121
|
+
res.clearCookie("Token")
|
|
122
|
+
res.status(200).json({ message: "Unauthenticated successfully" })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express"
|
|
2
|
+
import { Validator, ValidationException } from "@lyrajs/core"
|
|
3
|
+
import { User } from "@entity/User"
|
|
4
|
+
import { userRepository } from "@repository/UserRepository"
|
|
5
|
+
|
|
6
|
+
export class UserController {
|
|
7
|
+
static list = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
8
|
+
try {
|
|
9
|
+
const users = await userRepository.findAll().map( (user: User) => {
|
|
10
|
+
delete user.password
|
|
11
|
+
return user
|
|
12
|
+
} )
|
|
13
|
+
res.status(200).json({ message: "Users fetched successfully", users })
|
|
14
|
+
} catch (error) {
|
|
15
|
+
next(error)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static read = async (req: Request, res: Response, next: NextFunction) => {
|
|
20
|
+
try {
|
|
21
|
+
const { id } = req.params
|
|
22
|
+
const user = await userRepository.find(id)
|
|
23
|
+
delete user?.password
|
|
24
|
+
res.status(200).json({ message: "User fetched successfully", user })
|
|
25
|
+
} catch (error) {
|
|
26
|
+
next(error)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static create = async (req: Request, res: Response, next: NextFunction) => {
|
|
31
|
+
try {
|
|
32
|
+
const { data }: {data: User} = req.body
|
|
33
|
+
|
|
34
|
+
if(
|
|
35
|
+
!data.email || !data.password ||
|
|
36
|
+
!data.firstname || !data.lastname ||
|
|
37
|
+
!data.username
|
|
38
|
+
)
|
|
39
|
+
{
|
|
40
|
+
new ValidationException("All fields are required.")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if( !Validator.isUsernameValid(data.username) )
|
|
44
|
+
{
|
|
45
|
+
new ValidationException("Username to short (min 2 characters, only alphabetical characters and underscores )")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if( !Validator.isEmailValid(data.email) )
|
|
49
|
+
{
|
|
50
|
+
new ValidationException("Invalid email format.")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if( !Validator.isPasswordValid(data.password) )
|
|
54
|
+
{
|
|
55
|
+
new ValidationException("Password is to weak. I must be 10 characters long, including at least 1 lowercase, 1 uppercase, 1 number and 1 special character.")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
data.role = "ROLE_USER";
|
|
59
|
+
|
|
60
|
+
await userRepository.save(data)
|
|
61
|
+
res.status(201).json({ message: "User created successfully" })
|
|
62
|
+
} catch (error) {
|
|
63
|
+
next(error)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static update = async (req: Request, res: Response, next: NextFunction) => {
|
|
68
|
+
try {
|
|
69
|
+
const { data }: {data: User} = req.body
|
|
70
|
+
const user = await userRepository.find(data.id)
|
|
71
|
+
if (!user) res.status(404).json({ message: "User not found" })
|
|
72
|
+
if (user) await userRepository.save(data)
|
|
73
|
+
res.status(200).json({ message: "Users updated successfully" })
|
|
74
|
+
} catch (error) {
|
|
75
|
+
next(error)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static delete = async (req: Request, res: Response, next: NextFunction) => {
|
|
80
|
+
try {
|
|
81
|
+
const { id } = req.params
|
|
82
|
+
const user = await userRepository.find(id)
|
|
83
|
+
if (!user) res.status(404).json({ message: "User not found" })
|
|
84
|
+
if (!user?.id) res.status(400).json({ message: "Invalid user id" })
|
|
85
|
+
if (user?.id && id) await userRepository.delete(id)
|
|
86
|
+
res.status(200).json({ message: "User deleted successfully" })
|
|
87
|
+
} catch (error) {
|
|
88
|
+
next(error)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Column, Entity, Table } from "@lyrajs/core"
|
|
2
|
+
|
|
3
|
+
@Table()
|
|
4
|
+
export class User extends Entity<User> {
|
|
5
|
+
@Column({ type: "bigint", pk: true })
|
|
6
|
+
id: number
|
|
7
|
+
@Column({ type: "varchar", size: 255 })
|
|
8
|
+
username: string = ""
|
|
9
|
+
@Column({ type: "varchar", size: 255 })
|
|
10
|
+
firstname: string = ""
|
|
11
|
+
@Column({ type: "varchar", size: 255 })
|
|
12
|
+
lastname: string = ""
|
|
13
|
+
@Column({ type: "varchar", size: 255, unique: true })
|
|
14
|
+
email: string = ""
|
|
15
|
+
@Column({ type: "varchar", size: 255 })
|
|
16
|
+
password: string = ""
|
|
17
|
+
@Column({ type: "varchar", size: 255 })
|
|
18
|
+
role: string = "ROLE_USER"
|
|
19
|
+
@Column({ type: "varchar", size: 255, nullable: true })
|
|
20
|
+
refresh_token: string | null = null
|
|
21
|
+
@Column({ type: "timestamp" })
|
|
22
|
+
created_at: string | Date | null = new Date()
|
|
23
|
+
@Column({ type: "timestamp" })
|
|
24
|
+
updated_at: string | Date | null = new Date()
|
|
25
|
+
|
|
26
|
+
constructor(user?: Partial<User> | User) {
|
|
27
|
+
super(user)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import bcrypt from "bcrypt"
|
|
2
|
+
|
|
3
|
+
import { User } from "@entity/User"
|
|
4
|
+
import { userRepository } from "@repository/UserRepository"
|
|
5
|
+
|
|
6
|
+
export class AppFixtures {
|
|
7
|
+
private users = [
|
|
8
|
+
{
|
|
9
|
+
username: "Mithrandil",
|
|
10
|
+
firstname: "Gandalf",
|
|
11
|
+
lastname: "Thewhite",
|
|
12
|
+
email: "gandalf@mail.com",
|
|
13
|
+
password: "Y0u$ha11n0tpa$$!"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
username: "Pippin",
|
|
17
|
+
firstname: "Peregrin",
|
|
18
|
+
lastname: "Took",
|
|
19
|
+
email: "pippin@mail.com",
|
|
20
|
+
password: "Mu$hr0000m$!!"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
username: "Merry",
|
|
24
|
+
firstname: "Meriadoc",
|
|
25
|
+
lastname: "Brandybuck",
|
|
26
|
+
email: "merry@mail.com",
|
|
27
|
+
password: "Mu$hr0000m$!!"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
load = async () => {
|
|
32
|
+
await this.loadUsers()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private loadUsers = async () => {
|
|
36
|
+
for (const u of this.users) {
|
|
37
|
+
const hashedPassword = await bcrypt.hash(u.password, 10)
|
|
38
|
+
const user = new User()
|
|
39
|
+
user.username = u.username
|
|
40
|
+
user.firstname = u.firstname
|
|
41
|
+
user.lastname = u.lastname
|
|
42
|
+
user.email = u.email
|
|
43
|
+
user.password = hashedPassword
|
|
44
|
+
user.created_at = new Date()
|
|
45
|
+
user.updated_at = new Date()
|
|
46
|
+
await userRepository.save(user)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Response, Router } from "express"
|
|
2
|
+
|
|
3
|
+
import { Config } from "@lyrajs/core"
|
|
4
|
+
import { routes } from "@router/routes"
|
|
5
|
+
|
|
6
|
+
const routerBasePath = new Config().get("router.base_path")
|
|
7
|
+
|
|
8
|
+
export const router = Router()
|
|
9
|
+
|
|
10
|
+
router.use(routerBasePath, routes)
|
|
11
|
+
|
|
12
|
+
router.get("/", (req, res: Response) => {
|
|
13
|
+
res.send("Nothing here...")
|
|
14
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AuthController } from "@controller/AuthController"
|
|
2
|
+
import { Router } from "express"
|
|
3
|
+
import { rateLimiter } from "@lyrajs/core"
|
|
4
|
+
|
|
5
|
+
export const authRoutes = Router()
|
|
6
|
+
|
|
7
|
+
authRoutes.post("/sign-up", AuthController.signUp)
|
|
8
|
+
authRoutes.post("/sign-in", rateLimiter, AuthController.signIn)
|
|
9
|
+
authRoutes.get("/user", AuthController.getAuthenticatedUser)
|
|
10
|
+
authRoutes.get("/sign-out", AuthController.signOut)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { UserController } from "@controller/UserController"
|
|
2
|
+
import { Router } from "express"
|
|
3
|
+
|
|
4
|
+
export const userRoutes = Router()
|
|
5
|
+
|
|
6
|
+
userRoutes.get("/all", UserController.list)
|
|
7
|
+
userRoutes.get("/:id", UserController.read)
|
|
8
|
+
userRoutes.put("/", UserController.create)
|
|
9
|
+
userRoutes.patch("/:id", UserController.update)
|
|
10
|
+
userRoutes.delete("/:id", UserController.delete)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import cookieParser from "cookie-parser"
|
|
2
|
+
import cors from "cors"
|
|
3
|
+
import * as dotenv from "dotenv"
|
|
4
|
+
import express from "express"
|
|
5
|
+
import * as process from "node:process"
|
|
6
|
+
|
|
7
|
+
import { router } from "@app/router"
|
|
8
|
+
import { Config, LyraConsole, accessMiddleware, errorHandler, httpRequestMiddleware } from "@lyrajs/core"
|
|
9
|
+
|
|
10
|
+
dotenv.config()
|
|
11
|
+
|
|
12
|
+
const params = new Config().get("parameters")
|
|
13
|
+
const securityConfig = new Config().get("security")
|
|
14
|
+
|
|
15
|
+
const port = process.env.PORT
|
|
16
|
+
const app = express()
|
|
17
|
+
|
|
18
|
+
app.set("trust proxy", true)
|
|
19
|
+
app.use(cookieParser())
|
|
20
|
+
app.use(express.json({ limit: securityConfig.limits.request_max_size || "10mb" }))
|
|
21
|
+
app.use(express.urlencoded({ limit: securityConfig.limits.request_max_size || "10mb", extended: true }))
|
|
22
|
+
app.use(
|
|
23
|
+
cors({
|
|
24
|
+
origin: `${process.env.CLIENT_APP_URL}`,
|
|
25
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
|
|
26
|
+
credentials: true
|
|
27
|
+
})
|
|
28
|
+
)
|
|
29
|
+
app.use(httpRequestMiddleware)
|
|
30
|
+
app.use(accessMiddleware)
|
|
31
|
+
app.use(router)
|
|
32
|
+
app.use(errorHandler)
|
|
33
|
+
|
|
34
|
+
app.listen(port, (err?: Error) => {
|
|
35
|
+
if (err) {
|
|
36
|
+
LyraConsole.error("Error starting server:", err.message)
|
|
37
|
+
} else {
|
|
38
|
+
LyraConsole.info(
|
|
39
|
+
`${params.api_name} v${params.api_version}`,
|
|
40
|
+
`Server running at ${params.api_host}:${params.api_port}`,
|
|
41
|
+
""
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "esnext",
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"target": "es2017",
|
|
6
|
+
"lib": [
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"strictNullChecks": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"skipDefaultLibCheck": true,
|
|
15
|
+
"emitDecoratorMetadata": true,
|
|
16
|
+
"experimentalDecorators": true,
|
|
17
|
+
"outDir": "./dist",
|
|
18
|
+
"strictPropertyInitialization": false,
|
|
19
|
+
"noUnusedLocals": false,
|
|
20
|
+
"noUnusedParameters": false,
|
|
21
|
+
"paths": {
|
|
22
|
+
"@app/*": [
|
|
23
|
+
"./src/*"
|
|
24
|
+
],
|
|
25
|
+
"@config/*": [
|
|
26
|
+
"./config/*"
|
|
27
|
+
],
|
|
28
|
+
"@controller/*": [
|
|
29
|
+
"./src/controller/*"
|
|
30
|
+
],
|
|
31
|
+
"@fixtures/*": [
|
|
32
|
+
"./src/fixtures/*"
|
|
33
|
+
],
|
|
34
|
+
"@middleware/*": [
|
|
35
|
+
"./src/middleware/*"
|
|
36
|
+
],
|
|
37
|
+
"@entity/*": [
|
|
38
|
+
"./src/entity/*"
|
|
39
|
+
],
|
|
40
|
+
"@repository/*": [
|
|
41
|
+
"./src/repository/*"
|
|
42
|
+
],
|
|
43
|
+
"@router/*": [
|
|
44
|
+
"./src/router/*"
|
|
45
|
+
],
|
|
46
|
+
"@services/*": [
|
|
47
|
+
"./src/services/*"
|
|
48
|
+
],
|
|
49
|
+
"@tests/*": [
|
|
50
|
+
"./src/tests/*"
|
|
51
|
+
],
|
|
52
|
+
"@types/*": [
|
|
53
|
+
"./src/types/*"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"ts-node": {
|
|
58
|
+
"require": [
|
|
59
|
+
"tsconfig-paths/register"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|