create-sipere 0.9.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.
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const prompts = require('prompts');
5
+ const createProject = require('./lib/generator');
6
+
7
+ const program = new Command();
8
+
9
+ program
10
+ .description('create a new sipere project')
11
+ .argument('[project-name]', 'project name')
12
+ .option('-t, --typescript', 'use typescript')
13
+ .action(async (name, options) => {
14
+ await startGenerator(name, options);
15
+ });
16
+ program.parse(process.argv);
17
+
18
+ async function startGenerator(name, options) {
19
+ let projectName;
20
+ if(!name) {
21
+ const questions = [
22
+ {
23
+ type: 'text',
24
+ name: 'name',
25
+ message: 'Project name?',
26
+ initial: 'my-sipere'
27
+ }
28
+ ];
29
+ const answer = await prompts(questions);
30
+ projectName = answer.name;
31
+ }else {
32
+ projectName = name;
33
+ }
34
+
35
+ if (!projectName) return;
36
+ const CURRENT_DIR = __dirname;
37
+ await createProject(projectName, options, CURRENT_DIR);
38
+ }
39
+
@@ -0,0 +1,60 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const installDependencies = require('./utils');
4
+
5
+ const TARGET_DIR = process.cwd();
6
+
7
+ module.exports = async function createProject(projectName, options, CURRENT_DIR) {
8
+ const isTypeScript = options.typescript;
9
+
10
+ const templatePath = path.join(
11
+ CURRENT_DIR,
12
+ 'templates',
13
+ isTypeScript ? 'ts-rest-api' : 'js-rest-api'
14
+ );
15
+ const targetPath = path.join(TARGET_DIR, projectName);
16
+ if (fs.existsSync(targetPath)) {
17
+ console.log(`${targetPath} already exists`);
18
+ return;
19
+ }
20
+
21
+ try {
22
+ console.log('creating project...');
23
+ fs.mkdirSync(targetPath);
24
+ console.log('copying files...');
25
+ await fs.copy(templatePath, targetPath);
26
+ await updatePackageJson(targetPath, projectName);
27
+ console.log('installing dependencies...');
28
+ await installDependencies(targetPath);
29
+ printBasicHelp(projectName);
30
+ }catch (error) {
31
+ console.error(error);
32
+ }
33
+ }
34
+
35
+ async function updatePackageJson(targetPath, projectName) {
36
+ const packageJsonPath = path.join(targetPath, 'package.json');
37
+ if(fs.existsSync(packageJsonPath)) {
38
+ let pkg = await fs.readJson(packageJsonPath);
39
+ pkg.name = projectName;
40
+ pkg.description = `REST API project for ${projectName}`;
41
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
42
+ }
43
+ }
44
+
45
+ function printBasicHelp(name) {
46
+ console.log(`
47
+ Sipere REST API skeleton created
48
+ Read docs/user_doc.md
49
+ Run next commands:
50
+ cd ${name}
51
+ node op key:generate
52
+ npm run dev
53
+ Usable commands:
54
+ node op make:model thing
55
+ node op make:controller thing
56
+ The model and controller names must be
57
+ given in the singular. More info:
58
+ node op help
59
+ `);
60
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,14 @@
1
+ const { execa } = require('execa');
2
+
3
+ module.exports = async function installDependencies(projectPath) {
4
+ try {
5
+ await execa('npm', ['install'], {
6
+ cwd: projectPath,
7
+ stdio: 'inherit'
8
+ });
9
+ console.log('dependencies installed.');
10
+ } catch (error) {
11
+ console.error('Error! An error occurred during "npm install".', error);
12
+ throw new Error('npm installation error.');
13
+ }
14
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "create-sipere",
3
+ "version": "0.9.0",
4
+ "main": "create-sipere.js",
5
+ "bin": {
6
+ "create-sipere": "create-sipere.js"
7
+ },
8
+ "scripts": {
9
+ "test": "mocha"
10
+ },
11
+ "keywords": [
12
+ "express",
13
+ "generator"
14
+ ],
15
+ "author": "Sallai András",
16
+ "license": "MIT",
17
+ "description": "Backend framework for Express",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/sipere/create-sipere.git"
21
+ },
22
+ "dependencies": {
23
+ "commander": "^14.0.2",
24
+ "execa": "^9.6.0",
25
+ "fs-extra": "^11.3.2",
26
+ "prompts": "^2.4.2"
27
+ },
28
+ "devDependencies": {
29
+ "chai": "^6.2.1",
30
+ "exec": "^0.2.1",
31
+ "mocha": "^11.7.5"
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ APP_PORT=8000
2
+ APP_KEY=
3
+ APP_LOG=console.log
4
+
5
+ DB_DIALECT=sqlite
6
+ DB_HOST=127.0.0.1
7
+ DB_NAME=
8
+ DB_USER=
9
+ DB_PASS=
10
+ DB_STORAGE=:memory:
@@ -0,0 +1,7 @@
1
+
2
+ APP_PORT=8000
3
+ APP_KEY=my_secret_key
4
+ APP_LOG=false
5
+
6
+ DB_DIALECT=sqlite
7
+ DB_STORAGE=:memory:
@@ -0,0 +1,12 @@
1
+ # sipere expressapi template
2
+
3
+ Express based REST API template
4
+
5
+ ## Install dependencies
6
+
7
+ ```cmd
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ See the docs directory for details.
@@ -0,0 +1,17 @@
1
+ import express from 'express'
2
+ import morgan from 'morgan'
3
+ import cors from 'cors'
4
+ import fs from 'fs'
5
+ import router from './routes/api.js'
6
+ import './models/modrels.js'
7
+
8
+ const app = express()
9
+
10
+ const logfile = 'access.log'
11
+ var accessLogStream = fs.createWriteStream(logfile, { flags: 'a' })
12
+ app.use(morgan('dev', { stream: accessLogStream }))
13
+ app.use(cors())
14
+ app.use(express.json())
15
+ app.use('/api', router);
16
+
17
+ export default app
@@ -0,0 +1,103 @@
1
+ import bcrypt from 'bcryptjs'
2
+ import jwt from 'jsonwebtoken'
3
+ import User from '../models/user.js'
4
+ import dotenv from '@dotenvx/dotenvx'
5
+ dotenv.config({ quiet: true })
6
+
7
+ const AuthController = {
8
+ async register(req, res) {
9
+ var clientError = false;
10
+ try {
11
+ if(!req.body.name ||
12
+ !req.body.email ||
13
+ !req.body.password ||
14
+ !req.body.password_confirmation) {
15
+ clientError = true
16
+ throw new Error('Error! Bad request data!')
17
+ }
18
+ if(req.body.password != req.body.password_confirmation) {
19
+ clientError = true
20
+ throw new Error('Error! The two password is not same!')
21
+ }
22
+ const user = await User.findOne({
23
+ where: { name: req.body.name }
24
+ })
25
+ if(user) {
26
+ clientError = true
27
+ throw new Error('Error! User already exists: ' + user.name)
28
+ }
29
+ AuthController.tryRegister(req, res)
30
+ } catch (error) {
31
+ if (clientError) {
32
+ res.status(400)
33
+ }else {
34
+ res.status(500)
35
+ }
36
+ await res.json({
37
+ success: false,
38
+ message: 'Error! User creation failed!',
39
+ error: error.message
40
+ })
41
+ }
42
+ },
43
+ async tryRegister(req, res) {
44
+ const user = {
45
+ name: req.body.name,
46
+ email: req.body.email,
47
+ password: bcrypt.hashSync(req.body.password)
48
+ }
49
+ const result = await User.create(user)
50
+
51
+ res.status(201).json({
52
+ succes: true,
53
+ data: result
54
+ })
55
+
56
+ },
57
+ async login(req, res) {
58
+
59
+ try {
60
+ if(!req.body.name || !req.body.password) {
61
+ res.status(400)
62
+ throw new Error('Error! Bad name or password!')
63
+ }
64
+ const user = await User.findOne({
65
+ where: { name: req.body.name }
66
+ })
67
+
68
+ if(!user) {
69
+ res.status(404)
70
+ throw new Error('Error! User not found!')
71
+ }
72
+ var passwordIsValid = await bcrypt.compare(
73
+ req.body.password,
74
+ user.dataValues.password
75
+ );
76
+ if(!passwordIsValid) {
77
+ res.status(401)
78
+ throw new Error('Error! Password is not valid!')
79
+ }
80
+ AuthController.tryLogin(req, res, user)
81
+
82
+ } catch (error) {
83
+ res.json({
84
+ success: false,
85
+ message: 'Error! The login is failed!',
86
+ error: error.message
87
+ })
88
+ }
89
+ },
90
+ async tryLogin(req, res, user) {
91
+ var token = jwt.sign({ id: user.id }, process.env.APP_KEY, {
92
+ expiresIn: 86400 //24 óra
93
+ })
94
+ res.status(200).json({
95
+ id: user.id,
96
+ name: user.name,
97
+ email: user.email,
98
+ accessToken: token
99
+ })
100
+ }
101
+ }
102
+
103
+ export default AuthController
@@ -0,0 +1,129 @@
1
+ import bcrypt from 'bcryptjs'
2
+ import User from '../models/user.js'
3
+
4
+ const UserController = {
5
+ async index(req, res) {
6
+ try {
7
+ UserController.tryIndex(req, res)
8
+ }catch(error) {
9
+ res.status(500)
10
+ res.json({
11
+ success: false,
12
+ message: 'Error! The query is failed!'
13
+ })
14
+ }
15
+ },
16
+ async tryIndex(req, res) {
17
+ const users = await User.findAll()
18
+ res.status(200)
19
+ res.json({
20
+ success: true,
21
+ data: users
22
+ })
23
+ },
24
+ async show(req, res) {
25
+ try {
26
+ await UserController.tryShow(req, res)
27
+ }catch(error) {
28
+ res.status(500)
29
+ res.json({
30
+ success: false,
31
+ message: 'Error! The query is failed!'
32
+ })
33
+ }
34
+ },
35
+ async tryShow(req, res) {
36
+ const user = await User.findByPk(req.params.id)
37
+ res.status(200)
38
+ res.json({
39
+ success: true,
40
+ data: user
41
+ })
42
+ },
43
+ async create(req, res) {
44
+ var clientError = false;
45
+ try {
46
+ if(!req.body.name ||
47
+ !req.body.email ||
48
+ !req.body.password ||
49
+ !req.body.password_confirmation) {
50
+ clientError = true
51
+ throw new Error('Error! Bad request data!')
52
+ }
53
+ if(req.body.password != req.body.password_confirmation) {
54
+ clientError = true
55
+ throw new Error('Error! The two password is not same!')
56
+ }
57
+ const user = await User.findOne({
58
+ where: { name: req.body.name }
59
+ })
60
+ if(user) {
61
+ clientError = true
62
+ throw new Error('Error! User already exists: ' + user.name)
63
+ }
64
+ await UserController.tryCreate(req, res)
65
+ }catch(error) {
66
+ if (clientError) {
67
+ res.status(400)
68
+ }else {
69
+ res.status(500)
70
+ }
71
+ res.json({
72
+ success: false,
73
+ message: 'Error! The query is failed!',
74
+ error: error.message
75
+ })
76
+ }
77
+ },
78
+ async tryCreate(req, res) {
79
+ const newUser = {
80
+ name: req.body.name,
81
+ email: req.body.email,
82
+ password: bcrypt.hashSync(req.body.password)
83
+ }
84
+ const userData = await User.create(newUser)
85
+ res.status(201)
86
+ res.json({
87
+ success: true,
88
+ data: userData
89
+ })
90
+ },
91
+ async updatePassword(req, res) {
92
+ var clientError = false;
93
+ try {
94
+ if(!req.body.password ||
95
+ !req.body.password_confirmation) {
96
+ clientError = true
97
+ throw new Error('Error! Bad request data!')
98
+ }
99
+ if(req.body.password != req.body.password_confirmation) {
100
+ clientError = true
101
+ throw new Error('Error! The two password is not same!')
102
+ }
103
+ await UserController.tryUpdatePassword(req, res)
104
+ }catch(error) {
105
+ if (clientError) {
106
+ res.status(400)
107
+ }else {
108
+ res.status(500)
109
+ }
110
+ res.json({
111
+ success: false,
112
+ message: 'Error! The query is failed!',
113
+ error: error.message
114
+ })
115
+ }
116
+ },
117
+ async tryUpdatePassword(req, res) {
118
+ const user = await User.findByPk(req.params.id)
119
+ user.password = bcrypt.hashSync(req.body.password)
120
+ await user.save()
121
+ res.status(200)
122
+ res.json({
123
+ success: true,
124
+ data: user
125
+ })
126
+ }
127
+ }
128
+
129
+ export default UserController
@@ -0,0 +1,23 @@
1
+ import Sequelize from 'sequelize'
2
+ import dotenvFlow from 'dotenv-flow'
3
+
4
+ if (process.env.NODE_ENV === 'test') {
5
+ dotenvFlow.config({ path: '.env.test' })
6
+ }else {
7
+ dotenvFlow.config()
8
+ }
9
+
10
+ const sequelize = new Sequelize(
11
+ process.env.DB_NAME,
12
+ process.env.DB_USER,
13
+ process.env.DB_PASS,
14
+ {
15
+ dialect: process.env.DB_DIALECT,
16
+ storage: process.env.DB_STORAGE,
17
+ host: process.env.DB_HOST,
18
+ logging: process.env.APP_LOG === 'true' ? console.log : false,
19
+ dialectOptions: {}
20
+ }
21
+ )
22
+
23
+ export default sequelize
@@ -0,0 +1,10 @@
1
+ import app from './app.js'
2
+ import dotenvFlow from 'dotenv-flow'
3
+
4
+ dotenvFlow.config()
5
+
6
+ const PORT = process.env.APP_PORT || 8000
7
+
8
+ app.listen(PORT, () => {
9
+ console.log(`Listening on port: ${PORT}`)
10
+ })
@@ -0,0 +1,25 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import dotenvFlow from 'dotenv-flow';
3
+ dotenvFlow.config()
4
+
5
+ const verifyToken = (req, res, next) => {
6
+ let authData = req.headers.authorization;
7
+ if(!authData) {
8
+ return res.status(403).send({
9
+ message: 'No token provided!'
10
+ })
11
+ }
12
+ let token = authData.split(' ')[1];
13
+
14
+ jwt.verify(token, process.env.APP_KEY, (err, decoded) => {
15
+ if(err) {
16
+ return res.status(401).send({
17
+ message: "Unauthorized!"
18
+ })
19
+ }
20
+ req.userId = decoded.id;
21
+ next()
22
+ })
23
+ };
24
+
25
+ export default verifyToken
@@ -0,0 +1,16 @@
1
+ import User from './user.js';
2
+ import sequelize from '../database/database.js'
3
+
4
+ const db = {};
5
+
6
+ /* Import your models and write here.
7
+ For example User: */
8
+ db.User = User;
9
+
10
+ // await sequelize.sync({ alter: true })
11
+
12
+ /*
13
+ Write the relationships between the models here.
14
+ */
15
+
16
+ export default db;
@@ -0,0 +1,11 @@
1
+ import { DataTypes } from 'sequelize'
2
+ import sequelize from '../database/database.js'
3
+
4
+ const User = sequelize.define('user', {
5
+ name: { type: DataTypes.STRING, allowNull: false },
6
+ email: { type: DataTypes.STRING, allowNull: true },
7
+ password: { type: DataTypes.STRING , allowNull: false },
8
+ roleId: { type: DataTypes.INTEGER, defaultValue: 0 }
9
+ })
10
+
11
+ export default User
@@ -0,0 +1,14 @@
1
+ import Router from 'express'
2
+ const router = Router()
3
+
4
+ import AuthController from '../controllers/authController.js';
5
+ import UserController from '../controllers/userController.js';
6
+ import verifyToken from '../middleware/authjwt.js';
7
+
8
+ router.post('/register', AuthController.register)
9
+ router.post('/login', AuthController.login)
10
+ router.get('/users', [verifyToken], UserController.index)
11
+ router.get('/users/:id', [verifyToken], UserController.show)
12
+ router.put('/users/:id/password', [verifyToken], UserController.updatePassword)
13
+
14
+ export default router
@@ -0,0 +1,36 @@
1
+ import { DataTypes } from 'sequelize';
2
+
3
+ async function up({context: QueryInterface}) {
4
+ await QueryInterface.createTable('users', {
5
+ id: {
6
+ allowNull: false,
7
+ autoIncrement: true,
8
+ primaryKey: true,
9
+ type: DataTypes.INTEGER
10
+ },
11
+ name: {
12
+ type: DataTypes.STRING,
13
+ allowNull: false
14
+ },
15
+ email: {
16
+ type: DataTypes.STRING,
17
+ allowNull: true
18
+ },
19
+ password: {
20
+ type: DataTypes.STRING,
21
+ allowNull: false
22
+ },
23
+ roleId: {
24
+ type: DataTypes.INTEGER,
25
+ defaultValue: 0
26
+ },
27
+ createdAt: { type: DataTypes.DATE },
28
+ updatedAt: { type: DataTypes.DATE }
29
+ });
30
+ }
31
+
32
+ async function down({context: QueryInterface}) {
33
+ await QueryInterface.dropTable('users');
34
+ }
35
+
36
+ export { up, down }
@@ -0,0 +1,31 @@
1
+ # expressapi
2
+
3
+ ## API KEY
4
+
5
+ The app key generated with generate-api-key package.
6
+
7
+ ## Testing
8
+
9
+ Run tests:
10
+
11
+ ```cmd
12
+ npm test
13
+ ```
14
+
15
+ The test using the .env.test file and run in the memory database.
16
+
17
+ Tests can be placed in the test directory, where Mocha runs them.
18
+
19
+ ## Development
20
+
21
+ Start the application:
22
+
23
+ ```cmd
24
+ npm run dev
25
+ ```
26
+
27
+ Restarting the application when saving is done by nodemon.
28
+
29
+ ## Migartaions and seeders
30
+
31
+ The project uses umzug for migrations and seeders.
@@ -0,0 +1,340 @@
1
+ # User documentation
2
+
3
+ ## Install dependencies
4
+
5
+ Dependencies must be installed before use.
6
+
7
+ ```cmd
8
+ npm install
9
+ ```
10
+
11
+ Or use your favorite package manager.
12
+
13
+ ## Generate config file
14
+
15
+ The settings are located in a file called:
16
+
17
+ * .env
18
+
19
+ To generate the .env file:
20
+
21
+ ```cmd
22
+ node op conf:generate
23
+ ```
24
+
25
+ ### Test configurations file generate
26
+
27
+ ```cmd
28
+ node op testconf:generate
29
+ ```
30
+
31
+ Result: .env.test file.
32
+
33
+ ## App key generation
34
+
35
+ Te generate the application key:
36
+
37
+ ```cmd
38
+ node op key:generate
39
+ ```
40
+
41
+ ## Database setup
42
+
43
+ Edit the .env file.
44
+
45
+ ## Endpoints
46
+
47
+ All endpoint have a /api prefix.
48
+
49
+ | Endpoint | Method | Auth | Description |
50
+ |-|-|-|-|
51
+ | /register | POST | no | create user |
52
+ | /login | POST | no | login |
53
+ | /users | GET | yes | read users |
54
+ | /users/:id | GET | yes | read user |
55
+ | /users/:id/password | PUT | yes | change password |
56
+
57
+ ## The register endpoint
58
+
59
+ ```json
60
+ {
61
+ "name": "joe",
62
+ "email": "joe@green.lan",
63
+ "password": "secret",
64
+ "password_confirmation": "secret"
65
+ }
66
+ ```
67
+
68
+ ## The login endpoint
69
+
70
+ ```json
71
+ {
72
+ "name": "joe",
73
+ "password": "secret"
74
+ }
75
+ ```
76
+
77
+ You receive the bearear token with accessToken key.
78
+
79
+ ## The users endpoint
80
+
81
+ To query users or user, send the bearer token to endpoint.
82
+
83
+ ## Model and controller generation
84
+
85
+ Use the following instructions to generate models and controllers:
86
+
87
+ ```cmd
88
+ node op make:model thing
89
+ node op make:controller thing
90
+ ```
91
+
92
+ ## Key generation
93
+
94
+ You can generate the application key with the following command:
95
+
96
+ ```cmd
97
+ node op key:generate
98
+ ```
99
+
100
+ ## Generate admin user
101
+
102
+ You can create an admin user if it does not already exist in the database:
103
+
104
+ ```cmd
105
+ node op admin:generate
106
+ ```
107
+
108
+ ## Database import
109
+
110
+ It is possible to import data from JSON and CSV files.
111
+
112
+ ```cmd
113
+ node op db:import thing things.json
114
+ node op db:import thing things.csv
115
+ node op db:import thing things.csv ,
116
+ node op db:import thing things.csv ";"
117
+ node op db:import thing things.csv :
118
+ ```
119
+
120
+ The last option is the separator.
121
+
122
+ For example JSON file:
123
+
124
+ employees.json:
125
+
126
+ ```json
127
+ [
128
+ { "id": 1, "name": "Tom Large" },
129
+ { "id": 2, "name": "Jack Small" }
130
+ ]
131
+ ```
132
+
133
+ The default separator is comma.
134
+
135
+ ```cmd
136
+ node op db:import employee employees.json
137
+ ```
138
+
139
+ For example CSV file:
140
+
141
+ employees.csv:
142
+
143
+ ```csv
144
+ id,name
145
+ 1,Joe Kitin
146
+ 2,Peter Fall
147
+ ```
148
+
149
+ If you have colon separator, use sep parameter.
150
+
151
+ ```csv
152
+ id:name
153
+ 1:Joe Kitin
154
+ 2:Peter Fall
155
+ ```
156
+
157
+ ```cmd
158
+ node op db:import employee employees.csv --sep :
159
+ ```
160
+
161
+ If the file has semicolon separator, use sep parameter, for example:
162
+
163
+ ```csv
164
+ id;name
165
+ 1;Joe Kitin
166
+ 2;Peter Fall
167
+ ```
168
+
169
+ Use next command:
170
+
171
+ ```cmd
172
+ node op db:import employee employees.csv --sep ";"
173
+ ```
174
+
175
+ ## Database
176
+
177
+ ### Database synchronization
178
+
179
+ Models and database tables can be synchronized, but this can be dangerous.
180
+
181
+ Database synchronization can be set up in the app/models/modrels.js file. Default values are:
182
+
183
+ ```js
184
+ { alter: true }
185
+ ```
186
+
187
+ This preserves the data and existing structure.
188
+
189
+ Possible values:
190
+
191
+ ```js
192
+ { force: true }
193
+ ```
194
+
195
+ The latter deletes the contents of the database table!
196
+
197
+ If the value is false, there is no synchronization in either case.
198
+
199
+ ### Migration
200
+
201
+ Generate a migration:
202
+
203
+ ```bash
204
+ node op make/migration thing
205
+ ```
206
+
207
+ For example migration for employee table:
208
+
209
+ ```bash
210
+ node op make/migration employee
211
+ ```
212
+
213
+ ```javascript
214
+ import { DataTypes } from 'sequelize';
215
+
216
+ async function up({context: QueryInterface}) {
217
+ await QueryInterface.createTable('employees', {
218
+ id: {
219
+ allowNull: false,
220
+ autoIncrement: true,
221
+ primaryKey: true,
222
+ type: DataTypes.INTEGER
223
+ },
224
+ name: {
225
+ type: DataTypes.STRING
226
+ },
227
+ city: {
228
+ type: DataTypes.STRING
229
+ },
230
+ createdAt: { type: DataTypes.DATE },
231
+ updatedAt: { type: DataTypes.DATE }
232
+ });
233
+ }
234
+
235
+ async function down({context: QueryInterface}) {
236
+ await QueryInterface.dropTable('employees');
237
+ }
238
+
239
+ export { up, down }
240
+ ```
241
+
242
+ Run all migration:
243
+
244
+ ```bash
245
+ node op migration:run
246
+ node op migrate
247
+ ```
248
+
249
+ Run a migration:
250
+
251
+ ```bash
252
+ node op migration:run <migration_name>
253
+ node op migrate <migration_name>
254
+ ```
255
+
256
+ Rollback the last migration:
257
+
258
+ ```bash
259
+ node op migration:rollback
260
+ node op migrate:rollback
261
+ ```
262
+
263
+ Rollback two migrations:
264
+
265
+ ```bash
266
+ node op migration:rollback 2
267
+ node op migrate:rollback 2
268
+ ```
269
+
270
+ Reset the database:
271
+
272
+ ```bash
273
+ node op migration:reset
274
+ node op migrate:reset
275
+ ```
276
+
277
+ This command also undoes seeder operations.
278
+
279
+ Reset the database and run all migrations:
280
+
281
+ ```bash
282
+ node op migration:fresh
283
+ node op migrate:fresh
284
+ ```
285
+
286
+ ### Database seed
287
+
288
+ Generate a seeder:
289
+
290
+ ```bash
291
+ node op make/seeder thing
292
+ ```
293
+
294
+ Example seeder for employee table:
295
+
296
+ ```bash
297
+ node op make/seeder employee
298
+ ```
299
+
300
+ ```javascript
301
+
302
+ async function up({context: QueryInterface}) {
303
+ if(db.Employee) {
304
+ await db.Employee.bulkCreate([
305
+ { id: 1, name: "John Smith" },
306
+ { id: 2, name: "Alice Johnson" }
307
+ ]);
308
+ }else {
309
+ const now = new Date()
310
+ await QueryInterface.bulkInsert('things', [
311
+ {
312
+ id: 1, name: "John Smith",
313
+ createdAt: now, updatedAt: now
314
+ },
315
+ {
316
+ id: 2, name: "Alice Johnson",
317
+ createdAt: now, updatedAt: now
318
+ }
319
+ ]);
320
+ }
321
+ }
322
+
323
+ async function down({context: QueryInterface}) {
324
+ await QueryInterface.bulkDelete('things', null, {});
325
+ }
326
+
327
+ export { up, down }
328
+ ````
329
+
330
+ Run all seeders:
331
+
332
+ ```bash
333
+ node op db:seed
334
+ ```
335
+
336
+ Run a seeder:
337
+
338
+ ```bash
339
+ node op db:seed path_thing
340
+ ```
@@ -0,0 +1,4 @@
1
+ {
2
+ "watch": ["app", "config"],
3
+ "ext": ".js,.json"
4
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { opCommander } from '@sipere/op-cli/commander.js';
4
+
5
+ opCommander.parse(process.argv)
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "expressapi",
3
+ "version": "0.0.1",
4
+ "description": "Express API with simple Sequelize",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "NODE_ENV=test mocha",
8
+ "dev": "nodemon app",
9
+ "start": "node app"
10
+ },
11
+ "keywords": [
12
+ "express",
13
+ "api"
14
+ ],
15
+ "author": "your name",
16
+ "license": "ISC",
17
+ "dependencies": {
18
+ "@dotenvx/dotenvx": "^1.51.0",
19
+ "bcryptjs": "^2.4.3",
20
+ "cors": "^2.8.5",
21
+ "dotenv-flow": "^4.1.0",
22
+ "express": "^4.18.2",
23
+ "generate-api-key": "^1.0.2",
24
+ "jsonwebtoken": "^9.0.0",
25
+ "mariadb": "^3.1.2",
26
+ "morgan": "^1.10.0",
27
+ "read": "^4.1.0",
28
+ "replace": "^1.2.2",
29
+ "sequelize": "^6.32.0",
30
+ "sqlite3": "^5.1.6",
31
+ "umzug": "^3.8.2",
32
+ "yargs": "^18.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/bcryptjs": "^2.4.6",
36
+ "fs-extra": "^11.3.0",
37
+ "mocha": "^11.7.4",
38
+ "nodemon": "^2.0.22",
39
+ "supertest": "^6.3.3"
40
+ },
41
+ "type": "module"
42
+ }
@@ -0,0 +1,43 @@
1
+ import supertest from 'supertest'
2
+ import app from '../app/app.js'
3
+
4
+ describe('/api/register and /api/login', () => {
5
+ const restype= 'application/json; charset=utf-8'
6
+ var token = null
7
+
8
+ it('post /register ', function(done) {
9
+ supertest(app)
10
+ .post('/api/register')
11
+ .set('Accept', 'application/json')
12
+ .send({
13
+ name: 'mari',
14
+ email: 'mari@zold.lan',
15
+ password: 'titok',
16
+ password_confirmation: 'titok'
17
+ })
18
+ .expect('Content-Type', restype)
19
+ .expect(201, done)
20
+
21
+ })
22
+ it('post /login ', (done) => {
23
+ supertest(app)
24
+ .post('/api/login')
25
+ .set('Accept', 'application/json')
26
+ .send({
27
+ name: 'mari',
28
+ password: 'titok'
29
+ })
30
+ .expect('Content-Type', restype)
31
+ .expect(200, done)
32
+ .expect(res => {
33
+ token = res.body.accessToken
34
+ })
35
+ })
36
+ it('get /users ', function(done) {
37
+ supertest(app)
38
+ .get('/api/users')
39
+ .set('Accept', 'application/json')
40
+ .set('Authorization', 'Bearer ' + token)
41
+ .expect(200, done)
42
+ })
43
+ })
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "js-rest-api",
3
+ "version": "0.0.1",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "",
10
+ "license": "ISC",
11
+ "description": "",
12
+ "dependencies": {
13
+ "cors": "^2.8.5",
14
+ "express": "^5.1.0"
15
+ },
16
+ "devDependencies": {
17
+ "nodemon": "^3.1.11"
18
+ },
19
+ "type": "module"
20
+ }
File without changes
@@ -0,0 +1,33 @@
1
+ const { exec } = require('child_process');
2
+ const assert = require('assert');
3
+
4
+ describe('create-sipere', function() {
5
+ this.timeout(8000);
6
+ it('create project', (done) => {
7
+ const cmd = './create-sipere.js';
8
+ const child = exec(cmd);
9
+ let output = '';
10
+ child.stdout.on('data', (data) => {
11
+ output += data;
12
+ if(output.includes('Project name?')) {
13
+ child.stdin.write('tesztprojekt\n');
14
+ }
15
+ // if(output.includes('Típus:')) {
16
+ // child.stdin.write('typescript\n');
17
+ // }
18
+ });
19
+
20
+ child.on('close', (code) => {
21
+ assert.ok(output.includes('installing'));
22
+ exec('rm -rf tesztprojekt', () => {});
23
+ done();
24
+ });
25
+
26
+
27
+ });
28
+
29
+ });
30
+
31
+
32
+
33
+