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.
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "nodejs-boilerplate",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1",
7
+ "dev": "nodemon src/app.ts",
8
+ "build": "tsc",
9
+ "start": "node dist/app.js"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "ISC",
14
+ "description": "",
15
+ "dependencies": {
16
+ "bcryptjs": "^3.0.3",
17
+ "compression": "^1.8.1",
18
+ "cors": "^2.8.5",
19
+ "dotenv": "^17.2.3",
20
+ "express": "^5.2.1",
21
+ "helmet": "^8.1.0",
22
+ "joi": "^18.0.2",
23
+ "jsonwebtoken": "^9.0.3",
24
+ "mongoose": "^9.0.2",
25
+ "multer": "^2.0.2",
26
+ "socket.io": "^4.8.2"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bcryptjs": "^2.4.6",
30
+ "@types/compression": "^1.8.1",
31
+ "@types/cors": "^2.8.19",
32
+ "@types/express": "^5.0.6",
33
+ "@types/jsonwebtoken": "^9.0.10",
34
+ "@types/mongoose": "^5.11.96",
35
+ "@types/multer": "^2.0.0",
36
+ "@types/node": "^25.0.3",
37
+ "@types/socket.io": "^3.0.1",
38
+ "nodemon": "^3.1.11",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.9.3"
41
+ }
42
+ }
@@ -0,0 +1,68 @@
1
+ import express, { Application, Router } from "express";
2
+ import dotenv from "dotenv";
3
+ import http from "http";
4
+ import { Server } from "socket.io";
5
+
6
+ import authRoutes from "./routes/auth.routes";
7
+ import userRoutes from "./routes/user.routes";
8
+ import fileRoutes from "./routes/file.routes";
9
+
10
+ import { initializeSocket } from "./socket/socker.handler";
11
+ import { connectDB } from "./config/database";
12
+
13
+ dotenv.config();
14
+
15
+ const app: Application = express();
16
+ const server = http.createServer(app);
17
+
18
+ const io = new Server(server, {
19
+ cors: {
20
+ origin: "*",
21
+ methods: ["GET", "POST"],
22
+ },
23
+ });
24
+
25
+ app.use(express.json());
26
+ app.use(express.urlencoded({ extended: true }));
27
+
28
+ // Health check route
29
+ app.get("/", (req, res) => {
30
+ res.json({
31
+ status: 200,
32
+ message: "API is running successfully",
33
+ data: {
34
+ version: "1.0.0",
35
+ timestamp: new Date().toISOString(),
36
+ },
37
+ });
38
+ });
39
+
40
+ app.use("/api/v1/auth", authRoutes);
41
+ app.use("/api/v1/users", userRoutes);
42
+ app.use("/api/v1/files", fileRoutes);
43
+
44
+ initializeSocket(io);
45
+
46
+ const PORT = process.env.PORT || 3000;
47
+
48
+ const startServer = async () => {
49
+ try {
50
+ await connectDB();
51
+
52
+ server.listen(PORT, () => {
53
+ console.log(`Server running on http://localhost:${PORT}`);
54
+ });
55
+ } catch (error) {
56
+ console.error("Failed to start server:", error);
57
+ process.exit(1);
58
+ }
59
+ };
60
+
61
+ startServer();
62
+
63
+ process.on("unhandledRejection", (err: Error) => {
64
+ console.error("Unhandled Rejection:", err.message);
65
+ server.close(() => process.exit(1));
66
+ });
67
+
68
+ export { app, io };
@@ -0,0 +1,12 @@
1
+ import mongoose from "mongoose";
2
+
3
+ export const connectDB = async (): Promise<void> => {
4
+ try {
5
+ const mongoURI = process.env.MONGODB_URI;
6
+ await mongoose.connect(mongoURI as string);
7
+ mongoose.connection.on("error", (err) => { });
8
+ } catch (error) {
9
+ console.error("MongoDB connection error:", error);
10
+ process.exit(1);
11
+ }
12
+ };
@@ -0,0 +1,219 @@
1
+ import { Request, Response } from "express";
2
+ import User from "../models/user.model";
3
+ import { JWTUtil } from "../utils/jwt.util";
4
+ import bcrypt from "bcryptjs";
5
+
6
+ class AuthController {
7
+ async refreshToken(req: Request, res: Response) {
8
+ try {
9
+ const { refreshToken } = req.body;
10
+
11
+ if (!refreshToken) {
12
+ return res.status(400).json({
13
+ status: 400,
14
+ message: "Refresh token is required",
15
+ });
16
+ }
17
+
18
+ const decoded = JWTUtil.verifyRefreshToken(refreshToken);
19
+
20
+ if (!decoded) {
21
+ return res.status(401).json({
22
+ status: 401,
23
+ message: "Invalid or expired refresh token",
24
+ });
25
+ }
26
+
27
+ const user = await User.findById(decoded.userId).select("+refreshToken");
28
+
29
+ if (!user || user.refreshToken !== refreshToken) {
30
+ return res.status(401).json({
31
+ status: 401,
32
+ message: "Invalid refresh token",
33
+ });
34
+ }
35
+
36
+ const newAccessToken = JWTUtil.generateToken({
37
+ userId: user._id.toString(),
38
+ email: user.email,
39
+ });
40
+
41
+ return res.status(200).json({
42
+ status: 200,
43
+ message: "Token refreshed successfully",
44
+ data: {
45
+ accessToken: newAccessToken,
46
+ },
47
+ });
48
+ } catch (error) {
49
+ return res.status(500).json({
50
+ status: 500,
51
+ message: "Token refresh failed",
52
+ });
53
+ }
54
+ }
55
+
56
+ async register(req: Request, res: Response) {
57
+ try {
58
+ const { name, email, password } = req.body;
59
+
60
+ const existingUser = await User.findOne({ email });
61
+ if (existingUser) {
62
+ return res.status(400).json({
63
+ status: 400,
64
+ message: "Email already registered",
65
+ });
66
+ }
67
+
68
+ const user = await User.create({
69
+ name,
70
+ email,
71
+ password,
72
+ });
73
+
74
+ const accessToken = JWTUtil.generateToken({
75
+ userId: user._id.toString(),
76
+ email: user.email,
77
+ });
78
+
79
+ const refreshToken = JWTUtil.generateRefreshToken({
80
+ userId: user._id.toString(),
81
+ email: user.email,
82
+ });
83
+
84
+ user.refreshToken = refreshToken;
85
+ await user.save();
86
+
87
+ return res.status(201).json({
88
+ status: 201,
89
+ message: "User registered successfully",
90
+ data: {
91
+ user: {
92
+ id: user._id,
93
+ name: user.name,
94
+ email: user.email,
95
+ },
96
+ accessToken,
97
+ refreshToken,
98
+ },
99
+ });
100
+ } catch (error) {
101
+ return res.status(500).json({
102
+ status: 500,
103
+ message: "Registration failed",
104
+ });
105
+ }
106
+ }
107
+
108
+ async login(req: Request, res: Response) {
109
+ try {
110
+ const { email, password } = req.body;
111
+
112
+ const user = await User.findOne({ email }).select("+password");
113
+
114
+ if (!user) {
115
+ return res.status(401).json({
116
+ status: 401,
117
+ message: "Invalid credentials",
118
+ });
119
+ }
120
+
121
+ const isPasswordValid = await bcrypt.compare(password, user.password);
122
+
123
+ if (!isPasswordValid) {
124
+ return res.status(401).json({
125
+ status: 401,
126
+ message: "Invalid credentials",
127
+ });
128
+ }
129
+
130
+ const accessToken = JWTUtil.generateToken({
131
+ userId: user._id.toString(),
132
+ email: user.email,
133
+ });
134
+
135
+ const refreshToken = JWTUtil.generateRefreshToken({
136
+ userId: user._id.toString(),
137
+ email: user.email,
138
+ });
139
+
140
+ user.refreshToken = refreshToken;
141
+ await user.save();
142
+
143
+ return res.status(200).json({
144
+ status: 200,
145
+ message: "Login successful",
146
+ data: {
147
+ user: {
148
+ id: user._id,
149
+ name: user.name,
150
+ email: user.email,
151
+ },
152
+ accessToken,
153
+ refreshToken,
154
+ },
155
+ });
156
+ } catch (error) {
157
+ return res.status(500).json({
158
+ status: 500,
159
+ message: "Login failed",
160
+ });
161
+ }
162
+ }
163
+
164
+ async logout(req: Request, res: Response) {
165
+ try {
166
+ const userId = req.user?.userId;
167
+
168
+ if (!userId) {
169
+ return res.status(401).json({
170
+ status: 401,
171
+ message: "User not authenticated",
172
+ });
173
+ }
174
+
175
+ await User.findByIdAndUpdate(userId, { refreshToken: null });
176
+
177
+ return res.status(200).json({
178
+ status: 200,
179
+ message: "Logout successful",
180
+ });
181
+ } catch (error) {
182
+ return res.status(500).json({
183
+ status: 500,
184
+ message: "Logout failed",
185
+ });
186
+ }
187
+ }
188
+
189
+ async getProfile(req: Request, res: Response) {
190
+ try {
191
+ const user = await User.findById(req.user?.userId);
192
+
193
+ if (!user) {
194
+ return res.status(404).json({
195
+ status: 404,
196
+ message: "User not found",
197
+ });
198
+ }
199
+
200
+ return res.status(200).json({
201
+ status: 200,
202
+ message: "Profile retrieved successfully",
203
+ data: {
204
+ id: user._id,
205
+ name: user.name,
206
+ email: user.email,
207
+ createdAt: user.createdAt,
208
+ },
209
+ });
210
+ } catch (error) {
211
+ return res.status(500).json({
212
+ status: 500,
213
+ message: "Failed to get profile",
214
+ });
215
+ }
216
+ }
217
+ }
218
+
219
+ export const authController = new AuthController();
@@ -0,0 +1,31 @@
1
+ import { Request, Response } from "express";
2
+
3
+ class FileController {
4
+ async uploadFile(req: Request, res: Response) {
5
+ try {
6
+ if (!req.file) {
7
+ return res
8
+ .status(400)
9
+ .json({ success: false, message: "No file uploaded" });
10
+ }
11
+ return res.status(200).json({
12
+ success: true,
13
+ message: "File uploaded successfully",
14
+ data: {
15
+ filename: req.file.filename,
16
+ originalName: req.file.originalname,
17
+ size: req.file.size,
18
+ path: req.file.path,
19
+ },
20
+ });
21
+ } catch (error: any) {
22
+ return res.status(500).json({
23
+ success: false,
24
+ message: "Internal Server Error",
25
+ error: error?.message,
26
+ });
27
+ }
28
+ }
29
+ }
30
+
31
+ export const fileController = new FileController();
@@ -0,0 +1,160 @@
1
+ import { Request, Response } from 'express';
2
+ import User from '../models/user.model';
3
+
4
+ class UserController {
5
+ async getAllUsers(req: Request, res: Response) {
6
+ try {
7
+ const page = parseInt(req.query.page as string) || 1;
8
+ const limit = parseInt(req.query.limit as string) || 10;
9
+ const skip = (page - 1) * limit;
10
+
11
+ const searchQuery = req.query.search ? {
12
+ $or: [
13
+ { name: { $regex: String(req.query.search), $options: 'i' } },
14
+ { email: { $regex: String(req.query.search), $options: 'i' } }
15
+ ]
16
+ } : {};
17
+
18
+ const total = await User.countDocuments(searchQuery);
19
+
20
+ const users = await User.find(searchQuery)
21
+ .select('-password')
22
+ .skip(skip)
23
+ .limit(limit)
24
+ .sort({ createdAt: -1 });
25
+
26
+ const totalPages = Math.ceil(total / limit);
27
+
28
+ const userDetails = users.map(user => ({
29
+ id: user._id,
30
+ name: user.name,
31
+ email: user.email,
32
+ createdAt: user.createdAt
33
+ }));
34
+
35
+ return res.status(200).json({
36
+ status: 200,
37
+ message: 'Users retrieved successfully',
38
+ data: userDetails,
39
+ pagination: {
40
+ page,
41
+ limit,
42
+ total,
43
+ totalPages
44
+ }
45
+ });
46
+ } catch (error) {
47
+ return res.status(500).json({
48
+ status: 500,
49
+ message: 'Failed to get users'
50
+ });
51
+ }
52
+ }
53
+
54
+ async getUserById(req: Request, res: Response) {
55
+ try {
56
+ const { id } = req.params;
57
+
58
+ const user = await User.findById(id);
59
+
60
+ if (!user) {
61
+ return res.status(404).json({
62
+ status: 404,
63
+ message: 'User not found'
64
+ });
65
+ }
66
+
67
+ return res.status(200).json({
68
+ status: 200,
69
+ message: 'User retrieved successfully',
70
+ data: {
71
+ id: user._id,
72
+ name: user.name,
73
+ email: user.email,
74
+ createdAt: user.createdAt
75
+ }
76
+ });
77
+ } catch (error) {
78
+ return res.status(500).json({
79
+ status: 500,
80
+ message: 'Failed to get user'
81
+ });
82
+ }
83
+ }
84
+
85
+ async updateUser(req: Request, res: Response) {
86
+ try {
87
+ const { id } = req.params;
88
+ const { name, email } = req.body;
89
+
90
+ if (email) {
91
+ const existingUser = await User.findOne({ email, _id: { $ne: id } });
92
+ if (existingUser) {
93
+ return res.status(400).json({
94
+ status: 400,
95
+ message: 'Email already in use'
96
+ });
97
+ }
98
+ }
99
+
100
+ const user = await User.findByIdAndUpdate(
101
+ id,
102
+ { name, email },
103
+ { new: true, runValidators: true }
104
+ );
105
+
106
+ if (!user) {
107
+ return res.status(404).json({
108
+ status: 404,
109
+ message: 'User not found'
110
+ });
111
+ }
112
+
113
+ return res.status(200).json({
114
+ status: 200,
115
+ message: 'User updated successfully',
116
+ data: {
117
+ id: user._id,
118
+ name: user.name,
119
+ email: user.email,
120
+ updatedAt: user.updatedAt
121
+ }
122
+ });
123
+ } catch (error) {
124
+ return res.status(500).json({
125
+ status: 500,
126
+ message: 'Failed to update user'
127
+ });
128
+ }
129
+ }
130
+
131
+ async deleteUser(req: Request, res: Response) {
132
+ try {
133
+ const { id } = req.params;
134
+
135
+ const user = await User.findByIdAndDelete(id);
136
+
137
+ if (!user) {
138
+ return res.status(404).json({
139
+ status: 404,
140
+ message: 'User not found'
141
+ });
142
+ }
143
+
144
+ return res.status(200).json({
145
+ status: 200,
146
+ message: 'User deleted successfully',
147
+ data: {
148
+ id: user._id
149
+ }
150
+ });
151
+ } catch (error) {
152
+ return res.status(500).json({
153
+ status: 500,
154
+ message: 'Failed to delete user'
155
+ });
156
+ }
157
+ }
158
+ }
159
+
160
+ export const userController = new UserController();
@@ -0,0 +1,56 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { JWTUtil } from '../utils/jwt.util';
3
+ import User from '../models/user.model';
4
+
5
+ export const authenticate = async (
6
+ req: Request,
7
+ res: Response,
8
+ next: NextFunction
9
+ ) => {
10
+ try {
11
+ // Get token from header
12
+ const authHeader = req.headers.authorization;
13
+
14
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
15
+ return res.status(401).json({
16
+ status: 401,
17
+ message: 'No token provided'
18
+ });
19
+ }
20
+
21
+ const token = authHeader.split(' ')[1];
22
+
23
+ // Verify token
24
+ const decoded = JWTUtil.verifyToken(token);
25
+
26
+ if (!decoded) {
27
+ return res.status(401).json({
28
+ status: 401,
29
+ message: 'Invalid or expired token'
30
+ });
31
+ }
32
+
33
+ // Check if user exists
34
+ const user = await User.findById(decoded.userId);
35
+
36
+ if (!user) {
37
+ return res.status(401).json({
38
+ status: 401,
39
+ message: 'User not found'
40
+ });
41
+ }
42
+
43
+ // Attach user to request
44
+ req.user = {
45
+ userId: decoded.userId,
46
+ email: decoded.email
47
+ };
48
+
49
+ next();
50
+ } catch (error) {
51
+ return res.status(500).json({
52
+ status: 500,
53
+ message: 'Authentication failed'
54
+ });
55
+ }
56
+ };
@@ -0,0 +1,27 @@
1
+ import multer from "multer";
2
+
3
+ //Storing in uploads directory
4
+ const storage = multer.diskStorage({
5
+ destination: "uploads/",
6
+ filename(_req, file, callback) {
7
+ const fileName = file.originalname;
8
+ callback(null, `${Date.now()}-${fileName}`);
9
+ },
10
+ });
11
+
12
+ //FileFiller
13
+ const fileFillter: multer.Options["fileFilter"] = (_req, file, callback) => {
14
+ const allowedTypes = ["application/pdf", "text/csv"];
15
+
16
+ if (!allowedTypes.includes(file.mimetype)) {
17
+ return callback(new Error("only PDF and CSV file allowed"));
18
+ }
19
+
20
+ callback(null, true);
21
+ };
22
+
23
+ export const uploadMiddleware = multer({
24
+ storage,
25
+ fileFilter: fileFillter,
26
+ limits: { fileSize: 2 * 1024 * 1024 },
27
+ });
@@ -0,0 +1,70 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import Joi from 'joi';
3
+
4
+ export const validate = (schema: Joi.ObjectSchema) => {
5
+ return (req: Request, res: Response, next: NextFunction) => {
6
+ const { error, value } = schema.validate(req.body, {
7
+ abortEarly: false,
8
+ stripUnknown: true
9
+ });
10
+
11
+ if (error) {
12
+ const errors = error.details.map(detail => ({
13
+ field: detail.path.join('.'),
14
+ message: detail.message
15
+ }));
16
+
17
+ return res.status(400).json({
18
+ status: 400,
19
+ message: 'Validation failed',
20
+ data: errors
21
+ });
22
+ }
23
+
24
+ req.body = value;
25
+ next();
26
+ };
27
+ };
28
+
29
+ // Validation schemas
30
+ export const registerSchema = Joi.object({
31
+ name: Joi.string().min(2).max(50).required().messages({
32
+ 'string.min': 'Name must be at least 2 characters',
33
+ 'string.max': 'Name cannot exceed 50 characters',
34
+ 'any.required': 'Name is required'
35
+ }),
36
+ email: Joi.string().email().required().messages({
37
+ 'string.email': 'Please provide a valid email',
38
+ 'any.required': 'Email is required'
39
+ }),
40
+ password: Joi.string().min(6).required().messages({
41
+ 'string.min': 'Password must be at least 6 characters',
42
+ 'any.required': 'Password is required'
43
+ })
44
+ });
45
+
46
+ export const loginSchema = Joi.object({
47
+ email: Joi.string().email().required().messages({
48
+ 'string.email': 'Please provide a valid email',
49
+ 'any.required': 'Email is required'
50
+ }),
51
+ password: Joi.string().required().messages({
52
+ 'any.required': 'Password is required'
53
+ })
54
+ });
55
+
56
+ export const refreshTokenSchema = Joi.object({
57
+ refreshToken: Joi.string().required().messages({
58
+ 'any.required': 'Refresh token is required'
59
+ })
60
+ });
61
+
62
+ export const updateUserSchema = Joi.object({
63
+ name: Joi.string().min(2).max(50).messages({
64
+ 'string.min': 'Name must be at least 2 characters',
65
+ 'string.max': 'Name cannot exceed 50 characters'
66
+ }),
67
+ email: Joi.string().email().messages({
68
+ 'string.email': 'Please provide a valid email'
69
+ })
70
+ }).min(1);