Skip to content

Commit 749f737

Browse files
authored
Merge pull request #11 from foyzulkarim/feature/refactor-and-fix-demo-fails
Refactoring and Error Handling Enhancements
2 parents bb668e9 + 52872b2 commit 749f737

File tree

16 files changed

+446
-129
lines changed

16 files changed

+446
-129
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nodejs-boilerplate",
3-
"version": "1.0.4",
3+
"version": "1.1.0",
44
"description": "[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)",
55
"main": "index.js",
66
"directories": {

src/auth/index.js

Lines changed: 77 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,76 @@ function decryptToken(encryptedToken, iv) {
3535
return decrypted;
3636
}
3737

38+
async function getOrCreateUserFromGitHubProfile({ profile, accessToken }) {
39+
const isAdmin = config.ADMIN_USERNAMES.includes(profile.username);
40+
// Create a new user from GitHub API Profile data
41+
const payload = {
42+
githubId: profile.id,
43+
nodeId: profile.nodeId,
44+
displayName: profile.displayName,
45+
username: profile.username,
46+
profileUrl: profile.profileUrl,
47+
48+
avatarUrl: profile._json.avatar_url,
49+
apiUrl: profile._json.url,
50+
company: profile._json.company,
51+
blog: profile._json.blog,
52+
location: profile._json.location,
53+
email: profile._json.email,
54+
hireable: profile._json.hireable,
55+
bio: profile._json.bio,
56+
public_repos: profile._json.public_repos,
57+
public_gists: profile._json.public_gists,
58+
followers: profile._json.followers,
59+
following: profile._json.following,
60+
created_at: profile._json.created_at,
61+
updated_at: profile._json.updated_at,
62+
63+
isDemo: false,
64+
isVerified: true,
65+
isAdmin,
66+
};
67+
68+
let user = await getByGitHubId(profile.id);
69+
70+
const tokenInfo = encryptToken(accessToken);
71+
if (user) {
72+
if (user.isDeactivated) {
73+
throw new AppError('user-is-deactivated', 'User is deactivated', 401);
74+
}
75+
76+
// Update the user with the latest data
77+
user = Object.assign(user, payload, {
78+
accessToken: tokenInfo.token,
79+
accessTokenIV: tokenInfo.iv,
80+
updatedAt: new Date(),
81+
});
82+
await updateById(user._id, user);
83+
} else {
84+
// Create a new user
85+
user = await create({
86+
...payload,
87+
accessToken: tokenInfo.token,
88+
accessTokenIV: tokenInfo.iv,
89+
});
90+
}
91+
const userObj = user.toObject();
92+
const trimmedPayloadForSession = {
93+
_id: userObj._id,
94+
githubId: userObj.githubId,
95+
nodeId: userObj.nodeId,
96+
isAdmin: userObj.isAdmin,
97+
isDeactivated: userObj.isDeactivated,
98+
isDemo: userObj.isDemo,
99+
// UI info
100+
username: userObj.username,
101+
displayName: userObj.displayName,
102+
avatarUrl: userObj.avatarUrl,
103+
email: userObj.email,
104+
};
105+
return trimmedPayloadForSession;
106+
}
107+
38108
const getGitHubStrategy = () => {
39109
return new GitHubStrategy(
40110
{
@@ -44,76 +114,12 @@ const getGitHubStrategy = () => {
44114
},
45115
async (accessToken, refreshToken, profile, cb) => {
46116
try {
47-
const isAdmin = config.ADMIN_USERNAMES.includes(profile.username);
48-
// Create a new user from GitHub API Profile data
49-
const payload = {
50-
githubId: profile.id,
51-
nodeId: profile.nodeId,
52-
displayName: profile.displayName,
53-
username: profile.username,
54-
profileUrl: profile.profileUrl,
55-
56-
avatarUrl: profile._json.avatar_url,
57-
apiUrl: profile._json.url,
58-
company: profile._json.company,
59-
blog: profile._json.blog,
60-
location: profile._json.location,
61-
email: profile._json.email,
62-
hireable: profile._json.hireable,
63-
bio: profile._json.bio,
64-
public_repos: profile._json.public_repos,
65-
public_gists: profile._json.public_gists,
66-
followers: profile._json.followers,
67-
following: profile._json.following,
68-
created_at: profile._json.created_at,
69-
updated_at: profile._json.updated_at,
70-
71-
isDemo: false,
72-
isVerified: true,
73-
isAdmin,
74-
};
75-
76-
let user = await getByGitHubId(profile.id);
77-
78-
const tokenInfo = encryptToken(accessToken);
79-
if (user) {
80-
if (user.isDeactivated) {
81-
throw new AppError(
82-
'user-is-deactivated',
83-
'User is deactivated',
84-
401
85-
);
117+
const trimmedPayloadForSession = await getOrCreateUserFromGitHubProfile(
118+
{
119+
profile,
120+
accessToken,
86121
}
87-
88-
// Update the user with the latest data
89-
user = Object.assign(user, payload, {
90-
accessToken: tokenInfo.token,
91-
accessTokenIV: tokenInfo.iv,
92-
updatedAt: new Date(),
93-
});
94-
await updateById(user._id, user);
95-
} else {
96-
// Create a new user
97-
user = await create({
98-
...payload,
99-
accessToken: tokenInfo.token,
100-
accessTokenIV: tokenInfo.iv,
101-
});
102-
}
103-
const userObj = user.toObject();
104-
const trimmedPayloadForSession = {
105-
_id: userObj._id,
106-
githubId: userObj.githubId,
107-
nodeId: userObj.nodeId,
108-
isAdmin: userObj.isAdmin,
109-
isDeactivated: userObj.isDeactivated,
110-
isDemo: userObj.isDemo,
111-
// UI info
112-
username: userObj.username,
113-
displayName: userObj.displayName,
114-
avatarUrl: userObj.avatarUrl,
115-
email: userObj.email,
116-
};
122+
);
117123

118124
cb(null, trimmedPayloadForSession); // Pass the user object to the session
119125
} catch (error) {
@@ -133,7 +139,9 @@ const clearAuthInfo = async (userId) => {
133139
};
134140

135141
module.exports = {
142+
getOrCreateUserFromGitHubProfile,
136143
getGitHubStrategy,
137144
clearAuthInfo,
145+
encryptToken,
138146
decryptToken,
139147
};

src/domains/product/api.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ const routes = () => {
7171
async (req, res, next) => {
7272
try {
7373
const item = await updateById(req.params.id, req.body);
74-
if (!item) {
75-
throw new AppError(`${model} not found`, `${model} not found`, 404);
76-
}
7774
res.status(200).json(item);
7875
} catch (error) {
7976
next(error);

src/domains/product/service.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,29 @@ const search = async (query) => {
4444
const getById = async (id) => {
4545
try {
4646
const item = await Model.findById(id);
47+
if (!item) {
48+
throw new AppError(`${model} not found`, `${model} not found`, 404);
49+
}
4750
logger.info(`getById(): ${model} fetched`, { id });
4851
return item;
4952
} catch (error) {
5053
logger.error(`getById(): Failed to get ${model}`, error);
51-
throw new AppError(`Failed to get ${model}`, error.message);
54+
throw new AppError(`Failed to get ${model}`, error.message, error.HTTPStatus || 400);
5255
}
5356
};
5457

5558
const updateById = async (id, data) => {
5659
try {
5760
const item = await Model.findByIdAndUpdate(id, data, { new: true });
61+
if (!item) {
62+
throw new AppError(`${model} not found`, `${model} not found`, 404);
63+
}
64+
5865
logger.info(`updateById(): ${model} updated`, { id });
5966
return item;
6067
} catch (error) {
6168
logger.error(`updateById(): Failed to update ${model}`, error);
62-
throw new AppError(`Failed to update ${model}`, error.message);
69+
throw new AppError(`Failed to update ${model}`, error.message, error.HTTPStatus || 400);
6370
}
6471
};
6572

src/domains/repository/api.js

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const routes = () => {
3131
const router = express.Router();
3232
logger.info(`Setting up routes for ${model}`);
3333

34+
// '/search'
3435
router.get(
3536
'/search',
3637
logRequest({}),
@@ -46,6 +47,7 @@ const routes = () => {
4647
}
4748
);
4849

50+
// '/count',
4951
router.get(
5052
'/count',
5153
logRequest({}),
@@ -60,6 +62,7 @@ const routes = () => {
6062
}
6163
);
6264

65+
// '/search-one'
6366
router.post(
6467
'/search-one',
6568
logRequest({}),
@@ -75,7 +78,7 @@ const routes = () => {
7578
}
7679
);
7780

78-
// fetch repository details from GitHub API
81+
// '/fetch-from-github' fetch repository details from GitHub API
7982
router.post(
8083
'/fetch-from-github',
8184
logRequest({}),
@@ -95,6 +98,7 @@ const routes = () => {
9598
}
9699
);
97100

101+
//'/:id/follow'
98102
router.get(
99103
'/:id/follow',
100104
logRequest({}),
@@ -110,20 +114,7 @@ const routes = () => {
110114
}
111115
);
112116

113-
router.post(
114-
'/',
115-
logRequest({}),
116-
validateRequest({ schema: createSchema }),
117-
async (req, res, next) => {
118-
try {
119-
const item = await create(req.body);
120-
res.status(201).json(item);
121-
} catch (error) {
122-
next(error);
123-
}
124-
}
125-
);
126-
117+
//'/:id',
127118
router.get(
128119
'/:id',
129120
logRequest({}),

src/domains/repository/service.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const Model = require('./schema');
44
const User = require('../user/schema');
55
const { AppError } = require('../../libraries/error-handling/AppError');
66

7-
const { fetchRepoDetails } = require('../../libraries/util/githubUtils');
7+
const github = require('../../libraries/util/githubUtils');
88
const { decryptToken } = require('../../auth');
99

1010
const model = 'repository';
@@ -122,11 +122,15 @@ const searchOne = async (searchPayload) => {
122122
const getById = async (id) => {
123123
try {
124124
const item = await Model.findById(id);
125-
logger.info(`getById(): ${model} fetched`, { id, _id: item._id });
125+
logger.info(`getById(): ${model} fetched`, { id, _id: item?._id });
126126
return item;
127127
} catch (error) {
128128
logger.error(`getById(): Failed to get ${model}`, error);
129-
throw new AppError(`Failed to get ${model}`, error.message);
129+
throw new AppError(
130+
`Failed to get ${model}`,
131+
error.message,
132+
error.HTTPStatus || 400
133+
);
130134
}
131135
};
132136

@@ -227,7 +231,7 @@ const fetchGitHubRepoDetails = async (owner, repo, user) => {
227231
const { accessToken, accessTokenIV } = dbUser;
228232
const token = decryptToken(accessToken, accessTokenIV);
229233

230-
const response = await fetchRepoDetails(owner, repo, token);
234+
const response = await github.fetchRepoDetails(owner, repo, token);
231235
// check if the repository already exists in the database by id node_id
232236
// if it exists, update the repository details using mapSelectedGithubResponseToSchema
233237
// else create the repository using mapGithubResponseToSchema
@@ -269,7 +273,7 @@ const followRepository = async (followerId, repositoryId) => {
269273
const [repositoryUpdate, followerUserUpdate] = await Promise.all([
270274
// Update csFollowers of the repository
271275
Model.findByIdAndUpdate(repositoryId, {
272-
$push: { csFollowers: { _id: followerId, date: Date.now() } },
276+
$push: { csFollowers: { _id: followerId, date: Date.now() } },
273277
}),
274278

275279
// Update csFollowingRepositories of the follower user

0 commit comments

Comments
 (0)