Skip to content

Commit a7bf3e8

Browse files
committed
2 parents 33a13dc + 4930e1a commit a7bf3e8

File tree

591 files changed

+6683
-2555
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

591 files changed

+6683
-2555
lines changed

.gitignore

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
22

33
# dependencies
4-
/node_modules
5-
/.pnp
4+
node_modules
5+
.pnp
66
.pnp.js
77

88
# testing
9-
/coverage
9+
coverage
1010

1111
# production
12-
/build
12+
build
1313

1414
# misc
1515
.DS_Store

backend/constants.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const Constants = {
2+
PORT: process.env.PORT || 5000,
3+
ORIGIN_URL: process.env.ORIGIN_URL || 'http://localhost:3000',
4+
MONGODB_URL: process.env.MONGODB_URL || 'mongodb://localhost:27017/devdisplay',
5+
JSON_LIMIT: '50mb',
6+
ENV: process.env.NODE_ENV || 'development',
7+
API_BASE: '/api/v1',
8+
SALT_ROUNDS: 10,
9+
10+
// constraints
11+
USERNAME_MINLEN: 3,
12+
USERNAME_MAXLEN: 20,
13+
FULLNAME_MAXLEN: 30,
14+
FULLNAME_MINLEN: 2,
15+
BIO_MAXLEN: 200,
16+
17+
// JWT
18+
JWT_ALGORITHM: 'ES256',
19+
REFRESH_TOKEN_MAXAGE: 60 * 60 * 24 * 7, // 1 week
20+
ACCESS_TOKEN_MAXAGE: 60 * 20, // 20 minutes
21+
};
22+
23+
export default Constants;

backend/database/connectToDatabase.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import mongoose from 'mongoose';
2+
import CONSTANTS from '../constants.js';
3+
4+
async function connectToDatabase() {
5+
try {
6+
const uri = CONSTANTS.MONGODB_URL;
7+
const { connection } = await mongoose.connect(uri);
8+
if(CONSTANTS.ENV === 'development') {
9+
console.log(`🗄️ Connected to: ${uri}`);
10+
}
11+
console.info(`⚙️ MongoDB connected, DB HOST: ${connection.host}`);
12+
} catch (error) {
13+
console.error("⚠️ Error connecting to the database:", error);
14+
process.exit(1);
15+
}
16+
}
17+
18+
export default connectToDatabase;

backend/database/models/User.model.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Schema, model } from 'mongoose';
2+
import Constants from '../../constants.js';
3+
4+
const UserSchema = new Schema({
5+
// credentials
6+
username: {
7+
type: String,
8+
required: [true, "Username is a required field"],
9+
unique: true,
10+
index: true,
11+
trim: true,
12+
minlength: Constants.USERNAME_MINLEN,
13+
maxlength: Constants.USERNAME_MAXLEN,
14+
},
15+
email: {
16+
type: String,
17+
required: [true, "Email is a required field"],
18+
unique: true,
19+
trim: true,
20+
index: true,
21+
},
22+
passwordHash: {
23+
type: String,
24+
required: [true, "Password is a required field"],
25+
},
26+
27+
// profile
28+
fullName: {
29+
type: String,
30+
required: [true, "Full name is a required field"],
31+
trim: true,
32+
minlength: Constants.FULLNAME_MINLEN,
33+
maxlength: Constants.FULLNAME_MAXLEN,
34+
},
35+
location: {
36+
type: String,
37+
trim: true
38+
},
39+
bio: {
40+
type: String,
41+
trim: true,
42+
maxlength: Constants.BIO_MAXLEN
43+
},
44+
avatar: { type: String },
45+
portfolio: { type: String },
46+
skills: [{ type: String, trim: true }],
47+
social: {
48+
GitHub: { type: String },
49+
LinkedIn: { type: String },
50+
Twitter: { type: String },
51+
Instagram: { type: String },
52+
},
53+
54+
// meta
55+
emailVerified: { type: Boolean, default: false },
56+
refreshToken: { type: String },
57+
}, {
58+
timestamps: true,
59+
});
60+
61+
export default model('User', UserSchema);

