sk-test-node-deploy 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/bin/cli.js +13 -0
- package/lib/index.js +134 -0
- package/package.json +38 -0
- package/template/.github/workflows/staging.yml +25 -0
- package/template/COMMANDS.md +3 -0
- package/template/env.md +5 -0
- package/template/package-lock.json +2676 -0
- package/template/package.json +42 -0
- package/template/src/app.ts +68 -0
- package/template/src/config/database.ts +12 -0
- package/template/src/controllers/auth.controller.ts +219 -0
- package/template/src/controllers/file.controller.ts +31 -0
- package/template/src/controllers/user.controller.ts +160 -0
- package/template/src/middlewares/auth.middleware.ts +56 -0
- package/template/src/middlewares/upload.middleware.ts +27 -0
- package/template/src/middlewares/validation.middleware.ts +70 -0
- package/template/src/models/user.model.ts +47 -0
- package/template/src/routes/auth.routes.ts +15 -0
- package/template/src/routes/file.routes.ts +11 -0
- package/template/src/routes/user.routes.ts +15 -0
- package/template/src/socket/socker.handler.ts +103 -0
- package/template/src/types/express.d.ts +10 -0
- package/template/src/utils/jwt.util.ts +54 -0
- package/template/src/validation/user.validation.ts +27 -0
- package/template/tsconfig.json +31 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import mongoose, { Document, Schema } from 'mongoose';
|
|
2
|
+
|
|
3
|
+
export interface IUser extends Document {
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
password: string;
|
|
7
|
+
refreshToken?: string;
|
|
8
|
+
createdAt: Date;
|
|
9
|
+
updatedAt: Date;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const userSchema = new Schema<IUser>(
|
|
13
|
+
{
|
|
14
|
+
name: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: [true, 'Name is required'],
|
|
17
|
+
trim: true,
|
|
18
|
+
minlength: [2, 'Name must be at least 2 characters'],
|
|
19
|
+
maxlength: [50, 'Name cannot exceed 50 characters']
|
|
20
|
+
},
|
|
21
|
+
email: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: [true, 'Email is required'],
|
|
24
|
+
unique: true,
|
|
25
|
+
lowercase: true,
|
|
26
|
+
trim: true,
|
|
27
|
+
match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email']
|
|
28
|
+
},
|
|
29
|
+
password: {
|
|
30
|
+
type: String,
|
|
31
|
+
required: [true, 'Password is required'],
|
|
32
|
+
minlength: [6, 'Password must be at least 6 characters'],
|
|
33
|
+
select: false
|
|
34
|
+
},
|
|
35
|
+
refreshToken: {
|
|
36
|
+
type: String,
|
|
37
|
+
select: false
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
timestamps: true
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const User = mongoose.model<IUser>('User', userSchema);
|
|
46
|
+
|
|
47
|
+
export default User;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { authController } from '../controllers/auth.controller';
|
|
3
|
+
import { validate, registerSchema, loginSchema, refreshTokenSchema } from '../middlewares/validation.middleware';
|
|
4
|
+
import { authenticate } from '../middlewares/auth.middleware';
|
|
5
|
+
|
|
6
|
+
const authRoutes = Router();
|
|
7
|
+
|
|
8
|
+
authRoutes.post('/refresh', validate(refreshTokenSchema), authController.refreshToken);
|
|
9
|
+
authRoutes.post('/register', validate(registerSchema), authController.register);
|
|
10
|
+
authRoutes.post('/login', validate(loginSchema), authController.login);
|
|
11
|
+
|
|
12
|
+
authRoutes.post('/logout', authenticate, authController.logout);
|
|
13
|
+
authRoutes.get('/profile', authenticate, authController.getProfile);
|
|
14
|
+
|
|
15
|
+
export default authRoutes;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { fileController } from '../controllers/file.controller';
|
|
3
|
+
import { authenticate } from '../middlewares/auth.middleware';
|
|
4
|
+
import { uploadMiddleware } from '../middlewares/upload.middleware';
|
|
5
|
+
|
|
6
|
+
const fileRoutes = Router();
|
|
7
|
+
|
|
8
|
+
fileRoutes.use(authenticate);
|
|
9
|
+
fileRoutes.post('/upload', uploadMiddleware.single('file'), fileController.uploadFile);
|
|
10
|
+
|
|
11
|
+
export default fileRoutes;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { userController } from '../controllers/user.controller';
|
|
3
|
+
import { authenticate } from '../middlewares/auth.middleware';
|
|
4
|
+
import { validate, updateUserSchema } from '../middlewares/validation.middleware';
|
|
5
|
+
|
|
6
|
+
const userRoutes = Router();
|
|
7
|
+
|
|
8
|
+
userRoutes.use(authenticate);
|
|
9
|
+
|
|
10
|
+
userRoutes.get('/', userController.getAllUsers);
|
|
11
|
+
userRoutes.get('/:id', userController.getUserById);
|
|
12
|
+
userRoutes.put('/:id', validate(updateUserSchema), userController.updateUser);
|
|
13
|
+
userRoutes.delete('/:id', userController.deleteUser);
|
|
14
|
+
|
|
15
|
+
export default userRoutes;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Server, Socket } from 'socket.io';
|
|
2
|
+
|
|
3
|
+
interface Message {
|
|
4
|
+
username: string;
|
|
5
|
+
message: string;
|
|
6
|
+
timestamp: Date;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Store active users
|
|
10
|
+
const activeUsers = new Map<string, string>();
|
|
11
|
+
|
|
12
|
+
export const initializeSocket = (io: Server) => {
|
|
13
|
+
|
|
14
|
+
io.on('connection', (socket: Socket) => {
|
|
15
|
+
console.log(`User connected: ${socket.id}`);
|
|
16
|
+
|
|
17
|
+
// Handle user joining
|
|
18
|
+
socket.on('join', (username: string) => {
|
|
19
|
+
activeUsers.set(socket.id, username);
|
|
20
|
+
console.log(`${username} joined the chat`);
|
|
21
|
+
|
|
22
|
+
// Notify all users
|
|
23
|
+
io.emit('user_joined', {
|
|
24
|
+
username,
|
|
25
|
+
timestamp: new Date(),
|
|
26
|
+
activeUsers: activeUsers.size
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Send active users list to the new user
|
|
30
|
+
socket.emit('active_users', {
|
|
31
|
+
count: activeUsers.size,
|
|
32
|
+
users: Array.from(activeUsers.values())
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Handle incoming messages
|
|
37
|
+
socket.on('message', (data: { username: string; message: string }) => {
|
|
38
|
+
const messageData: Message = {
|
|
39
|
+
username: data.username,
|
|
40
|
+
message: data.message,
|
|
41
|
+
timestamp: new Date()
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
console.log(`Message from ${data.username}: ${data.message}`);
|
|
45
|
+
|
|
46
|
+
// Broadcast message to all users
|
|
47
|
+
io.emit('message', messageData);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Handle typing indicator
|
|
51
|
+
socket.on('typing', (data: { username: string; isTyping: boolean }) => {
|
|
52
|
+
socket.broadcast.emit('user_typing', data);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Handle private messages
|
|
56
|
+
socket.on('private_message', (data: { to: string; message: string; from: string }) => {
|
|
57
|
+
// Find recipient socket
|
|
58
|
+
const recipientSocketId = Array.from(activeUsers.entries())
|
|
59
|
+
.find(([_, username]) => username === data.to)?.[0];
|
|
60
|
+
|
|
61
|
+
if (recipientSocketId) {
|
|
62
|
+
io.to(recipientSocketId).emit('private_message', {
|
|
63
|
+
from: data.from,
|
|
64
|
+
message: data.message,
|
|
65
|
+
timestamp: new Date()
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Send confirmation to sender
|
|
69
|
+
socket.emit('message_sent', {
|
|
70
|
+
to: data.to,
|
|
71
|
+
status: 'delivered'
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
socket.emit('message_sent', {
|
|
75
|
+
to: data.to,
|
|
76
|
+
status: 'user_not_found'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Handle disconnection
|
|
82
|
+
socket.on('disconnect', () => {
|
|
83
|
+
const username = activeUsers.get(socket.id);
|
|
84
|
+
activeUsers.delete(socket.id);
|
|
85
|
+
|
|
86
|
+
console.log(`User disconnected: ${socket.id}`);
|
|
87
|
+
|
|
88
|
+
if (username) {
|
|
89
|
+
io.emit('user_left', {
|
|
90
|
+
username,
|
|
91
|
+
timestamp: new Date(),
|
|
92
|
+
activeUsers: activeUsers.size
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Handle errors
|
|
98
|
+
socket.on('error', (error) => {
|
|
99
|
+
console.error('Socket error:', error);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
|
|
3
|
+
interface TokenPayload {
|
|
4
|
+
userId: string;
|
|
5
|
+
email: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class JWTUtil {
|
|
9
|
+
private static secret = process.env.JWT_SECRET || 'JWT_SECRET';
|
|
10
|
+
private static expiresIn = (process.env.JWT_EXPIRE || '7d') as string;
|
|
11
|
+
private static refreshSecret = process.env.JWT_SECRET || 'JWT_SECRET';
|
|
12
|
+
private static refreshExpiresIn = (process.env.JWT_REFRESH_EXPIRE || '30d') as string;
|
|
13
|
+
|
|
14
|
+
// Generate access token
|
|
15
|
+
static generateToken(payload: TokenPayload): string {
|
|
16
|
+
return jwt.sign(payload, this.secret, {
|
|
17
|
+
expiresIn: this.expiresIn as jwt.SignOptions['expiresIn']
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Generate refresh token
|
|
22
|
+
static generateRefreshToken(payload: TokenPayload): string {
|
|
23
|
+
return jwt.sign(payload, this.refreshSecret, {
|
|
24
|
+
expiresIn: this.refreshExpiresIn as jwt.SignOptions['expiresIn']
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Verify access token
|
|
29
|
+
static verifyToken(token: string): TokenPayload | null {
|
|
30
|
+
try {
|
|
31
|
+
return jwt.verify(token, this.secret) as TokenPayload;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Verify refresh token
|
|
38
|
+
static verifyRefreshToken(token: string): TokenPayload | null {
|
|
39
|
+
try {
|
|
40
|
+
return jwt.verify(token, this.refreshSecret) as TokenPayload;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Decode token without verification
|
|
47
|
+
static decodeToken(token: string): any {
|
|
48
|
+
try {
|
|
49
|
+
return jwt.decode(token);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
|
|
3
|
+
export const registerSchema = Joi.object({
|
|
4
|
+
name: Joi.string().min(2).max(50).required().messages({
|
|
5
|
+
'string.empty': 'Name is required',
|
|
6
|
+
'string.min': 'Name must be at least 2 characters',
|
|
7
|
+
'string.max': 'Name cannot exceed 50 characters'
|
|
8
|
+
}),
|
|
9
|
+
email: Joi.string().email().required().messages({
|
|
10
|
+
'string.empty': 'Email is required',
|
|
11
|
+
'string.email': 'Please provide a valid email address'
|
|
12
|
+
}),
|
|
13
|
+
password: Joi.string().min(6).required().messages({
|
|
14
|
+
'string.empty': 'Password is required',
|
|
15
|
+
'string.min': 'Password must be at least 6 characters'
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const loginSchema = Joi.object({
|
|
20
|
+
email: Joi.string().email().required().messages({
|
|
21
|
+
'string.empty': 'Email is required',
|
|
22
|
+
'string.email': 'Please provide a valid email address'
|
|
23
|
+
}),
|
|
24
|
+
password: Joi.string().required().messages({
|
|
25
|
+
'string.empty': 'Password is required'
|
|
26
|
+
})
|
|
27
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020"
|
|
7
|
+
],
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"moduleResolution": "node",
|
|
16
|
+
"typeRoots": [
|
|
17
|
+
"./node_modules/@types",
|
|
18
|
+
"./src/types",
|
|
19
|
+
"./types"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"ts-node": {
|
|
23
|
+
"files": true
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"src/**/*"
|
|
27
|
+
],
|
|
28
|
+
"exclude": [
|
|
29
|
+
"node_modules"
|
|
30
|
+
]
|
|
31
|
+
}
|