backend/helpers/ApiError.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// API error helper functions
2+
import { errorResponse } from './ApiResponse.js';
3+
4+
const badRequest = (message, error) => errorResponse(400, message, error);
5+
const validationError = (message, error) => errorResponse(400, message, error);
6+
const semanticError = (message, error) => errorResponse(422, message, error);
7+
const unauthorized = (message, error) => errorResponse(401, message, error);
8+
const forbidden = (message, error) => errorResponse(403, message, error);
9+
const notFound = (message, error) => errorResponse(404, message, error);
10+
const conflict = (message, error) => errorResponse(409, message, error);
11+
const internalServerError = (message, error) => errorResponse(500, message, error);
12+
const unsupportedMediaType = (message, error) => errorResponse(415, message, error);
13+
14+
export {
15+
badRequest,
16+
unauthorized,
17+
forbidden,
18+
notFound,
19+
conflict,
20+
internalServerError,
21+
validationError,
22+
semanticError,
23+
unsupportedMediaType,
24+
};

backend/helpers/ApiResponse.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// standardized API response format
2+
import Constants from "../constants.js";
3+
4+
class ApiResponse {
5+
constructor(status, message, data = null, errorType = 'UNSPECIFIED', error = null) {
6+
this.success = status < 300 ? true : false; // Boolean value indicating whether the request was successful or not
7+
this.message = message, // Brief description of the result
8+
this.data = data || null, // Contains the actual response data (if any)
9+
this.status = status || 200 // HTTP status code
10+
11+
// If the environment is development, include the error details in the response
12+
// POLICY DECISION ?? Only include error details in response if in development mode
13+
if(!this.success) {
14+
this.error = Constants.ENV === 'development' ? error : null;
15+
this.errorType = Constants.ENV === 'development' ? errorType : null;
16+
}
17+
18+
// Log the response details to the console
19+
// DEVELOPMENT ONLY
20+
if (Constants.ENV === 'development') {
21+
console.log("[API RESPONSE START]");
22+
console.log(`[API RESPONSE] ${this.success ? 'SUCCESS' : 'FAILURE'}`);
23+
console.log(`[API RESPONSE] STATUS: ${this.status}`);
24+
console.log(`[API RESPONSE] MESSAGE: ${this.message}`);
25+
console.log("[API RESPONSE] DATA:-");
26+
console.dir(this.data);
27+
if(!this.success) {
28+
console.log("[API RESPONSE] ERROR:-");
29+
console.dir(this.error);
30+
console.log(`[API RESPONSE] ERROR TYPE: ${this.errorType}`);
31+
}
32+
console.log(`[API RESPONSE] TIMESTAMP: ${new Date().toISOString()}`);
33+
console.log("[API RESPONSE END]");
34+
}
35+
}
36+
}
37+
38+
// Helper functions to send API responses
39+
/** successResponse(res, data, message) => void */
40+
const successResponse = (res, data, message) => {
41+
res.status(200).json(new ApiResponse(200, message, data));
42+
}
43+
44+
/** successResponseWithCookies(res, data, message, cookies) => void */
45+
const successResponseWithCookies = (res, data, message, cookies) => {
46+
const options = { httpOnly: true, secure: true, sameSite: "strict" };
47+
48+
if(cookies) {
49+
if(cookies.accessToken) {
50+
res
51+
.status(200)
52+
.cookie("accessToken", cookies.accessToken, options)
53+
.json(new ApiResponse(200, message, data));
54+
}
55+
if(cookies.refreshToken) {
56+
res
57+
.status(200)
58+
.cookie("refreshToken", cookies.refreshToken, options)
59+
.json(new ApiResponse(200, message, data));
60+
}
61+
}
62+
}
63+
64+
/** errorResponse(res, status, message, errorType, error) => void */
65+
const errorResponse = (res, status, message, errorType, error) => {
66+
res.status(status).json(new ApiResponse(status, message, null, errorType, error));
67+
}
68+
69+
export { successResponse, errorResponse, successResponseWithCookies };

backend/helpers/AsyncHandler.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// a promise block wrapper, helps to reduce the try-catch blocks in controllers
2+
// also standardizes the error handling by forwarding the error to the error-handling middleware
3+
4+
const asyncHandler = (fn) => {
5+
return (req, res, next) => {
6+
Promise
7+
.resolve(fn(req, res, next)) // converts the sync function into a async promise
8+
.catch((err) => next(err)); // pass the error to the error-handling middleware
9+
};
10+
};
11+
12+
export default asyncHandler;

0 commit comments

Comments
 (0)